feat(ivy): disable strict null checks for input bindings (#33066)
This commit introduces an internal config option of the template type checker that allows to disable strict null checks of input bindings to directives. This may be particularly useful when a directive is from a library that is not compiled with `strictNullChecks` enabled. Right now, strict null checks are enabled when `fullTemplateTypeCheck` is turned on, and disabled when it's off. In the near future, several of the internal configuration options will be added as public Angular compiler options so that users can have fine-grained control over which areas of the template type checker to enable, allowing for a more incremental migration strategy. PR Close #33066
This commit is contained in:
parent
50bf17aca0
commit
ece0b2d7ce
|
@ -401,6 +401,7 @@ export class NgtscProgram implements api.Program {
|
||||||
checkQueries: false,
|
checkQueries: false,
|
||||||
checkTemplateBodies: true,
|
checkTemplateBodies: true,
|
||||||
checkTypeOfInputBindings: true,
|
checkTypeOfInputBindings: true,
|
||||||
|
strictNullInputBindings: true,
|
||||||
// Even in full template type-checking mode, DOM binding checks are not quite ready yet.
|
// Even in full template type-checking mode, DOM binding checks are not quite ready yet.
|
||||||
checkTypeOfDomBindings: false,
|
checkTypeOfDomBindings: false,
|
||||||
checkTypeOfPipes: true,
|
checkTypeOfPipes: true,
|
||||||
|
@ -412,6 +413,7 @@ export class NgtscProgram implements api.Program {
|
||||||
checkQueries: false,
|
checkQueries: false,
|
||||||
checkTemplateBodies: false,
|
checkTemplateBodies: false,
|
||||||
checkTypeOfInputBindings: false,
|
checkTypeOfInputBindings: false,
|
||||||
|
strictNullInputBindings: false,
|
||||||
checkTypeOfDomBindings: false,
|
checkTypeOfDomBindings: false,
|
||||||
checkTypeOfPipes: false,
|
checkTypeOfPipes: false,
|
||||||
strictSafeNavigationTypes: false,
|
strictSafeNavigationTypes: false,
|
||||||
|
|
|
@ -83,6 +83,18 @@ export interface TypeCheckingConfig {
|
||||||
*/
|
*/
|
||||||
checkTypeOfInputBindings: boolean;
|
checkTypeOfInputBindings: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use strict null types for input bindings for directives.
|
||||||
|
*
|
||||||
|
* If this is `true`, applications that are compiled with TypeScript's `strictNullChecks` enabled
|
||||||
|
* will produce type errors for bindings which can evaluate to `undefined` or `null` where the
|
||||||
|
* inputs's type does not include `undefined` or `null` in its type. If set to `false`, all
|
||||||
|
* binding expressions are wrapped in a non-null assertion operator to effectively disable strict
|
||||||
|
* null checks. This may be particularly useful when the directive is from a library that is not
|
||||||
|
* compiled with `strictNullChecks` enabled.
|
||||||
|
*/
|
||||||
|
strictNullInputBindings: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to check the left-hand side type of binding operations to DOM properties.
|
* Whether to check the left-hand side type of binding operations to DOM properties.
|
||||||
*
|
*
|
||||||
|
|
|
@ -388,11 +388,14 @@ class TcbUnclaimedInputsOp extends TcbOp {
|
||||||
|
|
||||||
let expr = tcbExpression(
|
let expr = tcbExpression(
|
||||||
binding.value, this.tcb, this.scope, binding.valueSpan || binding.sourceSpan);
|
binding.value, this.tcb, this.scope, binding.valueSpan || binding.sourceSpan);
|
||||||
|
|
||||||
// If checking the type of bindings is disabled, cast the resulting expression to 'any' before
|
|
||||||
// the assignment.
|
|
||||||
if (!this.tcb.env.config.checkTypeOfInputBindings) {
|
if (!this.tcb.env.config.checkTypeOfInputBindings) {
|
||||||
|
// If checking the type of bindings is disabled, cast the resulting expression to 'any'
|
||||||
|
// before the assignment.
|
||||||
expr = tsCastToAny(expr);
|
expr = tsCastToAny(expr);
|
||||||
|
} else if (!this.tcb.env.config.strictNullInputBindings) {
|
||||||
|
// If strict null checks are disabled, erase `null` and `undefined` from the type by
|
||||||
|
// wrapping the expression in a non-null assertion.
|
||||||
|
expr = ts.createNonNullExpression(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.tcb.env.config.checkTypeOfDomBindings && binding.type === BindingType.Property) {
|
if (this.tcb.env.config.checkTypeOfDomBindings && binding.type === BindingType.Property) {
|
||||||
|
@ -781,11 +784,18 @@ function tcbCallTypeCtor(
|
||||||
const members = inputs.map(input => {
|
const members = inputs.map(input => {
|
||||||
if (input.type === 'binding') {
|
if (input.type === 'binding') {
|
||||||
// For bound inputs, the property is assigned the binding expression.
|
// For bound inputs, the property is assigned the binding expression.
|
||||||
let expression = input.expression;
|
let expr = input.expression;
|
||||||
if (!tcb.env.config.checkTypeOfInputBindings) {
|
if (!tcb.env.config.checkTypeOfInputBindings) {
|
||||||
expression = tsCastToAny(expression);
|
// If checking the type of bindings is disabled, cast the resulting expression to 'any'
|
||||||
|
// before the assignment.
|
||||||
|
expr = tsCastToAny(expr);
|
||||||
|
} else if (!tcb.env.config.strictNullInputBindings) {
|
||||||
|
// If strict null checks are disabled, erase `null` and `undefined` from the type by
|
||||||
|
// wrapping the expression in a non-null assertion.
|
||||||
|
expr = ts.createNonNullExpression(expr);
|
||||||
}
|
}
|
||||||
const assignment = ts.createPropertyAssignment(input.field, wrapForDiagnostics(expression));
|
|
||||||
|
const assignment = ts.createPropertyAssignment(input.field, wrapForDiagnostics(expr));
|
||||||
addParseSpanInfo(assignment, input.sourceSpan);
|
addParseSpanInfo(assignment, input.sourceSpan);
|
||||||
return assignment;
|
return assignment;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -119,6 +119,7 @@ export const ALL_ENABLED_CONFIG: TypeCheckingConfig = {
|
||||||
checkQueries: false,
|
checkQueries: false,
|
||||||
checkTemplateBodies: true,
|
checkTemplateBodies: true,
|
||||||
checkTypeOfInputBindings: true,
|
checkTypeOfInputBindings: true,
|
||||||
|
strictNullInputBindings: true,
|
||||||
// Feature is still in development.
|
// Feature is still in development.
|
||||||
// TODO(alxhub): enable when DOM checking via lib.dom.d.ts is further along.
|
// TODO(alxhub): enable when DOM checking via lib.dom.d.ts is further along.
|
||||||
checkTypeOfDomBindings: false,
|
checkTypeOfDomBindings: false,
|
||||||
|
@ -160,6 +161,7 @@ export function tcb(
|
||||||
applyTemplateContextGuards: true,
|
applyTemplateContextGuards: true,
|
||||||
checkQueries: false,
|
checkQueries: false,
|
||||||
checkTypeOfInputBindings: true,
|
checkTypeOfInputBindings: true,
|
||||||
|
strictNullInputBindings: true,
|
||||||
checkTypeOfDomBindings: false,
|
checkTypeOfDomBindings: false,
|
||||||
checkTypeOfPipes: true,
|
checkTypeOfPipes: true,
|
||||||
checkTemplateBodies: true,
|
checkTemplateBodies: true,
|
||||||
|
|
|
@ -222,6 +222,7 @@ describe('type check blocks', () => {
|
||||||
checkQueries: false,
|
checkQueries: false,
|
||||||
checkTemplateBodies: true,
|
checkTemplateBodies: true,
|
||||||
checkTypeOfInputBindings: true,
|
checkTypeOfInputBindings: true,
|
||||||
|
strictNullInputBindings: true,
|
||||||
checkTypeOfDomBindings: false,
|
checkTypeOfDomBindings: false,
|
||||||
checkTypeOfPipes: true,
|
checkTypeOfPipes: true,
|
||||||
strictSafeNavigationTypes: true,
|
strictSafeNavigationTypes: true,
|
||||||
|
@ -257,20 +258,37 @@ describe('type check blocks', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('config.strictNullInputBindings', () => {
|
||||||
|
const TEMPLATE = `<div dir [dirInput]="a" [nonDirInput]="b"></div>`;
|
||||||
|
|
||||||
|
it('should include null and undefined when enabled', () => {
|
||||||
|
const block = tcb(TEMPLATE, DIRECTIVES);
|
||||||
|
expect(block).toContain('Dir.ngTypeCtor({ dirInput: ((ctx).a) })');
|
||||||
|
expect(block).toContain('(ctx).b;');
|
||||||
|
});
|
||||||
|
it('should use the non-null assertion operator when disabled', () => {
|
||||||
|
const DISABLED_CONFIG:
|
||||||
|
TypeCheckingConfig = {...BASE_CONFIG, strictNullInputBindings: false};
|
||||||
|
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
|
||||||
|
expect(block).toContain('Dir.ngTypeCtor({ dirInput: ((ctx).a!) })');
|
||||||
|
expect(block).toContain('(ctx).b!;');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('config.checkTypeOfBindings', () => {
|
describe('config.checkTypeOfBindings', () => {
|
||||||
const TEMPLATE = `<div dir [dirInput]="a" [nonDirInput]="a"></div>`;
|
const TEMPLATE = `<div dir [dirInput]="a" [nonDirInput]="b"></div>`;
|
||||||
|
|
||||||
it('should check types of bindings when enabled', () => {
|
it('should check types of bindings when enabled', () => {
|
||||||
const block = tcb(TEMPLATE, DIRECTIVES);
|
const block = tcb(TEMPLATE, DIRECTIVES);
|
||||||
expect(block).toContain('Dir.ngTypeCtor({ dirInput: ((ctx).a) })');
|
expect(block).toContain('Dir.ngTypeCtor({ dirInput: ((ctx).a) })');
|
||||||
expect(block).toContain('(ctx).a;');
|
expect(block).toContain('(ctx).b;');
|
||||||
});
|
});
|
||||||
it('should not check types of bindings when disabled', () => {
|
it('should not check types of bindings when disabled', () => {
|
||||||
const DISABLED_CONFIG:
|
const DISABLED_CONFIG:
|
||||||
TypeCheckingConfig = {...BASE_CONFIG, checkTypeOfInputBindings: false};
|
TypeCheckingConfig = {...BASE_CONFIG, checkTypeOfInputBindings: false};
|
||||||
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
|
const block = tcb(TEMPLATE, DIRECTIVES, DISABLED_CONFIG);
|
||||||
expect(block).toContain('Dir.ngTypeCtor({ dirInput: (((ctx).a as any)) })');
|
expect(block).toContain('Dir.ngTypeCtor({ dirInput: (((ctx).a as any)) })');
|
||||||
expect(block).toContain('((ctx).a as any);');
|
expect(block).toContain('((ctx).b as any);');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue