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);
|
const inputs = getBoundInputs(this.dir, this.node, this.tcb);
|
||||||
for (const input of inputs) {
|
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) {
|
for (const fieldName of input.fieldNames) {
|
||||||
// Skip the field if an attribute has already been bound to it; we can't have a duplicate
|
// Skip the field if an attribute has already been bound to it; we can't have a duplicate
|
||||||
// key in the type constructor call.
|
// key in the type constructor call.
|
||||||
@ -654,6 +659,12 @@ class TcbDirectiveInputsOp extends TcbOp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addParseSpanInfo(assignment, input.attribute.sourceSpan);
|
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));
|
this.scope.addStatement(ts.createExpressionStatement(assignment));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1732,11 +1743,6 @@ function getBoundInputs(
|
|||||||
return;
|
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.
|
// Skip the attribute if the directive does not have an input for it.
|
||||||
const inputs = directive.inputs.getByBindingPropertyName(attr.name);
|
const inputs = directive.inputs.getByBindingPropertyName(attr.name);
|
||||||
if (inputs === null) {
|
if (inputs === null) {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import {ASTWithSource, Binary, BindingPipe, Conditional, Interpolation, PropertyRead, TmplAstBoundAttribute, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate} from '@angular/compiler';
|
import {ASTWithSource, Binary, BindingPipe, Conditional, Interpolation, PropertyRead, TmplAstBoundAttribute, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
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 {runInEachFileSystem} from '../../file_system/testing';
|
||||||
import {ClassDeclaration} from '../../reflection';
|
import {ClassDeclaration} from '../../reflection';
|
||||||
import {DirectiveSymbol, DomBindingSymbol, ElementSymbol, ExpressionSymbol, InputBindingSymbol, OutputBindingSymbol, ReferenceSymbol, Symbol, SymbolKind, TemplateSymbol, TemplateTypeChecker, TypeCheckingConfig, VariableSymbol} from '../api';
|
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);
|
expect(beforeSymbol).not.toBe(afterSymbol);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get a symbol for text attributes corresponding with a directive input', () => {
|
describe('should get a symbol for text attributes corresponding with a directive input', () => {
|
||||||
const fileName = absoluteFrom('/main.ts');
|
let fileName: AbsoluteFsPath;
|
||||||
|
let targets: TypeCheckingTarget[];
|
||||||
|
beforeEach(() => {
|
||||||
|
fileName = absoluteFrom('/main.ts');
|
||||||
const dirFile = absoluteFrom('/dir.ts');
|
const dirFile = absoluteFrom('/dir.ts');
|
||||||
const templateString = `<div name="helloWorld"></div>`;
|
const templateString = `<div name="helloWorld"></div>`;
|
||||||
const {templateTypeChecker, program} = setup(
|
targets = [
|
||||||
[
|
|
||||||
{
|
{
|
||||||
fileName,
|
fileName,
|
||||||
templates: {'Cmp': templateString},
|
templates: {'Cmp': templateString} as {[key: string]: string},
|
||||||
declarations: [{
|
declarations: [{
|
||||||
name: 'NameDiv',
|
name: 'NameDiv',
|
||||||
selector: 'div[name]',
|
selector: 'div[name]',
|
||||||
file: dirFile,
|
file: dirFile,
|
||||||
type: 'directive',
|
type: 'directive' as const,
|
||||||
inputs: {name: 'name'},
|
inputs: {name: 'name'},
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
@ -88,12 +90,14 @@ runInEachFileSystem(() => {
|
|||||||
source: `export class NameDiv {name!: string;}`,
|
source: `export class NameDiv {name!: string;}`,
|
||||||
templates: {},
|
templates: {},
|
||||||
}
|
}
|
||||||
],
|
];
|
||||||
);
|
});
|
||||||
|
|
||||||
|
it('checkTypeOfAttributes = true', () => {
|
||||||
|
const {templateTypeChecker, program} = setup(targets, {checkTypeOfAttributes: true});
|
||||||
const sf = getSourceFileOrError(program, fileName);
|
const sf = getSourceFileOrError(program, fileName);
|
||||||
const cmp = getClass(sf, 'Cmp');
|
const cmp = getClass(sf, 'Cmp');
|
||||||
const {attributes} = getAstElements(templateTypeChecker, cmp)[0];
|
const {attributes} = getAstElements(templateTypeChecker, cmp)[0];
|
||||||
|
|
||||||
const symbol = templateTypeChecker.getSymbolOfNode(attributes[0], cmp)!;
|
const symbol = templateTypeChecker.getSymbolOfNode(attributes[0], cmp)!;
|
||||||
assertInputBindingSymbol(symbol);
|
assertInputBindingSymbol(symbol);
|
||||||
expect(
|
expect(
|
||||||
@ -106,6 +110,19 @@ runInEachFileSystem(() => {
|
|||||||
expect(mapping.span.toString()).toEqual('name');
|
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('templates', () => {
|
||||||
describe('ng-templates', () => {
|
describe('ng-templates', () => {
|
||||||
let templateTypeChecker: TemplateTypeChecker;
|
let templateTypeChecker: TemplateTypeChecker;
|
||||||
|
@ -104,6 +104,7 @@ function quickInfoSkeleton(): TestFile[] {
|
|||||||
describe('quick info', () => {
|
describe('quick info', () => {
|
||||||
let env: LanguageServiceTestEnvironment;
|
let env: LanguageServiceTestEnvironment;
|
||||||
|
|
||||||
|
describe('strict templates (happy path)', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
initMockFileSystem('Native');
|
initMockFileSystem('Native');
|
||||||
env = LanguageServiceTestEnvironment.setup(quickInfoSkeleton());
|
env = LanguageServiceTestEnvironment.setup(quickInfoSkeleton());
|
||||||
@ -168,7 +169,8 @@ describe('quick info', () => {
|
|||||||
|
|
||||||
it('should work for directives with compound selectors, some of which are bindings', () => {
|
it('should work for directives with compound selectors, some of which are bindings', () => {
|
||||||
expectQuickInfo({
|
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',
|
expectedSpanText: 'ngFor',
|
||||||
expectedDisplayString: '(directive) NgForOf<Hero, Hero[]>'
|
expectedDisplayString: '(directive) NgForOf<Hero, Hero[]>'
|
||||||
});
|
});
|
||||||
@ -462,6 +464,20 @@ describe('quick info', () => {
|
|||||||
expect(documentation).toBe('This is the title of the `AppCmp` Component.');
|
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(
|
function expectQuickInfo(
|
||||||
{templateOverride, expectedSpanText, expectedDisplayString}:
|
{templateOverride, expectedSpanText, expectedDisplayString}:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user