diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b315e9b53f..c9faefed23 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -517,16 +517,15 @@ namespace ts { const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !hasModifier(node, ModifierFlags.Async) && !!getImmediatelyInvokedFunctionExpression(node); // A non-async IIFE is considered part of the containing control flow. Return statements behave // similarly to break statements that exit to a label just past the statement body. - if (isIIFE) { - currentReturnTarget = createBranchLabel(); - } - else { + if (!isIIFE) { currentFlow = { flags: FlowFlags.Start }; if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethod)) { (currentFlow).container = node; } - currentReturnTarget = undefined; } + // We create a return control flow graph for IIFEs and constructors. For constructors + // we use the return control flow graph in strict property intialization checks. + currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor ? createBranchLabel() : undefined; currentBreakTarget = undefined; currentContinueTarget = undefined; activeLabels = undefined; @@ -541,11 +540,14 @@ namespace ts { if (node.kind === SyntaxKind.SourceFile) { node.flags |= emitFlags; } - if (isIIFE) { + if (currentReturnTarget) { addAntecedent(currentReturnTarget, currentFlow); currentFlow = finishFlowLabel(currentReturnTarget); + if (node.kind === SyntaxKind.Constructor) { + (node).returnFlowNode = currentFlow; + } } - else { + if (!isIIFE) { currentFlow = saveCurrentFlow; } currentBreakTarget = saveBreakTarget; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cde61bb407..dc1bab72c3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -69,6 +69,7 @@ namespace ts { const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); + const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); @@ -12283,7 +12284,7 @@ namespace ts { // on empty arrays are possible without implicit any errors and new element types can be inferred without // type mismatch errors. const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? anyArrayType : finalizeEvolvingArrayType(evolvedType); - if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { + if (reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { return declaredType; } return resultType; @@ -15561,63 +15562,68 @@ namespace ts { } function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { - const type = checkNonNullExpression(left); - if (isTypeAny(type) || type === silentNeverType) { - return type; - } - - const apparentType = getApparentType(getWidenedType(type)); - if (apparentType === unknownType || (type.flags & TypeFlags.TypeParameter && isTypeAny(apparentType))) { - // handle cases when type is Type parameter with invalid or any constraint + let propType: Type; + const leftType = checkNonNullExpression(left); + const apparentType = getApparentType(getWidenedType(leftType)); + if (isTypeAny(apparentType) || apparentType === silentNeverType) { return apparentType; } + const assignmentKind = getAssignmentTargetKind(node); const prop = getPropertyOfType(apparentType, right.escapedText); if (!prop) { const indexInfo = getIndexInfoOfType(apparentType, IndexKind.String); - if (indexInfo && indexInfo.type) { - if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) { - error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); + if (!(indexInfo && indexInfo.type)) { + if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { + reportNonexistentProperty(right, leftType.flags & TypeFlags.TypeParameter && (leftType as TypeParameter).isThisType ? apparentType : leftType); } - return getFlowTypeOfPropertyAccess(node, /*prop*/ undefined, indexInfo.type, getAssignmentTargetKind(node)); - } - if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { - reportNonexistentProperty(right, type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType ? apparentType : type); - } - return unknownType; - } - - checkPropertyNotUsedBeforeDeclaration(prop, node, right); - - markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword); - - getNodeLinks(node).resolvedSymbol = prop; - - checkPropertyAccessibility(node, left, apparentType, prop); - - const propType = getDeclaredOrApparentType(prop, node); - const assignmentKind = getAssignmentTargetKind(node); - - if (assignmentKind) { - if (isReferenceToReadonlyEntity(node, prop) || isReferenceThroughNamespaceImport(node)) { - error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, idText(right)); return unknownType; } + if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) { + error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); + } + propType = indexInfo.type; } - return getFlowTypeOfPropertyAccess(node, prop, propType, assignmentKind); - } - - /** - * Only compute control flow type if this is a property access expression that isn't an - * assignment target, and the referenced property was declared as a variable, property, - * accessor, or optional method. - */ - function getFlowTypeOfPropertyAccess(node: PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, type: Type, assignmentKind: AssignmentKind) { + else { + checkPropertyNotUsedBeforeDeclaration(prop, node, right); + markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword); + getNodeLinks(node).resolvedSymbol = prop; + checkPropertyAccessibility(node, left, apparentType, prop); + if (assignmentKind) { + if (isReferenceToReadonlyEntity(node, prop) || isReferenceThroughNamespaceImport(node)) { + error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, idText(right)); + return unknownType; + } + } + propType = getDeclaredOrApparentType(prop, node); + } + // Only compute control flow type if this is a property access expression that isn't an + // assignment target, and the referenced property was declared as a variable, property, + // accessor, or optional method. if (node.kind !== SyntaxKind.PropertyAccessExpression || assignmentKind === AssignmentKind.Definite || - prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && type.flags & TypeFlags.Union)) { - return type; + prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { + return propType; + } + // If strict null checks and strict property initialization checks are enabled, if we have + // a this.xxx property access, if the property is an instance property without an initializer, + // and if we are in a constructor of the same class as the property declaration, assume that + // the property is uninitialized at the top of the control flow. + let assumeUninitialized = false; + if (strictNullChecks && strictPropertyInitialization && left.kind === SyntaxKind.ThisKeyword) { + const declaration = prop && prop.valueDeclaration; + if (declaration && isInstancePropertyWithoutInitializer(declaration)) { + const flowContainer = getControlFlowContainer(node); + if (flowContainer.kind === SyntaxKind.Constructor && flowContainer.parent === declaration.parent) { + assumeUninitialized = true; + } + } + } + const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); + if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { + error(right, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop)); + // Return the declared type to reduce follow-on errors + return propType; } - const flowType = getFlowTypeOfReference(node, type); return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } @@ -22483,6 +22489,7 @@ namespace ts { if (produceDiagnostics) { checkIndexConstraints(type); checkTypeForDuplicateIndexSignatures(node); + checkPropertyInitialization(node); } } @@ -22639,6 +22646,39 @@ namespace ts { return ok; } + function checkPropertyInitialization(node: ClassLikeDeclaration) { + if (!strictNullChecks || !strictPropertyInitialization || node.flags & NodeFlags.Ambient) { + return; + } + const constructor = findConstructorDeclaration(node); + for (const member of node.members) { + if (isInstancePropertyWithoutInitializer(member)) { + const propName = (member).name; + if (isIdentifier(propName)) { + const type = getTypeOfSymbol(getSymbolOfNode(member)); + if (!(type.flags & TypeFlags.Any || getFalsyFlags(type) & TypeFlags.Undefined)) { + if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) { + error(member.name, Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor, declarationNameToString(propName)); + } + } + } + } + } + } + + function isInstancePropertyWithoutInitializer(node: Node) { + return node.kind === SyntaxKind.PropertyDeclaration && + !hasModifier(node, ModifierFlags.Static | ModifierFlags.Abstract) && + !(node).initializer; + } + + function isPropertyInitializedInConstructor(propName: Identifier, propType: Type, constructor: ConstructorDeclaration) { + const reference = createPropertyAccess(createThis(), propName); + reference.flowNode = constructor.returnFlowNode; + const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); + return !(getFalsyFlags(flowType) & TypeFlags.Undefined); + } + function checkInterfaceDeclaration(node: InterfaceDeclaration) { // Grammar checking if (!checkGrammarDecoratorsAndModifiers(node)) checkGrammarInterfaceDeclaration(node); diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 8cec21113c..989bb6a714 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -277,6 +277,13 @@ namespace ts { category: Diagnostics.Strict_Type_Checking_Options, description: Diagnostics.Enable_strict_checking_of_function_types }, + { + name: "strictPropertyInitialization", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Enable_strict_checking_of_property_initialization_in_classes + }, { name: "noImplicitThis", type: "boolean", diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 6084948ea7..867a22b92a 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1967,7 +1967,7 @@ namespace ts { : moduleKind === ModuleKind.System; } - export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "alwaysStrict"; + export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "strictPropertyInitialization" | "alwaysStrict"; export function getStrictOptionValue(compilerOptions: CompilerOptions, flag: StrictOptionName): boolean { return compilerOptions[flag] === undefined ? compilerOptions.strict : compilerOptions[flag]; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 95929632ef..7c0336cf60 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1952,6 +1952,14 @@ "category": "Error", "code": 2563 }, + "Property '{0}' has no initializer and is not definitely assigned in the constructor.": { + "category": "Error", + "code": 2564 + }, + "Property '{0}' is used before being assigned.": { + "category": "Error", + "code": 2565 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 @@ -3391,6 +3399,10 @@ "category": "Message", "code": 6186 }, + "Enable strict checking of property initialization in classes.": { + "category": "Message", + "code": 6187 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 26148a0b2f..1ac7894b7a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -947,6 +947,7 @@ namespace ts { kind: SyntaxKind.Constructor; parent?: ClassDeclaration | ClassExpression; body?: FunctionBody; + /* @internal */ returnFlowNode?: FlowNode; } /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ @@ -3854,6 +3855,7 @@ namespace ts { strict?: boolean; strictFunctionTypes?: boolean; // Always combine with strict property strictNullChecks?: boolean; // Always combine with strict property + strictPropertyInitialization?: boolean; // Always combine with strict property /* @internal */ stripInternal?: boolean; suppressExcessPropertyErrors?: boolean; suppressImplicitAnyIndexErrors?: boolean; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index d3a97c7569..99480c0ee2 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2287,6 +2287,7 @@ declare namespace ts { strict?: boolean; strictFunctionTypes?: boolean; strictNullChecks?: boolean; + strictPropertyInitialization?: boolean; suppressExcessPropertyErrors?: boolean; suppressImplicitAnyIndexErrors?: boolean; target?: ScriptTarget; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 43b668fe2d..c0cf5d5c54 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2287,6 +2287,7 @@ declare namespace ts { strict?: boolean; strictFunctionTypes?: boolean; strictNullChecks?: boolean; + strictPropertyInitialization?: boolean; suppressExcessPropertyErrors?: boolean; suppressImplicitAnyIndexErrors?: boolean; target?: ScriptTarget; diff --git a/tests/baselines/reference/indexedAccessTypeConstraints.js b/tests/baselines/reference/indexedAccessTypeConstraints.js index 236f2f33ff..7a218586d8 100644 --- a/tests/baselines/reference/indexedAccessTypeConstraints.js +++ b/tests/baselines/reference/indexedAccessTypeConstraints.js @@ -10,7 +10,7 @@ type Data = { }; class Parent { - private data: Data; + constructor(private data: Data) {} getData(): Data { return this.data; } @@ -50,7 +50,8 @@ var __extends = (this && this.__extends) || (function () { })(); exports.__esModule = true; var Parent = /** @class */ (function () { - function Parent() { + function Parent(data) { + this.data = data; } Parent.prototype.getData = function () { return this.data; diff --git a/tests/baselines/reference/indexedAccessTypeConstraints.symbols b/tests/baselines/reference/indexedAccessTypeConstraints.symbols index 6d81534aa2..4937db57f3 100644 --- a/tests/baselines/reference/indexedAccessTypeConstraints.symbols +++ b/tests/baselines/reference/indexedAccessTypeConstraints.symbols @@ -29,20 +29,20 @@ class Parent { >Parent : Symbol(Parent, Decl(indexedAccessTypeConstraints.ts, 8, 2)) >M : Symbol(M, Decl(indexedAccessTypeConstraints.ts, 10, 13)) - private data: Data; ->data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 10, 17)) + constructor(private data: Data) {} +>data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 11, 16)) >Data : Symbol(Data, Decl(indexedAccessTypeConstraints.ts, 4, 1)) >M : Symbol(M, Decl(indexedAccessTypeConstraints.ts, 10, 13)) getData(): Data { ->getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 26)) +>getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 41)) >Data : Symbol(Data, Decl(indexedAccessTypeConstraints.ts, 4, 1)) >M : Symbol(M, Decl(indexedAccessTypeConstraints.ts, 10, 13)) return this.data; ->this.data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 10, 17)) +>this.data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 11, 16)) >this : Symbol(Parent, Decl(indexedAccessTypeConstraints.ts, 8, 2)) ->data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 10, 17)) +>data : Symbol(Parent.data, Decl(indexedAccessTypeConstraints.ts, 11, 16)) } } @@ -59,9 +59,9 @@ export class Foo extends Parent> { return this.getData().get('content'); >this.getData().get : Symbol(get, Decl(indexedAccessTypeConstraints.ts, 6, 16)) ->this.getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 26)) +>this.getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 41)) >this : Symbol(Foo, Decl(indexedAccessTypeConstraints.ts, 15, 1)) ->getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 26)) +>getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 41)) >get : Symbol(get, Decl(indexedAccessTypeConstraints.ts, 6, 16)) } } @@ -81,9 +81,9 @@ export class Bar> extends Parent { return this.getData().get('content'); >this.getData().get : Symbol(get, Decl(indexedAccessTypeConstraints.ts, 6, 16)) ->this.getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 26)) +>this.getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 41)) >this : Symbol(Bar, Decl(indexedAccessTypeConstraints.ts, 21, 1)) ->getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 26)) +>getData : Symbol(Parent.getData, Decl(indexedAccessTypeConstraints.ts, 11, 41)) >get : Symbol(get, Decl(indexedAccessTypeConstraints.ts, 6, 16)) } } diff --git a/tests/baselines/reference/indexedAccessTypeConstraints.types b/tests/baselines/reference/indexedAccessTypeConstraints.types index c7373fe5d4..dad5fd7d61 100644 --- a/tests/baselines/reference/indexedAccessTypeConstraints.types +++ b/tests/baselines/reference/indexedAccessTypeConstraints.types @@ -29,7 +29,7 @@ class Parent { >Parent : Parent >M : M - private data: Data; + constructor(private data: Data) {} >data : { get: (prop: K) => M[K]; } >Data : { get: (prop: K) => T[K]; } >M : M diff --git a/tests/baselines/reference/strictPropertyInitialization.errors.txt b/tests/baselines/reference/strictPropertyInitialization.errors.txt new file mode 100644 index 0000000000..4a3fe0eb3e --- /dev/null +++ b/tests/baselines/reference/strictPropertyInitialization.errors.txt @@ -0,0 +1,120 @@ +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(4,5): error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(6,5): error TS2564: Property 'c' has no initializer and is not definitely assigned in the constructor. +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(48,5): error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(71,5): error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(93,22): error TS2565: Property 'a' is used before being assigned. +tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts(94,23): error TS2565: Property 'b' is used before being assigned. + + +==== tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts (6 errors) ==== + // Properties with non-undefined types require initialization + + class C1 { + a: number; // Error + ~ +!!! error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. + b: number | undefined; + c: number | null; // Error + ~ +!!! error TS2564: Property 'c' has no initializer and is not definitely assigned in the constructor. + d?: number; + } + + // No strict initialization checks in ambient contexts + + declare class C2 { + a: number; + b: number | undefined; + c: number | null; + d?: number; + } + + // No strict initialization checks for static members + + class C3 { + static a: number; + static b: number | undefined; + static c: number | null; + static d?: number; + } + + // Initializer satisfies strict initialization check + + class C4 { + a = 0; + b: number = 0; + c: string = "abc"; + } + + // Assignment in constructor satisfies strict initialization check + + class C5 { + a: number; + constructor() { + this.a = 0; + } + } + + // All code paths must contain assignment + + class C6 { + a: number; // Error + ~ +!!! error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. + constructor(cond: boolean) { + if (cond) { + return; + } + this.a = 0; + } + } + + class C7 { + a: number; + constructor(cond: boolean) { + if (cond) { + this.a = 1; + return; + } + this.a = 0; + } + } + + // Properties with string literal names aren't checked + + class C8 { + a: number; // Error + ~ +!!! error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor. + "b": number; + 0: number; + } + + // No strict initialization checks for abstract members + + abstract class C9 { + abstract a: number; + abstract b: number | undefined; + abstract c: number | null; + abstract d?: number; + } + + // Properties with non-undefined types must be assigned before they can be accessed + // within their constructor + + class C10 { + a: number; + b: number; + c?: number; + constructor() { + let x = this.a; // Error + ~ +!!! error TS2565: Property 'a' is used before being assigned. + this.a = this.b; // Error + ~ +!!! error TS2565: Property 'b' is used before being assigned. + this.b = x; + let y = this.c; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/strictPropertyInitialization.js b/tests/baselines/reference/strictPropertyInitialization.js new file mode 100644 index 0000000000..6de5084e40 --- /dev/null +++ b/tests/baselines/reference/strictPropertyInitialization.js @@ -0,0 +1,229 @@ +//// [strictPropertyInitialization.ts] +// Properties with non-undefined types require initialization + +class C1 { + a: number; // Error + b: number | undefined; + c: number | null; // Error + d?: number; +} + +// No strict initialization checks in ambient contexts + +declare class C2 { + a: number; + b: number | undefined; + c: number | null; + d?: number; +} + +// No strict initialization checks for static members + +class C3 { + static a: number; + static b: number | undefined; + static c: number | null; + static d?: number; +} + +// Initializer satisfies strict initialization check + +class C4 { + a = 0; + b: number = 0; + c: string = "abc"; +} + +// Assignment in constructor satisfies strict initialization check + +class C5 { + a: number; + constructor() { + this.a = 0; + } +} + +// All code paths must contain assignment + +class C6 { + a: number; // Error + constructor(cond: boolean) { + if (cond) { + return; + } + this.a = 0; + } +} + +class C7 { + a: number; + constructor(cond: boolean) { + if (cond) { + this.a = 1; + return; + } + this.a = 0; + } +} + +// Properties with string literal names aren't checked + +class C8 { + a: number; // Error + "b": number; + 0: number; +} + +// No strict initialization checks for abstract members + +abstract class C9 { + abstract a: number; + abstract b: number | undefined; + abstract c: number | null; + abstract d?: number; +} + +// Properties with non-undefined types must be assigned before they can be accessed +// within their constructor + +class C10 { + a: number; + b: number; + c?: number; + constructor() { + let x = this.a; // Error + this.a = this.b; // Error + this.b = x; + let y = this.c; + } +} + + +//// [strictPropertyInitialization.js] +"use strict"; +// Properties with non-undefined types require initialization +var C1 = /** @class */ (function () { + function C1() { + } + return C1; +}()); +// No strict initialization checks for static members +var C3 = /** @class */ (function () { + function C3() { + } + return C3; +}()); +// Initializer satisfies strict initialization check +var C4 = /** @class */ (function () { + function C4() { + this.a = 0; + this.b = 0; + this.c = "abc"; + } + return C4; +}()); +// Assignment in constructor satisfies strict initialization check +var C5 = /** @class */ (function () { + function C5() { + this.a = 0; + } + return C5; +}()); +// All code paths must contain assignment +var C6 = /** @class */ (function () { + function C6(cond) { + if (cond) { + return; + } + this.a = 0; + } + return C6; +}()); +var C7 = /** @class */ (function () { + function C7(cond) { + if (cond) { + this.a = 1; + return; + } + this.a = 0; + } + return C7; +}()); +// Properties with string literal names aren't checked +var C8 = /** @class */ (function () { + function C8() { + } + return C8; +}()); +// No strict initialization checks for abstract members +var C9 = /** @class */ (function () { + function C9() { + } + return C9; +}()); +// Properties with non-undefined types must be assigned before they can be accessed +// within their constructor +var C10 = /** @class */ (function () { + function C10() { + var x = this.a; // Error + this.a = this.b; // Error + this.b = x; + var y = this.c; + } + return C10; +}()); + + +//// [strictPropertyInitialization.d.ts] +declare class C1 { + a: number; + b: number | undefined; + c: number | null; + d?: number; +} +declare class C2 { + a: number; + b: number | undefined; + c: number | null; + d?: number; +} +declare class C3 { + static a: number; + static b: number | undefined; + static c: number | null; + static d?: number; +} +declare class C4 { + a: number; + b: number; + c: string; +} +declare class C5 { + a: number; + constructor(); +} +declare class C6 { + a: number; + constructor(cond: boolean); +} +declare class C7 { + a: number; + constructor(cond: boolean); +} +declare class C8 { + a: number; + "b": number; + 0: number; +} +declare abstract class C9 { + abstract a: number; + abstract b: number | undefined; + abstract c: number | null; + abstract d?: number; +} +declare class C10 { + a: number; + b: number; + c?: number; + constructor(); +} diff --git a/tests/baselines/reference/strictPropertyInitialization.symbols b/tests/baselines/reference/strictPropertyInitialization.symbols new file mode 100644 index 0000000000..3247d975bb --- /dev/null +++ b/tests/baselines/reference/strictPropertyInitialization.symbols @@ -0,0 +1,209 @@ +=== tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts === +// Properties with non-undefined types require initialization + +class C1 { +>C1 : Symbol(C1, Decl(strictPropertyInitialization.ts, 0, 0)) + + a: number; // Error +>a : Symbol(C1.a, Decl(strictPropertyInitialization.ts, 2, 10)) + + b: number | undefined; +>b : Symbol(C1.b, Decl(strictPropertyInitialization.ts, 3, 14)) + + c: number | null; // Error +>c : Symbol(C1.c, Decl(strictPropertyInitialization.ts, 4, 26)) + + d?: number; +>d : Symbol(C1.d, Decl(strictPropertyInitialization.ts, 5, 21)) +} + +// No strict initialization checks in ambient contexts + +declare class C2 { +>C2 : Symbol(C2, Decl(strictPropertyInitialization.ts, 7, 1)) + + a: number; +>a : Symbol(C2.a, Decl(strictPropertyInitialization.ts, 11, 18)) + + b: number | undefined; +>b : Symbol(C2.b, Decl(strictPropertyInitialization.ts, 12, 14)) + + c: number | null; +>c : Symbol(C2.c, Decl(strictPropertyInitialization.ts, 13, 26)) + + d?: number; +>d : Symbol(C2.d, Decl(strictPropertyInitialization.ts, 14, 21)) +} + +// No strict initialization checks for static members + +class C3 { +>C3 : Symbol(C3, Decl(strictPropertyInitialization.ts, 16, 1)) + + static a: number; +>a : Symbol(C3.a, Decl(strictPropertyInitialization.ts, 20, 10)) + + static b: number | undefined; +>b : Symbol(C3.b, Decl(strictPropertyInitialization.ts, 21, 21)) + + static c: number | null; +>c : Symbol(C3.c, Decl(strictPropertyInitialization.ts, 22, 33)) + + static d?: number; +>d : Symbol(C3.d, Decl(strictPropertyInitialization.ts, 23, 28)) +} + +// Initializer satisfies strict initialization check + +class C4 { +>C4 : Symbol(C4, Decl(strictPropertyInitialization.ts, 25, 1)) + + a = 0; +>a : Symbol(C4.a, Decl(strictPropertyInitialization.ts, 29, 10)) + + b: number = 0; +>b : Symbol(C4.b, Decl(strictPropertyInitialization.ts, 30, 10)) + + c: string = "abc"; +>c : Symbol(C4.c, Decl(strictPropertyInitialization.ts, 31, 18)) +} + +// Assignment in constructor satisfies strict initialization check + +class C5 { +>C5 : Symbol(C5, Decl(strictPropertyInitialization.ts, 33, 1)) + + a: number; +>a : Symbol(C5.a, Decl(strictPropertyInitialization.ts, 37, 10)) + + constructor() { + this.a = 0; +>this.a : Symbol(C5.a, Decl(strictPropertyInitialization.ts, 37, 10)) +>this : Symbol(C5, Decl(strictPropertyInitialization.ts, 33, 1)) +>a : Symbol(C5.a, Decl(strictPropertyInitialization.ts, 37, 10)) + } +} + +// All code paths must contain assignment + +class C6 { +>C6 : Symbol(C6, Decl(strictPropertyInitialization.ts, 42, 1)) + + a: number; // Error +>a : Symbol(C6.a, Decl(strictPropertyInitialization.ts, 46, 10)) + + constructor(cond: boolean) { +>cond : Symbol(cond, Decl(strictPropertyInitialization.ts, 48, 16)) + + if (cond) { +>cond : Symbol(cond, Decl(strictPropertyInitialization.ts, 48, 16)) + + return; + } + this.a = 0; +>this.a : Symbol(C6.a, Decl(strictPropertyInitialization.ts, 46, 10)) +>this : Symbol(C6, Decl(strictPropertyInitialization.ts, 42, 1)) +>a : Symbol(C6.a, Decl(strictPropertyInitialization.ts, 46, 10)) + } +} + +class C7 { +>C7 : Symbol(C7, Decl(strictPropertyInitialization.ts, 54, 1)) + + a: number; +>a : Symbol(C7.a, Decl(strictPropertyInitialization.ts, 56, 10)) + + constructor(cond: boolean) { +>cond : Symbol(cond, Decl(strictPropertyInitialization.ts, 58, 16)) + + if (cond) { +>cond : Symbol(cond, Decl(strictPropertyInitialization.ts, 58, 16)) + + this.a = 1; +>this.a : Symbol(C7.a, Decl(strictPropertyInitialization.ts, 56, 10)) +>this : Symbol(C7, Decl(strictPropertyInitialization.ts, 54, 1)) +>a : Symbol(C7.a, Decl(strictPropertyInitialization.ts, 56, 10)) + + return; + } + this.a = 0; +>this.a : Symbol(C7.a, Decl(strictPropertyInitialization.ts, 56, 10)) +>this : Symbol(C7, Decl(strictPropertyInitialization.ts, 54, 1)) +>a : Symbol(C7.a, Decl(strictPropertyInitialization.ts, 56, 10)) + } +} + +// Properties with string literal names aren't checked + +class C8 { +>C8 : Symbol(C8, Decl(strictPropertyInitialization.ts, 65, 1)) + + a: number; // Error +>a : Symbol(C8.a, Decl(strictPropertyInitialization.ts, 69, 10)) + + "b": number; + 0: number; +} + +// No strict initialization checks for abstract members + +abstract class C9 { +>C9 : Symbol(C9, Decl(strictPropertyInitialization.ts, 73, 1)) + + abstract a: number; +>a : Symbol(C9.a, Decl(strictPropertyInitialization.ts, 77, 19)) + + abstract b: number | undefined; +>b : Symbol(C9.b, Decl(strictPropertyInitialization.ts, 78, 23)) + + abstract c: number | null; +>c : Symbol(C9.c, Decl(strictPropertyInitialization.ts, 79, 35)) + + abstract d?: number; +>d : Symbol(C9.d, Decl(strictPropertyInitialization.ts, 80, 30)) +} + +// Properties with non-undefined types must be assigned before they can be accessed +// within their constructor + +class C10 { +>C10 : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) + + a: number; +>a : Symbol(C10.a, Decl(strictPropertyInitialization.ts, 87, 11)) + + b: number; +>b : Symbol(C10.b, Decl(strictPropertyInitialization.ts, 88, 14)) + + c?: number; +>c : Symbol(C10.c, Decl(strictPropertyInitialization.ts, 89, 14)) + + constructor() { + let x = this.a; // Error +>x : Symbol(x, Decl(strictPropertyInitialization.ts, 92, 11)) +>this.a : Symbol(C10.a, Decl(strictPropertyInitialization.ts, 87, 11)) +>this : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) +>a : Symbol(C10.a, Decl(strictPropertyInitialization.ts, 87, 11)) + + this.a = this.b; // Error +>this.a : Symbol(C10.a, Decl(strictPropertyInitialization.ts, 87, 11)) +>this : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) +>a : Symbol(C10.a, Decl(strictPropertyInitialization.ts, 87, 11)) +>this.b : Symbol(C10.b, Decl(strictPropertyInitialization.ts, 88, 14)) +>this : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) +>b : Symbol(C10.b, Decl(strictPropertyInitialization.ts, 88, 14)) + + this.b = x; +>this.b : Symbol(C10.b, Decl(strictPropertyInitialization.ts, 88, 14)) +>this : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) +>b : Symbol(C10.b, Decl(strictPropertyInitialization.ts, 88, 14)) +>x : Symbol(x, Decl(strictPropertyInitialization.ts, 92, 11)) + + let y = this.c; +>y : Symbol(y, Decl(strictPropertyInitialization.ts, 95, 11)) +>this.c : Symbol(C10.c, Decl(strictPropertyInitialization.ts, 89, 14)) +>this : Symbol(C10, Decl(strictPropertyInitialization.ts, 82, 1)) +>c : Symbol(C10.c, Decl(strictPropertyInitialization.ts, 89, 14)) + } +} + diff --git a/tests/baselines/reference/strictPropertyInitialization.types b/tests/baselines/reference/strictPropertyInitialization.types new file mode 100644 index 0000000000..24cbdb14ef --- /dev/null +++ b/tests/baselines/reference/strictPropertyInitialization.types @@ -0,0 +1,226 @@ +=== tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts === +// Properties with non-undefined types require initialization + +class C1 { +>C1 : C1 + + a: number; // Error +>a : number + + b: number | undefined; +>b : number | undefined + + c: number | null; // Error +>c : number | null +>null : null + + d?: number; +>d : number | undefined +} + +// No strict initialization checks in ambient contexts + +declare class C2 { +>C2 : C2 + + a: number; +>a : number + + b: number | undefined; +>b : number | undefined + + c: number | null; +>c : number | null +>null : null + + d?: number; +>d : number | undefined +} + +// No strict initialization checks for static members + +class C3 { +>C3 : C3 + + static a: number; +>a : number + + static b: number | undefined; +>b : number | undefined + + static c: number | null; +>c : number | null +>null : null + + static d?: number; +>d : number | undefined +} + +// Initializer satisfies strict initialization check + +class C4 { +>C4 : C4 + + a = 0; +>a : number +>0 : 0 + + b: number = 0; +>b : number +>0 : 0 + + c: string = "abc"; +>c : string +>"abc" : "abc" +} + +// Assignment in constructor satisfies strict initialization check + +class C5 { +>C5 : C5 + + a: number; +>a : number + + constructor() { + this.a = 0; +>this.a = 0 : 0 +>this.a : number +>this : this +>a : number +>0 : 0 + } +} + +// All code paths must contain assignment + +class C6 { +>C6 : C6 + + a: number; // Error +>a : number + + constructor(cond: boolean) { +>cond : boolean + + if (cond) { +>cond : boolean + + return; + } + this.a = 0; +>this.a = 0 : 0 +>this.a : number +>this : this +>a : number +>0 : 0 + } +} + +class C7 { +>C7 : C7 + + a: number; +>a : number + + constructor(cond: boolean) { +>cond : boolean + + if (cond) { +>cond : boolean + + this.a = 1; +>this.a = 1 : 1 +>this.a : number +>this : this +>a : number +>1 : 1 + + return; + } + this.a = 0; +>this.a = 0 : 0 +>this.a : number +>this : this +>a : number +>0 : 0 + } +} + +// Properties with string literal names aren't checked + +class C8 { +>C8 : C8 + + a: number; // Error +>a : number + + "b": number; + 0: number; +} + +// No strict initialization checks for abstract members + +abstract class C9 { +>C9 : C9 + + abstract a: number; +>a : number + + abstract b: number | undefined; +>b : number | undefined + + abstract c: number | null; +>c : number | null +>null : null + + abstract d?: number; +>d : number | undefined +} + +// Properties with non-undefined types must be assigned before they can be accessed +// within their constructor + +class C10 { +>C10 : C10 + + a: number; +>a : number + + b: number; +>b : number + + c?: number; +>c : number | undefined + + constructor() { + let x = this.a; // Error +>x : number +>this.a : number +>this : this +>a : number + + this.a = this.b; // Error +>this.a = this.b : number +>this.a : number +>this : this +>a : number +>this.b : number +>this : this +>b : number + + this.b = x; +>this.b = x : number +>this.b : number +>this : this +>b : number +>x : number + + let y = this.c; +>y : number | undefined +>this.c : number | undefined +>this : this +>c : number | undefined + } +} + diff --git a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json index 08887fc6c9..25a2c11d33 100644 --- a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json index ca2b4aa408..d6534c834a 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json index 9437685c29..414f3b5790 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json index d2e7e85ad5..940970d297 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json index 3f4100033d..074f24cad5 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json index 08887fc6c9..25a2c11d33 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json index 22cb044420..a5eae1a465 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json index fc3321600f..2468ac7921 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json @@ -23,6 +23,7 @@ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/typeVariableTypeGuards.js b/tests/baselines/reference/typeVariableTypeGuards.js index 378146359c..379d9a331d 100644 --- a/tests/baselines/reference/typeVariableTypeGuards.js +++ b/tests/baselines/reference/typeVariableTypeGuards.js @@ -6,7 +6,7 @@ interface Foo { } class A

> { - props: Readonly

+ constructor(public props: Readonly

) {} doSomething() { this.props.foo && this.props.foo() } @@ -19,7 +19,7 @@ interface Banana { } class Monkey { - a: T; + constructor(public a: T) {} render() { if (this.a) { this.a.color; @@ -96,7 +96,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); var A = /** @class */ (function () { - function A() { + function A(props) { + this.props = props; } A.prototype.doSomething = function () { this.props.foo && this.props.foo(); @@ -104,7 +105,8 @@ var A = /** @class */ (function () { return A; }()); var Monkey = /** @class */ (function () { - function Monkey() { + function Monkey(a) { + this.a = a; } Monkey.prototype.render = function () { if (this.a) { diff --git a/tests/baselines/reference/typeVariableTypeGuards.symbols b/tests/baselines/reference/typeVariableTypeGuards.symbols index d201d0538a..5a91e712e1 100644 --- a/tests/baselines/reference/typeVariableTypeGuards.symbols +++ b/tests/baselines/reference/typeVariableTypeGuards.symbols @@ -14,24 +14,24 @@ class A

> { >Partial : Symbol(Partial, Decl(lib.d.ts, --, --)) >Foo : Symbol(Foo, Decl(typeVariableTypeGuards.ts, 0, 0)) - props: Readonly

->props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33)) + constructor(public props: Readonly

) {} +>props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 7, 16)) >Readonly : Symbol(Readonly, Decl(lib.d.ts, --, --)) >P : Symbol(P, Decl(typeVariableTypeGuards.ts, 6, 8)) doSomething() { ->doSomething : Symbol(A.doSomething, Decl(typeVariableTypeGuards.ts, 7, 22)) +>doSomething : Symbol(A.doSomething, Decl(typeVariableTypeGuards.ts, 7, 45)) this.props.foo && this.props.foo() >this.props.foo : Symbol(foo, Decl(typeVariableTypeGuards.ts, 2, 15)) ->this.props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33)) +>this.props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 7, 16)) >this : Symbol(A, Decl(typeVariableTypeGuards.ts, 4, 1)) ->props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33)) +>props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 7, 16)) >foo : Symbol(foo, Decl(typeVariableTypeGuards.ts, 2, 15)) >this.props.foo : Symbol(foo, Decl(typeVariableTypeGuards.ts, 2, 15)) ->this.props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33)) +>this.props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 7, 16)) >this : Symbol(A, Decl(typeVariableTypeGuards.ts, 4, 1)) ->props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33)) +>props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 7, 16)) >foo : Symbol(foo, Decl(typeVariableTypeGuards.ts, 2, 15)) } } @@ -50,23 +50,23 @@ class Monkey { >T : Symbol(T, Decl(typeVariableTypeGuards.ts, 19, 13)) >Banana : Symbol(Banana, Decl(typeVariableTypeGuards.ts, 11, 1)) - a: T; ->a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) + constructor(public a: T) {} +>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >T : Symbol(T, Decl(typeVariableTypeGuards.ts, 19, 13)) render() { ->render : Symbol(Monkey.render, Decl(typeVariableTypeGuards.ts, 20, 9)) +>render : Symbol(Monkey.render, Decl(typeVariableTypeGuards.ts, 20, 31)) if (this.a) { ->this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >this : Symbol(Monkey, Decl(typeVariableTypeGuards.ts, 17, 1)) ->a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) this.a.color; >this.a.color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18)) ->this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >this : Symbol(Monkey, Decl(typeVariableTypeGuards.ts, 17, 1)) ->a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18)) } } @@ -86,15 +86,15 @@ class BigMonkey extends Monkey { >render : Symbol(BigMonkey.render, Decl(typeVariableTypeGuards.ts, 31, 43)) if (this.a) { ->this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >this : Symbol(BigMonkey, Decl(typeVariableTypeGuards.ts, 29, 1)) ->a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) this.a.color; >this.a.color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18)) ->this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >this : Symbol(BigMonkey, Decl(typeVariableTypeGuards.ts, 29, 1)) ->a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44)) +>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 20, 16)) >color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18)) } } diff --git a/tests/baselines/reference/typeVariableTypeGuards.types b/tests/baselines/reference/typeVariableTypeGuards.types index bfcf851648..c3fc637b5b 100644 --- a/tests/baselines/reference/typeVariableTypeGuards.types +++ b/tests/baselines/reference/typeVariableTypeGuards.types @@ -14,7 +14,7 @@ class A

