refactor(compiler-cli): TemplateTypeChecker with checkTypeOfAttributes=false should still work (#39537)
When the compiler option `checkTypeOfAttributes` is `false`, we should still be able to produce type information from the `TemplateTypeChecker`. The current behavior ignores all attributes that map to directive inputs. This commit includes those attribute bindings in the TCB but adds the "ignore for diagnostics" marker so they do not produce errors. This way, consumers of the TTC (the Language Service) can still get valid information about these attributes even when the user has configured the compiler to not produce diagnostics/errors for them. PR Close #39537
This commit is contained in:
parent
86fdc77ddf
commit
a694838c41
@ -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) {
|
||||
|
@ -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,20 +66,22 @@ runInEachFileSystem(() => {
|
||||
expect(beforeSymbol).not.toBe(afterSymbol);
|
||||
});
|
||||
|
||||
it('should get a symbol for text attributes corresponding with a directive input', () => {
|
||||
const fileName = absoluteFrom('/main.ts');
|
||||
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 = `<div name="helloWorld"></div>`;
|
||||
const {templateTypeChecker, program} = setup(
|
||||
[
|
||||
targets = [
|
||||
{
|
||||
fileName,
|
||||
templates: {'Cmp': templateString},
|
||||
templates: {'Cmp': templateString} as {[key: string]: string},
|
||||
declarations: [{
|
||||
name: 'NameDiv',
|
||||
selector: 'div[name]',
|
||||
file: dirFile,
|
||||
type: 'directive',
|
||||
type: 'directive' as const,
|
||||
inputs: {name: 'name'},
|
||||
}]
|
||||
},
|
||||
@ -88,12 +90,14 @@ runInEachFileSystem(() => {
|
||||
source: `export class NameDiv {name!: string;}`,
|
||||
templates: {},
|
||||
}
|
||||
],
|
||||
);
|
||||
];
|
||||
});
|
||||
|
||||
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(
|
||||
@ -106,6 +110,19 @@ runInEachFileSystem(() => {
|
||||
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', () => {
|
||||
describe('ng-templates', () => {
|
||||
let templateTypeChecker: TemplateTypeChecker;
|
||||
|
@ -104,6 +104,7 @@ function quickInfoSkeleton(): TestFile[] {
|
||||
describe('quick info', () => {
|
||||
let env: LanguageServiceTestEnvironment;
|
||||
|
||||
describe('strict templates (happy path)', () => {
|
||||
beforeEach(() => {
|
||||
initMockFileSystem('Native');
|
||||
env = LanguageServiceTestEnvironment.setup(quickInfoSkeleton());
|
||||
@ -168,7 +169,8 @@ describe('quick info', () => {
|
||||
|
||||
it('should work for directives with compound selectors, some of which are bindings', () => {
|
||||
expectQuickInfo({
|
||||
templateOverride: `<ng-template ngF¦or let-hero [ngForOf]="heroes">{{hero}}</ng-template>`,
|
||||
templateOverride:
|
||||
`<ng-template ngF¦or let-hero [ngForOf]="heroes">{{hero}}</ng-template>`,
|
||||
expectedSpanText: 'ngFor',
|
||||
expectedDisplayString: '(directive) NgForOf<Hero, Hero[]>'
|
||||
});
|
||||
@ -462,6 +464,20 @@ describe('quick info', () => {
|
||||
expect(documentation).toBe('This is the title of the `AppCmp` Component.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('non-strict compiler options', () => {
|
||||
it('should find input binding on text attribute when strictAttributeTypes is false', () => {
|
||||
initMockFileSystem('Native');
|
||||
env =
|
||||
LanguageServiceTestEnvironment.setup(quickInfoSkeleton(), {strictAttributeTypes: false});
|
||||
expectQuickInfo({
|
||||
templateOverride: `<test-comp tcN¦ame="title"></test-comp>`,
|
||||
expectedSpanText: 'tcName',
|
||||
expectedDisplayString: '(property) TestComponent.name: string'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function expectQuickInfo(
|
||||
{templateOverride, expectedSpanText, expectedDisplayString}:
|
||||
|
Loading…
x
Reference in New Issue
Block a user