> { >Partial : Partial >Foo : Foo - props: Readonly

+ constructor(public props: Readonly

) {} >props : Readonly

>Readonly : Readonly >P : P @@ -52,7 +52,7 @@ class Monkey { >T : T >Banana : Banana - a: T; + constructor(public a: T) {} >a : T >T : T diff --git a/tests/cases/compiler/indexedAccessTypeConstraints.ts b/tests/cases/compiler/indexedAccessTypeConstraints.ts index c1c1bca198..aee8b6bcb6 100644 --- a/tests/cases/compiler/indexedAccessTypeConstraints.ts +++ b/tests/cases/compiler/indexedAccessTypeConstraints.ts @@ -11,7 +11,7 @@ type Data = { }; class Parent { - private data: Data; + constructor(private data: Data) {} getData(): Data { return this.data; } diff --git a/tests/cases/compiler/typeVariableTypeGuards.ts b/tests/cases/compiler/typeVariableTypeGuards.ts index 0a4221c93a..91e5e7a552 100644 --- a/tests/cases/compiler/typeVariableTypeGuards.ts +++ b/tests/cases/compiler/typeVariableTypeGuards.ts @@ -7,7 +7,7 @@ interface Foo { } class A

> { - props: Readonly

+ constructor(public props: Readonly

) {} doSomething() { this.props.foo && this.props.foo() } @@ -20,7 +20,7 @@ interface Banana { } class Monkey { - a: T; + constructor(public a: T) {} render() { if (this.a) { this.a.color; diff --git a/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts b/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts new file mode 100644 index 0000000000..0117720042 --- /dev/null +++ b/tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts @@ -0,0 +1,101 @@ +// @strict: true +// @declaration: true + +// Properties with non-undefined types require initialization + +class C1 { + a: number; // Error + b: number | undefined; + c: number | null; // Error + d?: number; +} + +// No strict initialization checks in ambient contexts + +declare class C2 { + a: number; + b: number | undefined; + c: number | null; + d?: number; +} + +// No strict initialization checks for static members + +class C3 { + static a: number; + static b: number | undefined; + static c: number | null; + static d?: number; +} + +// Initializer satisfies strict initialization check + +class C4 { + a = 0; + b: number = 0; + c: string = "abc"; +} + +// Assignment in constructor satisfies strict initialization check + +class C5 { + a: number; + constructor() { + this.a = 0; + } +} + +// All code paths must contain assignment + +class C6 { + a: number; // Error + constructor(cond: boolean) { + if (cond) { + return; + } + this.a = 0; + } +} + +class C7 { + a: number; + constructor(cond: boolean) { + if (cond) { + this.a = 1; + return; + } + this.a = 0; + } +} + +// Properties with string literal names aren't checked + +class C8 { + a: number; // Error + "b": number; + 0: number; +} + +// No strict initialization checks for abstract members + +abstract class C9 { + abstract a: number; + abstract b: number | undefined; + abstract c: number | null; + abstract d?: number; +} + +// Properties with non-undefined types must be assigned before they can be accessed +// within their constructor + +class C10 { + a: number; + b: number; + c?: number; + constructor() { + let x = this.a; // Error + this.a = this.b; // Error + this.b = x; + let y = this.c; + } +}