From 228ce06461fad0fb260f4dd3ca0c1f8a5abecfba Mon Sep 17 00:00:00 2001 From: Charles Pierce Date: Wed, 5 Jul 2017 10:03:56 -0700 Subject: [PATCH 01/50] #15214 Remove nonpublic members from destructuring completion lists --- src/services/completions.ts | 2 +- .../fourslash/completionListInObjectBindingPattern14.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/completionListInObjectBindingPattern14.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 410ba636d7b..f11130efa86 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1002,7 +1002,7 @@ namespace ts.Completions { const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); if (!typeForObject) return false; // In a binding pattern, get only known properties. Everywhere else we will get all possible properties. - typeMembers = typeChecker.getPropertiesOfType(typeForObject); + typeMembers = typeChecker.getPropertiesOfType(typeForObject).filter((symbol) => !(getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.NonPublicAccessibilityModifier)); existingMembers = (objectLikeContainer).elements; } } diff --git a/tests/cases/fourslash/completionListInObjectBindingPattern14.ts b/tests/cases/fourslash/completionListInObjectBindingPattern14.ts new file mode 100644 index 00000000000..425813a5543 --- /dev/null +++ b/tests/cases/fourslash/completionListInObjectBindingPattern14.ts @@ -0,0 +1,9 @@ +/// + +////const { b/**/ } = new class { +//// private ab; +//// protected bc; +////} + +goTo.marker(); +verify.completionListIsEmpty(); From d1459f7e9cab507d1e5c860faf79ae05d95dd38f Mon Sep 17 00:00:00 2001 From: vvakame Date: Tue, 25 Jul 2017 18:24:04 +0900 Subject: [PATCH 02/50] Add SpaceBetweenOpenParens rule --- src/services/formatting/rules.ts | 4 +++- .../fourslash/formattingSpaceBetweenParent.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/formattingSpaceBetweenParent.ts diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index 15bbf5041d3..2daf8d9d284 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -195,6 +195,7 @@ namespace ts.formatting { // Insert space after opening and before closing nonempty parenthesis public SpaceAfterOpenParen: Rule; public SpaceBeforeCloseParen: Rule; + public SpaceBetweenOpenParens: Rule; public NoSpaceBetweenParens: Rule; public NoSpaceAfterOpenParen: Rule; public NoSpaceBeforeCloseParen: Rule; @@ -457,6 +458,7 @@ namespace ts.formatting { // Insert space after opening and before closing nonempty parenthesis this.SpaceAfterOpenParen = new Rule(RuleDescriptor.create3(SyntaxKind.OpenParenToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); this.SpaceBeforeCloseParen = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); + this.SpaceBetweenOpenParens = new Rule(RuleDescriptor.create1(SyntaxKind.OpenParenToken, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); this.NoSpaceBetweenParens = new Rule(RuleDescriptor.create1(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); this.NoSpaceAfterOpenParen = new Rule(RuleDescriptor.create3(SyntaxKind.OpenParenToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); this.NoSpaceBeforeCloseParen = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); @@ -544,7 +546,7 @@ namespace ts.formatting { this.SpaceAfterComma, this.NoSpaceAfterComma, this.SpaceAfterAnonymousFunctionKeyword, this.NoSpaceAfterAnonymousFunctionKeyword, this.SpaceAfterKeywordInControl, this.NoSpaceAfterKeywordInControl, - this.SpaceAfterOpenParen, this.SpaceBeforeCloseParen, this.NoSpaceBetweenParens, this.NoSpaceAfterOpenParen, this.NoSpaceBeforeCloseParen, + this.SpaceAfterOpenParen, this.SpaceBeforeCloseParen, this.SpaceBetweenOpenParens, this.NoSpaceBetweenParens, this.NoSpaceAfterOpenParen, this.NoSpaceBeforeCloseParen, this.SpaceAfterOpenBracket, this.SpaceBeforeCloseBracket, this.NoSpaceBetweenBrackets, this.NoSpaceAfterOpenBracket, this.NoSpaceBeforeCloseBracket, this.SpaceAfterOpenBrace, this.SpaceBeforeCloseBrace, this.NoSpaceBetweenEmptyBraceBrackets, this.NoSpaceAfterOpenBrace, this.NoSpaceBeforeCloseBrace, this.SpaceAfterTemplateHeadAndMiddle, this.SpaceBeforeTemplateMiddleAndTail, this.NoSpaceAfterTemplateHeadAndMiddle, this.NoSpaceBeforeTemplateMiddleAndTail, diff --git a/tests/cases/fourslash/formattingSpaceBetweenParent.ts b/tests/cases/fourslash/formattingSpaceBetweenParent.ts new file mode 100644 index 00000000000..60ec632f59c --- /dev/null +++ b/tests/cases/fourslash/formattingSpaceBetweenParent.ts @@ -0,0 +1,14 @@ +/// + +/////*1*/foo(() => 1); +/////*2*/foo(1); +/////*3*/if((true)){} + +format.setOption("InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis", true); +format.document(); +goTo.marker("1"); +verify.currentLineContentIs("foo( () => 1 );"); +goTo.marker("2"); +verify.currentLineContentIs("foo( 1 );"); +goTo.marker("3"); +verify.currentLineContentIs("if ( ( true ) ) { }"); From eee6851911d92e24c6bb3a487c36b52035fdd507 Mon Sep 17 00:00:00 2001 From: ikatyang Date: Wed, 26 Jul 2017 14:39:26 +0800 Subject: [PATCH 03/50] Retain literal type for prefix plus on number literal --- src/compiler/checker.ts | 6 +++- .../emitExponentiationOperator3.types | 32 +++++++++---------- .../reference/enumClassification.types | 2 +- ...dNumberLiteralAssignToNumberLiteralType.js | 8 +++++ ...erLiteralAssignToNumberLiteralType.symbols | 7 ++++ ...mberLiteralAssignToNumberLiteralType.types | 13 ++++++++ tests/baselines/reference/unaryPlus.types | 2 +- ...dNumberLiteralAssignToNumberLiteralType.ts | 3 ++ 8 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.js create mode 100644 tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.symbols create mode 100644 tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.types create mode 100644 tests/cases/compiler/prefixedNumberLiteralAssignToNumberLiteralType.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ff45ee0b73..ce71f190131 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16952,9 +16952,13 @@ namespace ts { if (operandType === silentNeverType) { return silentNeverType; } - if (node.operator === SyntaxKind.MinusToken && node.operand.kind === SyntaxKind.NumericLiteral) { + const isOperandNumericLiteral = node.operand.kind === SyntaxKind.NumericLiteral; + if (isOperandNumericLiteral && node.operator === SyntaxKind.MinusToken) { return getFreshTypeOfLiteralType(getLiteralType(-(node.operand).text)); } + if (isOperandNumericLiteral && node.operator === SyntaxKind.PlusToken) { + return getFreshTypeOfLiteralType(getLiteralType(+(node.operand).text)); + } switch (node.operator) { case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: diff --git a/tests/baselines/reference/emitExponentiationOperator3.types b/tests/baselines/reference/emitExponentiationOperator3.types index 2fe95d4db8f..666d46dca39 100644 --- a/tests/baselines/reference/emitExponentiationOperator3.types +++ b/tests/baselines/reference/emitExponentiationOperator3.types @@ -113,32 +113,32 @@ var temp = 10; (+3) ** temp++; >(+3) ** temp++ : number ->(+3) : number ->+3 : number +>(+3) : 3 +>+3 : 3 >3 : 3 >temp++ : number >temp : number (+3) ** temp--; >(+3) ** temp-- : number ->(+3) : number ->+3 : number +>(+3) : 3 +>+3 : 3 >3 : 3 >temp-- : number >temp : number (+3) ** ++temp; >(+3) ** ++temp : number ->(+3) : number ->+3 : number +>(+3) : 3 +>+3 : 3 >3 : 3 >++temp : number >temp : number (+3) ** --temp; >(+3) ** --temp : number ->(+3) : number ->+3 : number +>(+3) : 3 +>+3 : 3 >3 : 3 >--temp : number >temp : number @@ -185,8 +185,8 @@ var temp = 10; (+3) ** temp++ ** 2; >(+3) ** temp++ ** 2 : number ->(+3) : number ->+3 : number +>(+3) : 3 +>+3 : 3 >3 : 3 >temp++ ** 2 : number >temp++ : number @@ -195,8 +195,8 @@ var temp = 10; (+3) ** temp-- ** 2; >(+3) ** temp-- ** 2 : number ->(+3) : number ->+3 : number +>(+3) : 3 +>+3 : 3 >3 : 3 >temp-- ** 2 : number >temp-- : number @@ -205,8 +205,8 @@ var temp = 10; (+3) ** ++temp ** 2; >(+3) ** ++temp ** 2 : number ->(+3) : number ->+3 : number +>(+3) : 3 +>+3 : 3 >3 : 3 >++temp ** 2 : number >++temp : number @@ -215,8 +215,8 @@ var temp = 10; (+3) ** --temp ** 2; >(+3) ** --temp ** 2 : number ->(+3) : number ->+3 : number +>(+3) : 3 +>+3 : 3 >3 : 3 >--temp ** 2 : number >--temp : number diff --git a/tests/baselines/reference/enumClassification.types b/tests/baselines/reference/enumClassification.types index d0581c7fcae..73c8041a6b7 100644 --- a/tests/baselines/reference/enumClassification.types +++ b/tests/baselines/reference/enumClassification.types @@ -131,7 +131,7 @@ enum E11 { A = +0, >A : E11 ->+0 : number +>+0 : 0 >0 : 0 B, diff --git a/tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.js b/tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.js new file mode 100644 index 00000000000..63f1f42d101 --- /dev/null +++ b/tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.js @@ -0,0 +1,8 @@ +//// [prefixedNumberLiteralAssignToNumberLiteralType.ts] +let x: 1 = +1; + +let y: -1 = -1; + +//// [prefixedNumberLiteralAssignToNumberLiteralType.js] +var x = +1; +var y = -1; diff --git a/tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.symbols b/tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.symbols new file mode 100644 index 00000000000..4ab5d371e8a --- /dev/null +++ b/tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.symbols @@ -0,0 +1,7 @@ +=== tests/cases/compiler/prefixedNumberLiteralAssignToNumberLiteralType.ts === +let x: 1 = +1; +>x : Symbol(x, Decl(prefixedNumberLiteralAssignToNumberLiteralType.ts, 0, 3)) + +let y: -1 = -1; +>y : Symbol(y, Decl(prefixedNumberLiteralAssignToNumberLiteralType.ts, 2, 3)) + diff --git a/tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.types b/tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.types new file mode 100644 index 00000000000..93bd5cc6274 --- /dev/null +++ b/tests/baselines/reference/prefixedNumberLiteralAssignToNumberLiteralType.types @@ -0,0 +1,13 @@ +=== tests/cases/compiler/prefixedNumberLiteralAssignToNumberLiteralType.ts === +let x: 1 = +1; +>x : 1 +>+1 : 1 +>1 : 1 + +let y: -1 = -1; +>y : -1 +>-1 : -1 +>1 : 1 +>-1 : -1 +>1 : 1 + diff --git a/tests/baselines/reference/unaryPlus.types b/tests/baselines/reference/unaryPlus.types index 8f92b51a159..9bd741f2ad1 100644 --- a/tests/baselines/reference/unaryPlus.types +++ b/tests/baselines/reference/unaryPlus.types @@ -2,7 +2,7 @@ // allowed per spec var a = +1; >a : number ->+1 : number +>+1 : 1 >1 : 1 var b = +(""); diff --git a/tests/cases/compiler/prefixedNumberLiteralAssignToNumberLiteralType.ts b/tests/cases/compiler/prefixedNumberLiteralAssignToNumberLiteralType.ts new file mode 100644 index 00000000000..6c2afbba2bd --- /dev/null +++ b/tests/cases/compiler/prefixedNumberLiteralAssignToNumberLiteralType.ts @@ -0,0 +1,3 @@ +let x: 1 = +1; + +let y: -1 = -1; \ No newline at end of file From 5a85fca0cd9db17b95e80068f17467b0c7f04fe5 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 26 Jul 2017 15:19:17 -0700 Subject: [PATCH 04/50] Properly check mapped type constituents / Fix generic mapped type display --- src/compiler/checker.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ff45ee0b73..ec9867fa9b7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2558,10 +2558,8 @@ namespace ts { } function createTypeNodeFromObjectType(type: ObjectType): TypeNode { - if (type.objectFlags & ObjectFlags.Mapped) { - if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - return createMappedTypeNodeFromType(type); - } + if (isGenericMappedType(type)) { + return createMappedTypeNodeFromType(type); } const resolved = resolveStructuredTypeMembers(type); @@ -3464,11 +3462,9 @@ namespace ts { } function writeLiteralType(type: ObjectType, flags: TypeFormatFlags) { - if (type.objectFlags & ObjectFlags.Mapped) { - if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - writeMappedType(type); - return; - } + if (isGenericMappedType(type)) { + writeMappedType(type); + return; } const resolved = resolveStructuredTypeMembers(type); @@ -18641,6 +18637,8 @@ namespace ts { } function checkIndexedAccessType(node: IndexedAccessTypeNode) { + checkSourceElement(node.objectType); + checkSourceElement(node.indexType); checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); } From 072884a981b204c9f91169db170198dbb24f4e09 Mon Sep 17 00:00:00 2001 From: ikatyang Date: Thu, 27 Jul 2017 09:25:26 +0800 Subject: [PATCH 05/50] fold into one check --- src/compiler/checker.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ce71f190131..f2e6f3f840f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16952,12 +16952,13 @@ namespace ts { if (operandType === silentNeverType) { return silentNeverType; } - const isOperandNumericLiteral = node.operand.kind === SyntaxKind.NumericLiteral; - if (isOperandNumericLiteral && node.operator === SyntaxKind.MinusToken) { - return getFreshTypeOfLiteralType(getLiteralType(-(node.operand).text)); - } - if (isOperandNumericLiteral && node.operator === SyntaxKind.PlusToken) { - return getFreshTypeOfLiteralType(getLiteralType(+(node.operand).text)); + if (node.operand.kind === SyntaxKind.NumericLiteral) { + if (node.operator === SyntaxKind.MinusToken) { + return getFreshTypeOfLiteralType(getLiteralType(-(node.operand).text)); + } + else if (node.operator === SyntaxKind.PlusToken) { + return getFreshTypeOfLiteralType(getLiteralType(+(node.operand).text)); + } } switch (node.operator) { case SyntaxKind.PlusToken: From 62ddc99a4948d81a713bb9997dd6ddef4c460dea Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 27 Jul 2017 09:35:43 -0700 Subject: [PATCH 06/50] Accept new baselines --- .../reference/anyIndexedAccessArrayNoException.errors.txt | 5 ++++- .../reference/keyofAndIndexedAccessErrors.errors.txt | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt b/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt index e207468122b..01c5bd6b2e2 100644 --- a/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt +++ b/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt @@ -1,8 +1,11 @@ +tests/cases/compiler/anyIndexedAccessArrayNoException.ts(1,12): error TS1122: A tuple type element list cannot be empty. tests/cases/compiler/anyIndexedAccessArrayNoException.ts(1,12): error TS2538: Type '[]' cannot be used as an index type. -==== tests/cases/compiler/anyIndexedAccessArrayNoException.ts (1 errors) ==== +==== tests/cases/compiler/anyIndexedAccessArrayNoException.ts (2 errors) ==== var x: any[[]]; ~~ +!!! error TS1122: A tuple type element list cannot be empty. + ~~ !!! error TS2538: Type '[]' cannot be used as an index type. \ No newline at end of file diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt index 1e88e2abc43..0634c3419e0 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt @@ -15,6 +15,7 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(35,21): error tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(36,21): error TS2538: Type 'boolean' cannot be used as an index type. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(41,31): error TS2538: Type 'boolean' cannot be used as an index type. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(46,16): error TS2538: Type 'boolean' cannot be used as an index type. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(49,12): error TS1122: A tuple type element list cannot be empty. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(63,33): error TS2345: Argument of type '"size"' is not assignable to parameter of type '"name" | "width" | "height" | "visible"'. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(64,33): error TS2345: Argument of type '"name" | "size"' is not assignable to parameter of type '"name" | "width" | "height" | "visible"'. Type '"size"' is not assignable to type '"name" | "width" | "height" | "visible"'. @@ -28,7 +29,7 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(76,5): error tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'. -==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (24 errors) ==== +==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (25 errors) ==== class Shape { name: string; width: number; @@ -112,6 +113,8 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error type T60 = {}["toString"]; type T61 = []["toString"]; + ~~ +!!! error TS1122: A tuple type element list cannot be empty. declare let cond: boolean; From b6ec9512076a30f2c635cb58e59282ea6df6ca5b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 27 Jul 2017 09:50:57 -0700 Subject: [PATCH 07/50] Add missing check in getIndexedAccessForMappedType --- src/compiler/checker.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ec9867fa9b7..05b06860ecf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7573,10 +7573,16 @@ namespace ts { } function getIndexedAccessForMappedType(type: MappedType, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { - const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; - if (accessExpression && isAssignmentTarget(accessExpression) && type.declaration.readonlyToken) { - error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(type)); - return unknownType; + if (accessNode) { + // Check if the index type is assignable to 'keyof T' for the object type. + if (!isTypeAssignableTo(indexType, getIndexType(type))) { + error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(type)); + return unknownType; + } + if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && type.declaration.readonlyToken) { + error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(type)); + return unknownType; + } } const mapper = createTypeMapper([getTypeParameterFromMappedType(type)], [indexType]); const templateMapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper; From 9e900942b5080d61bbd23e3dd09d60d922a781a1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 27 Jul 2017 09:51:17 -0700 Subject: [PATCH 08/50] Add regression tests --- .../reference/mappedTypeErrors2.errors.txt | 35 +++++++++++++ .../baselines/reference/mappedTypeErrors2.js | 49 +++++++++++++++++++ .../types/mapped/mappedTypeErrors2.ts | 22 +++++++++ 3 files changed, 106 insertions(+) create mode 100644 tests/baselines/reference/mappedTypeErrors2.errors.txt create mode 100644 tests/baselines/reference/mappedTypeErrors2.js create mode 100644 tests/cases/conformance/types/mapped/mappedTypeErrors2.ts diff --git a/tests/baselines/reference/mappedTypeErrors2.errors.txt b/tests/baselines/reference/mappedTypeErrors2.errors.txt new file mode 100644 index 00000000000..18bc7cc7239 --- /dev/null +++ b/tests/baselines/reference/mappedTypeErrors2.errors.txt @@ -0,0 +1,35 @@ +tests/cases/conformance/types/mapped/mappedTypeErrors2.ts(9,30): error TS2536: Type 'K' cannot be used to index type 'T1'. +tests/cases/conformance/types/mapped/mappedTypeErrors2.ts(13,30): error TS2536: Type 'K' cannot be used to index type 'T3'. +tests/cases/conformance/types/mapped/mappedTypeErrors2.ts(15,47): error TS2536: Type 'S' cannot be used to index type 'AB'. +tests/cases/conformance/types/mapped/mappedTypeErrors2.ts(17,49): error TS2536: Type 'L' cannot be used to index type '{ [key in AB[S]]: true; }'. + + +==== tests/cases/conformance/types/mapped/mappedTypeErrors2.ts (4 errors) ==== + // Repros from #17238 + + type AB = { + a: 'a' + b: 'a' + }; + + type T1 = { [key in AB[K]]: true }; + type T2 = T1[K]; // Error + ~~~~~~~~ +!!! error TS2536: Type 'K' cannot be used to index type 'T1'. + + type R = AB[keyof AB]; // "a" + type T3 = { [key in R]: true }; + type T4 = T3[K] // Error + ~~~~~ +!!! error TS2536: Type 'K' cannot be used to index type 'T3'. + + type T5 = {[key in AB[S]]: true}[S]; // Error + ~~~~~ +!!! error TS2536: Type 'S' cannot be used to index type 'AB'. + + type T6 = {[key in AB[S]]: true}[L]; // Error + ~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type 'L' cannot be used to index type '{ [key in AB[S]]: true; }'. + + type T7 = {[key in AB[S]]: true}[L]; + \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeErrors2.js b/tests/baselines/reference/mappedTypeErrors2.js new file mode 100644 index 00000000000..d9fc6b0bc72 --- /dev/null +++ b/tests/baselines/reference/mappedTypeErrors2.js @@ -0,0 +1,49 @@ +//// [mappedTypeErrors2.ts] +// Repros from #17238 + +type AB = { + a: 'a' + b: 'a' +}; + +type T1 = { [key in AB[K]]: true }; +type T2 = T1[K]; // Error + +type R = AB[keyof AB]; // "a" +type T3 = { [key in R]: true }; +type T4 = T3[K] // Error + +type T5 = {[key in AB[S]]: true}[S]; // Error + +type T6 = {[key in AB[S]]: true}[L]; // Error + +type T7 = {[key in AB[S]]: true}[L]; + + +//// [mappedTypeErrors2.js] +// Repros from #17238 + + +//// [mappedTypeErrors2.d.ts] +declare type AB = { + a: 'a'; + b: 'a'; +}; +declare type T1 = { + [key in AB[K]]: true; +}; +declare type T2 = T1[K]; +declare type R = AB[keyof AB]; +declare type T3 = { + [key in R]: true; +}; +declare type T4 = T3[K]; +declare type T5 = { + [key in AB[S]]: true; +}[S]; +declare type T6 = { + [key in AB[S]]: true; +}[L]; +declare type T7 = { + [key in AB[S]]: true; +}[L]; diff --git a/tests/cases/conformance/types/mapped/mappedTypeErrors2.ts b/tests/cases/conformance/types/mapped/mappedTypeErrors2.ts new file mode 100644 index 00000000000..100a93eb159 --- /dev/null +++ b/tests/cases/conformance/types/mapped/mappedTypeErrors2.ts @@ -0,0 +1,22 @@ +// @strictNullChecks: true +// @declaration: true + +// Repros from #17238 + +type AB = { + a: 'a' + b: 'a' +}; + +type T1 = { [key in AB[K]]: true }; +type T2 = T1[K]; // Error + +type R = AB[keyof AB]; // "a" +type T3 = { [key in R]: true }; +type T4 = T3[K] // Error + +type T5 = {[key in AB[S]]: true}[S]; // Error + +type T6 = {[key in AB[S]]: true}[L]; // Error + +type T7 = {[key in AB[S]]: true}[L]; From c2d0d533c43573fa1a2de557b608c5af1239ff92 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 27 Jul 2017 16:30:22 -0700 Subject: [PATCH 09/50] dispose the watched wild card directories only if present --- .../unittests/tsserverProjectSystem.ts | 26 +++++++++++++++++++ src/server/project.ts | 10 ++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index f11900aea11..70388eac99b 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2332,6 +2332,32 @@ namespace ts.projectSystem { const navbar = projectService.externalProjects[0].getLanguageService(/*ensureSynchronized*/ false).getNavigationBarItems(f1.path); assert.equal(navbar[0].spans[0].length, f1.content.length); }); + + it("deleting config file opened from the external project works", () => { + const site = { + path: "/user/someuser/project/js/site.js", + content: "" + }; + const configFile = { + path: "/user/someuser/project/tsconfig.json", + content: "{}" + }; + const projectFileName = "/user/someuser/project/WebApplication6.csproj"; + const host = createServerHost([libFile, site, configFile]); + const projectService = createProjectService(host); + projectService.openExternalProjects([{ + projectFileName, + rootFiles: [toExternalFile(configFile.path), toExternalFile(site.path)], + options: { "allowJs": false, "allowNonTsExtensions": false, "allowSyntheticDefaultImports": false, "allowUnreachableCode": false, "allowUnusedLabels": false, "alwaysStrict": false, "compileOnSave": true, "declaration": false, "emitBOM": false, "emitDecoratorMetadata": false, "experimentalAsyncFunctions": false, "experimentalDecorators": false, "forceConsistentCasingInFileNames": false, "importHelpers": false, "inlineSourceMap": false, "inlineSources": false, "isolatedModules": false, "jsx": 0, "noEmit": false, "noEmitHelpers": false, "noEmitOnError": true, "noFallthroughCasesInSwitch": false, "noImplicitAny": false, "noImplicitReturns": false, "noImplicitThis": false, "noImplicitUseStrict": false, "noLib": false, "noResolve": false, "noUnusedLocals": false, "noUnusedParameters": false, "preserveConstEnums": false, "removeComments": false, "skipDefaultLibCheck": false, "skipLibCheck": false, "sourceMap": true, "strictNullChecks": false, "stripInternal": false, "suppressExcessPropertyErrors": false, "suppressImplicitAnyIndexErrors": false, "target": 1 }, + typeAcquisition: { "include": [] } + }]); + + let knownProjects = projectService.synchronizeProjectList([]); + host.reloadFS([libFile, site]); + host.triggerFileWatcherCallback(configFile.path); + + knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info)); + }); }); describe("Proper errors", () => { diff --git a/src/server/project.ts b/src/server/project.ts index 106089fad97..aadae734ee4 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1134,10 +1134,12 @@ namespace ts.server { this.typeRootsWatchers = undefined; } - this.directoriesWatchedForWildcards.forEach(watcher => { - watcher.close(); - }); - this.directoriesWatchedForWildcards = undefined; + if (this.directoriesWatchedForWildcards) { + this.directoriesWatchedForWildcards.forEach(watcher => { + watcher.close(); + }); + this.directoriesWatchedForWildcards = undefined; + } this.stopWatchingDirectory(); } From 711e890e59e10aa05a43cb938474a3d9c2270429 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 28 Jul 2017 10:31:59 -0700 Subject: [PATCH 10/50] Added | undefined to properties for watching that can be undefined --- src/server/project.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/project.ts b/src/server/project.ts index aadae734ee4..524b6c4d28d 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -936,9 +936,9 @@ namespace ts.server { export class ConfiguredProject extends Project { private typeAcquisition: TypeAcquisition; private projectFileWatcher: FileWatcher; - private directoryWatcher: FileWatcher; - private directoriesWatchedForWildcards: Map; - private typeRootsWatchers: FileWatcher[]; + private directoryWatcher: FileWatcher | undefined; + private directoriesWatchedForWildcards: Map | undefined; + private typeRootsWatchers: FileWatcher[] | undefined; readonly canonicalConfigFilePath: NormalizedPath; private plugins: PluginModule[] = []; From c9f8d90c9878ef4f29bdc5da0d2065fcc5cb0bf5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 28 Jul 2017 14:53:25 -0700 Subject: [PATCH 11/50] Update the test --- .../unittests/tsserverProjectSystem.ts | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 70388eac99b..00a3d66e83d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2345,18 +2345,36 @@ namespace ts.projectSystem { const projectFileName = "/user/someuser/project/WebApplication6.csproj"; const host = createServerHost([libFile, site, configFile]); const projectService = createProjectService(host); - projectService.openExternalProjects([{ + + const externalProject: protocol.ExternalProject = { projectFileName, - rootFiles: [toExternalFile(configFile.path), toExternalFile(site.path)], - options: { "allowJs": false, "allowNonTsExtensions": false, "allowSyntheticDefaultImports": false, "allowUnreachableCode": false, "allowUnusedLabels": false, "alwaysStrict": false, "compileOnSave": true, "declaration": false, "emitBOM": false, "emitDecoratorMetadata": false, "experimentalAsyncFunctions": false, "experimentalDecorators": false, "forceConsistentCasingInFileNames": false, "importHelpers": false, "inlineSourceMap": false, "inlineSources": false, "isolatedModules": false, "jsx": 0, "noEmit": false, "noEmitHelpers": false, "noEmitOnError": true, "noFallthroughCasesInSwitch": false, "noImplicitAny": false, "noImplicitReturns": false, "noImplicitThis": false, "noImplicitUseStrict": false, "noLib": false, "noResolve": false, "noUnusedLocals": false, "noUnusedParameters": false, "preserveConstEnums": false, "removeComments": false, "skipDefaultLibCheck": false, "skipLibCheck": false, "sourceMap": true, "strictNullChecks": false, "stripInternal": false, "suppressExcessPropertyErrors": false, "suppressImplicitAnyIndexErrors": false, "target": 1 }, + rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)], + options: { allowJs: false }, typeAcquisition: { "include": [] } - }]); + }; + + projectService.openExternalProjects([externalProject]); let knownProjects = projectService.synchronizeProjectList([]); + checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 }); + + const configProject = projectService.configuredProjects[0]; + checkProjectActualFiles(configProject, [libFile.path]); + + const diagnostics = configProject.getProjectErrors(); + assert.equal(diagnostics[0].code, Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); + host.reloadFS([libFile, site]); host.triggerFileWatcherCallback(configFile.path); knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info)); + checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 }); + + externalProject.rootFiles.length = 1; + projectService.openExternalProjects([externalProject]); + + checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); + checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); }); }); From 13171536fe38d2efcb188cf363c9324a1f88062f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 28 Jul 2017 15:03:43 -0700 Subject: [PATCH 12/50] Fix the errors in branch after port of #17469 --- src/harness/unittests/tsserverProjectSystem.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 00a3d66e83d..4a7cafa245b 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2361,11 +2361,11 @@ namespace ts.projectSystem { const configProject = projectService.configuredProjects[0]; checkProjectActualFiles(configProject, [libFile.path]); - const diagnostics = configProject.getProjectErrors(); + const diagnostics = configProject.getAllProjectErrors(); assert.equal(diagnostics[0].code, Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); host.reloadFS([libFile, site]); - host.triggerFileWatcherCallback(configFile.path); + host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Deleted); knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info)); checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 }); From 5895057578b47230ddb1a7195859b760737f9351 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 30 Jul 2017 17:44:38 -0700 Subject: [PATCH 13/50] Defer indexed access type resolution in more cases --- src/compiler/checker.ts | 59 ++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4de0c7a2a6c..a9a006c8ad8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2583,10 +2583,8 @@ namespace ts { } function createTypeNodeFromObjectType(type: ObjectType): TypeNode { - if (type.objectFlags & ObjectFlags.Mapped) { - if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - return createMappedTypeNodeFromType(type); - } + if (isGenericMappedType(type)) { + return createMappedTypeNodeFromType(type); } const resolved = resolveStructuredTypeMembers(type); @@ -3489,11 +3487,9 @@ namespace ts { } function writeLiteralType(type: ObjectType, flags: TypeFormatFlags) { - if (type.objectFlags & ObjectFlags.Mapped) { - if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - writeMappedType(type); - return; - } + if (isGenericMappedType(type)) { + writeMappedType(type); + return; } const resolved = resolveStructuredTypeMembers(type); @@ -5792,8 +5788,7 @@ namespace ts { } function isGenericMappedType(type: Type) { - return getObjectFlags(type) & ObjectFlags.Mapped && - maybeTypeOfKind(getConstraintTypeFromMappedType(type), TypeFlags.TypeVariable | TypeFlags.Index); + return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(type)); } function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { @@ -7602,26 +7597,36 @@ namespace ts { return instantiateType(getTemplateTypeFromMappedType(type), templateMapper); } + function isGenericObjectType(type: Type): boolean { + return type.flags & TypeFlags.TypeVariable ? true : + getObjectFlags(type) & ObjectFlags.Mapped ? isGenericIndexType(getConstraintTypeFromMappedType(type)) : + type.flags & TypeFlags.UnionOrIntersection ? forEach((type).types, isGenericObjectType) : + false; + } + + function isGenericIndexType(type: Type): boolean { + return type.flags & (TypeFlags.TypeVariable | TypeFlags.Index) ? true : + type.flags & TypeFlags.UnionOrIntersection ? forEach((type).types, isGenericIndexType) : + false; + } + function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { - // If the index type is generic, if the object type is generic and doesn't originate in an expression, - // or if the object type is a mapped type with a generic constraint, we are performing a higher-order - // index access where we cannot meaningfully access the properties of the object type. Note that for a - // generic T and a non-generic K, we eagerly resolve T[K] if it originates in an expression. This is to - // preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved - // eagerly using the constraint type of 'this' at the given location. - if (maybeTypeOfKind(indexType, TypeFlags.TypeVariable | TypeFlags.Index) || - maybeTypeOfKind(objectType, TypeFlags.TypeVariable) && !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) || - isGenericMappedType(objectType)) { + // If the object type is a mapped type { [P in K]: E }, where K is generic, we instantiate E using a mapper + // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we + // construct the type Box. + if (isGenericMappedType(objectType)) { + return getIndexedAccessForMappedType(objectType, indexType, accessNode); + } + // Otherwise, if the index type is generic, or if the object type is generic and doesn't originate in an + // expression, we are performing a higher-order index access where we cannot meaningfully access the properties + // of the object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates + // in an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]' + // has always been resolved eagerly using the constraint type of 'this' at the given location. + if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) && isGenericObjectType(objectType)) { if (objectType.flags & TypeFlags.Any) { return objectType; } - // If the object type is a mapped type { [P in K]: E }, we instantiate E using a mapper that substitutes - // the index type for P. For example, for an index access { [P in K]: Box }[X], we construct the - // type Box. - if (isGenericMappedType(objectType)) { - return getIndexedAccessForMappedType(objectType, indexType, accessNode); - } - // Otherwise we defer the operation by creating an indexed access type. + // Defer the operation by creating an indexed access type. const id = objectType.id + "," + indexType.id; let type = indexedAccessTypes.get(id); if (!type) { From 9cb14feef56d06d5f879cdd992656260ab5dfef9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 30 Jul 2017 18:08:10 -0700 Subject: [PATCH 14/50] Add tests --- .../compiler/deferredLookupTypeResolution.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/cases/compiler/deferredLookupTypeResolution.ts diff --git a/tests/cases/compiler/deferredLookupTypeResolution.ts b/tests/cases/compiler/deferredLookupTypeResolution.ts new file mode 100644 index 00000000000..6c9853266ad --- /dev/null +++ b/tests/cases/compiler/deferredLookupTypeResolution.ts @@ -0,0 +1,28 @@ +// @strict: true +// @declaration: true + +// Repro from #17456 + +type StringContains = ( + { [K in S]: 'true' } & + { [key: string]: 'false' } + )[L] + +type ObjectHasKey = StringContains + +type First = ObjectHasKey; // Should be deferred + +type T1 = ObjectHasKey<{ a: string }, 'a'>; // 'true' +type T2 = ObjectHasKey<{ a: string }, 'b'>; // 'false' + +// Verify that mapped type isn't eagerly resolved in type-to-string operation + +declare function f1(a: A, b: B): { [P in A | B]: any }; + +function f2(a: A) { + return f1(a, 'x'); +} + +function f3(x: 'a' | 'b') { + return f2(x); +} From b2ba275f23f50822ffb3ef8d31ee316d777d67b7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 30 Jul 2017 18:08:19 -0700 Subject: [PATCH 15/50] Accept new baselines --- .../reference/deferredLookupTypeResolution.js | 64 +++++++++++++++ .../deferredLookupTypeResolution.symbols | 76 ++++++++++++++++++ .../deferredLookupTypeResolution.types | 79 +++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 tests/baselines/reference/deferredLookupTypeResolution.js create mode 100644 tests/baselines/reference/deferredLookupTypeResolution.symbols create mode 100644 tests/baselines/reference/deferredLookupTypeResolution.types diff --git a/tests/baselines/reference/deferredLookupTypeResolution.js b/tests/baselines/reference/deferredLookupTypeResolution.js new file mode 100644 index 00000000000..5f8edd63e57 --- /dev/null +++ b/tests/baselines/reference/deferredLookupTypeResolution.js @@ -0,0 +1,64 @@ +//// [deferredLookupTypeResolution.ts] +// Repro from #17456 + +type StringContains = ( + { [K in S]: 'true' } & + { [key: string]: 'false' } + )[L] + +type ObjectHasKey = StringContains + +type First = ObjectHasKey; // Should be deferred + +type T1 = ObjectHasKey<{ a: string }, 'a'>; // 'true' +type T2 = ObjectHasKey<{ a: string }, 'b'>; // 'false' + +// Verify that mapped type isn't eagerly resolved in type-to-string operation + +declare function f1(a: A, b: B): { [P in A | B]: any }; + +function f2(a: A) { + return f1(a, 'x'); +} + +function f3(x: 'a' | 'b') { + return f2(x); +} + + +//// [deferredLookupTypeResolution.js] +"use strict"; +// Repro from #17456 +function f2(a) { + return f1(a, 'x'); +} +function f3(x) { + return f2(x); +} + + +//// [deferredLookupTypeResolution.d.ts] +declare type StringContains = ({ + [K in S]: 'true'; +} & { + [key: string]: 'false'; +})[L]; +declare type ObjectHasKey = StringContains; +declare type First = ObjectHasKey; +declare type T1 = ObjectHasKey<{ + a: string; +}, 'a'>; +declare type T2 = ObjectHasKey<{ + a: string; +}, 'b'>; +declare function f1(a: A, b: B): { + [P in A | B]: any; +}; +declare function f2(a: A): { + [P in A | "x"]: any; +}; +declare function f3(x: 'a' | 'b'): { + a: any; + b: any; + x: any; +}; diff --git a/tests/baselines/reference/deferredLookupTypeResolution.symbols b/tests/baselines/reference/deferredLookupTypeResolution.symbols new file mode 100644 index 00000000000..022dc3cc2f4 --- /dev/null +++ b/tests/baselines/reference/deferredLookupTypeResolution.symbols @@ -0,0 +1,76 @@ +=== tests/cases/compiler/deferredLookupTypeResolution.ts === +// Repro from #17456 + +type StringContains = ( +>StringContains : Symbol(StringContains, Decl(deferredLookupTypeResolution.ts, 0, 0)) +>S : Symbol(S, Decl(deferredLookupTypeResolution.ts, 2, 20)) +>L : Symbol(L, Decl(deferredLookupTypeResolution.ts, 2, 37)) + + { [K in S]: 'true' } & +>K : Symbol(K, Decl(deferredLookupTypeResolution.ts, 3, 7)) +>S : Symbol(S, Decl(deferredLookupTypeResolution.ts, 2, 20)) + + { [key: string]: 'false' } +>key : Symbol(key, Decl(deferredLookupTypeResolution.ts, 4, 7)) + + )[L] +>L : Symbol(L, Decl(deferredLookupTypeResolution.ts, 2, 37)) + +type ObjectHasKey = StringContains +>ObjectHasKey : Symbol(ObjectHasKey, Decl(deferredLookupTypeResolution.ts, 5, 6)) +>O : Symbol(O, Decl(deferredLookupTypeResolution.ts, 7, 18)) +>L : Symbol(L, Decl(deferredLookupTypeResolution.ts, 7, 20)) +>StringContains : Symbol(StringContains, Decl(deferredLookupTypeResolution.ts, 0, 0)) +>O : Symbol(O, Decl(deferredLookupTypeResolution.ts, 7, 18)) +>L : Symbol(L, Decl(deferredLookupTypeResolution.ts, 7, 20)) + +type First = ObjectHasKey; // Should be deferred +>First : Symbol(First, Decl(deferredLookupTypeResolution.ts, 7, 67)) +>T : Symbol(T, Decl(deferredLookupTypeResolution.ts, 9, 11)) +>ObjectHasKey : Symbol(ObjectHasKey, Decl(deferredLookupTypeResolution.ts, 5, 6)) +>T : Symbol(T, Decl(deferredLookupTypeResolution.ts, 9, 11)) + +type T1 = ObjectHasKey<{ a: string }, 'a'>; // 'true' +>T1 : Symbol(T1, Decl(deferredLookupTypeResolution.ts, 9, 37)) +>ObjectHasKey : Symbol(ObjectHasKey, Decl(deferredLookupTypeResolution.ts, 5, 6)) +>a : Symbol(a, Decl(deferredLookupTypeResolution.ts, 11, 24)) + +type T2 = ObjectHasKey<{ a: string }, 'b'>; // 'false' +>T2 : Symbol(T2, Decl(deferredLookupTypeResolution.ts, 11, 43)) +>ObjectHasKey : Symbol(ObjectHasKey, Decl(deferredLookupTypeResolution.ts, 5, 6)) +>a : Symbol(a, Decl(deferredLookupTypeResolution.ts, 12, 24)) + +// Verify that mapped type isn't eagerly resolved in type-to-string operation + +declare function f1(a: A, b: B): { [P in A | B]: any }; +>f1 : Symbol(f1, Decl(deferredLookupTypeResolution.ts, 12, 43)) +>A : Symbol(A, Decl(deferredLookupTypeResolution.ts, 16, 20)) +>B : Symbol(B, Decl(deferredLookupTypeResolution.ts, 16, 37)) +>a : Symbol(a, Decl(deferredLookupTypeResolution.ts, 16, 56)) +>A : Symbol(A, Decl(deferredLookupTypeResolution.ts, 16, 20)) +>b : Symbol(b, Decl(deferredLookupTypeResolution.ts, 16, 61)) +>B : Symbol(B, Decl(deferredLookupTypeResolution.ts, 16, 37)) +>P : Symbol(P, Decl(deferredLookupTypeResolution.ts, 16, 72)) +>A : Symbol(A, Decl(deferredLookupTypeResolution.ts, 16, 20)) +>B : Symbol(B, Decl(deferredLookupTypeResolution.ts, 16, 37)) + +function f2(a: A) { +>f2 : Symbol(f2, Decl(deferredLookupTypeResolution.ts, 16, 91)) +>A : Symbol(A, Decl(deferredLookupTypeResolution.ts, 18, 12)) +>a : Symbol(a, Decl(deferredLookupTypeResolution.ts, 18, 30)) +>A : Symbol(A, Decl(deferredLookupTypeResolution.ts, 18, 12)) + + return f1(a, 'x'); +>f1 : Symbol(f1, Decl(deferredLookupTypeResolution.ts, 12, 43)) +>a : Symbol(a, Decl(deferredLookupTypeResolution.ts, 18, 30)) +} + +function f3(x: 'a' | 'b') { +>f3 : Symbol(f3, Decl(deferredLookupTypeResolution.ts, 20, 1)) +>x : Symbol(x, Decl(deferredLookupTypeResolution.ts, 22, 12)) + + return f2(x); +>f2 : Symbol(f2, Decl(deferredLookupTypeResolution.ts, 16, 91)) +>x : Symbol(x, Decl(deferredLookupTypeResolution.ts, 22, 12)) +} + diff --git a/tests/baselines/reference/deferredLookupTypeResolution.types b/tests/baselines/reference/deferredLookupTypeResolution.types new file mode 100644 index 00000000000..d9486d30b07 --- /dev/null +++ b/tests/baselines/reference/deferredLookupTypeResolution.types @@ -0,0 +1,79 @@ +=== tests/cases/compiler/deferredLookupTypeResolution.ts === +// Repro from #17456 + +type StringContains = ( +>StringContains : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>S : S +>L : L + + { [K in S]: 'true' } & +>K : K +>S : S + + { [key: string]: 'false' } +>key : string + + )[L] +>L : L + +type ObjectHasKey = StringContains +>ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>O : O +>L : L +>StringContains : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>O : O +>L : L + +type First = ObjectHasKey; // Should be deferred +>First : ({ [K in S]: "true"; } & { [key: string]: "false"; })["0"] +>T : T +>ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>T : T + +type T1 = ObjectHasKey<{ a: string }, 'a'>; // 'true' +>T1 : "true" +>ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>a : string + +type T2 = ObjectHasKey<{ a: string }, 'b'>; // 'false' +>T2 : "false" +>ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>a : string + +// Verify that mapped type isn't eagerly resolved in type-to-string operation + +declare function f1(a: A, b: B): { [P in A | B]: any }; +>f1 : (a: A, b: B) => { [P in A | B]: any; } +>A : A +>B : B +>a : A +>A : A +>b : B +>B : B +>P : P +>A : A +>B : B + +function f2(a: A) { +>f2 : (a: A) => { [P in A | B]: any; } +>A : A +>a : A +>A : A + + return f1(a, 'x'); +>f1(a, 'x') : { [P in A | B]: any; } +>f1 : (a: A, b: B) => { [P in A | B]: any; } +>a : A +>'x' : "x" +} + +function f3(x: 'a' | 'b') { +>f3 : (x: "a" | "b") => { a: any; b: any; x: any; } +>x : "a" | "b" + + return f2(x); +>f2(x) : { a: any; b: any; x: any; } +>f2 : (a: A) => { [P in A | B]: any; } +>x : "a" | "b" +} + From caea4f3a50ece23a0d7a3adb20b2d6ee227e7e8a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 2 Aug 2017 11:54:29 -0700 Subject: [PATCH 16/50] Properly handle constraints for types like (T & { [x: string]: D })[K] --- src/compiler/checker.ts | 52 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a9a006c8ad8..0d3ac082f2a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5900,6 +5900,10 @@ namespace ts { } function getConstraintOfIndexedAccess(type: IndexedAccessType) { + const transformed = getTransformedIndexedAccessType(type); + if (transformed) { + return transformed; + } const baseObjectType = getBaseConstraintOfType(type.objectType); const baseIndexType = getBaseConstraintOfType(type.indexType); return baseObjectType || baseIndexType ? getIndexedAccessType(baseObjectType || type.objectType, baseIndexType || type.indexType) : undefined; @@ -5971,11 +5975,18 @@ namespace ts { return stringType; } if (t.flags & TypeFlags.IndexedAccess) { + const transformed = getTransformedIndexedAccessType(t); + if (transformed) { + return getBaseConstraint(transformed); + } const baseObjectType = getBaseConstraint((t).objectType); const baseIndexType = getBaseConstraint((t).indexType); const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType) : undefined; return baseIndexedAccess && baseIndexedAccess !== unknownType ? getBaseConstraint(baseIndexedAccess) : undefined; } + if (isGenericMappedType(t)) { + return emptyObjectType; + } return t; } } @@ -7610,7 +7621,44 @@ namespace ts { false; } - function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { + // Return true if the given type is a non-generic object type with a string index signature and no + // other members. + function isStringIndexOnlyType(type: Type) { + if (type.flags & TypeFlags.Object && !isGenericMappedType(type)) { + const t = resolveStructuredTypeMembers(type); + return t.properties.length === 0 && + t.callSignatures.length === 0 && t.constructSignatures.length === 0 && + t.stringIndexInfo && !t.numberIndexInfo; + } + return false; + } + + // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or + // more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a + // transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed + // access types with default property values as expressed by D. + function getTransformedIndexedAccessType(type: IndexedAccessType): Type { + const objectType = type.objectType; + if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((objectType).types, isStringIndexOnlyType)) { + const regularTypes: Type[] = []; + const stringIndexTypes: Type[] = []; + for (const t of (objectType).types) { + if (isStringIndexOnlyType(t)) { + stringIndexTypes.push(getIndexTypeOfType(t, IndexKind.String)); + } + else { + regularTypes.push(t); + } + } + return getUnionType([ + getIndexedAccessType(getIntersectionType(regularTypes), type.indexType), + getIntersectionType(stringIndexTypes) + ]); + } + return undefined; + } + + function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type { // If the object type is a mapped type { [P in K]: E }, where K is generic, we instantiate E using a mapper // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we // construct the type Box. @@ -18662,6 +18710,8 @@ namespace ts { } function checkIndexedAccessType(node: IndexedAccessTypeNode) { + checkSourceElement(node.objectType); + checkSourceElement(node.indexType); checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); } From 0bb1f6a4b84d656227781ae01b4228bcc3b8b0b4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 2 Aug 2017 12:06:39 -0700 Subject: [PATCH 17/50] Accept new baselines --- .../reference/anyIndexedAccessArrayNoException.errors.txt | 5 ++++- .../reference/keyofAndIndexedAccessErrors.errors.txt | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt b/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt index e207468122b..01c5bd6b2e2 100644 --- a/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt +++ b/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt @@ -1,8 +1,11 @@ +tests/cases/compiler/anyIndexedAccessArrayNoException.ts(1,12): error TS1122: A tuple type element list cannot be empty. tests/cases/compiler/anyIndexedAccessArrayNoException.ts(1,12): error TS2538: Type '[]' cannot be used as an index type. -==== tests/cases/compiler/anyIndexedAccessArrayNoException.ts (1 errors) ==== +==== tests/cases/compiler/anyIndexedAccessArrayNoException.ts (2 errors) ==== var x: any[[]]; ~~ +!!! error TS1122: A tuple type element list cannot be empty. + ~~ !!! error TS2538: Type '[]' cannot be used as an index type. \ No newline at end of file diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt index 1e88e2abc43..0634c3419e0 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt @@ -15,6 +15,7 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(35,21): error tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(36,21): error TS2538: Type 'boolean' cannot be used as an index type. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(41,31): error TS2538: Type 'boolean' cannot be used as an index type. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(46,16): error TS2538: Type 'boolean' cannot be used as an index type. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(49,12): error TS1122: A tuple type element list cannot be empty. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(63,33): error TS2345: Argument of type '"size"' is not assignable to parameter of type '"name" | "width" | "height" | "visible"'. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(64,33): error TS2345: Argument of type '"name" | "size"' is not assignable to parameter of type '"name" | "width" | "height" | "visible"'. Type '"size"' is not assignable to type '"name" | "width" | "height" | "visible"'. @@ -28,7 +29,7 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(76,5): error tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'. -==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (24 errors) ==== +==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (25 errors) ==== class Shape { name: string; width: number; @@ -112,6 +113,8 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error type T60 = {}["toString"]; type T61 = []["toString"]; + ~~ +!!! error TS1122: A tuple type element list cannot be empty. declare let cond: boolean; From 98f6761590c5eb1e8aa388268e7df957234dd00c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 2 Aug 2017 12:07:09 -0700 Subject: [PATCH 18/50] Add tests --- .../deferredLookupTypeResolution2.errors.txt | 31 +++++++++++ .../deferredLookupTypeResolution2.js | 55 +++++++++++++++++++ .../compiler/deferredLookupTypeResolution2.ts | 24 ++++++++ 3 files changed, 110 insertions(+) create mode 100644 tests/baselines/reference/deferredLookupTypeResolution2.errors.txt create mode 100644 tests/baselines/reference/deferredLookupTypeResolution2.js create mode 100644 tests/cases/compiler/deferredLookupTypeResolution2.ts diff --git a/tests/baselines/reference/deferredLookupTypeResolution2.errors.txt b/tests/baselines/reference/deferredLookupTypeResolution2.errors.txt new file mode 100644 index 00000000000..f6bbe72f1a6 --- /dev/null +++ b/tests/baselines/reference/deferredLookupTypeResolution2.errors.txt @@ -0,0 +1,31 @@ +tests/cases/compiler/deferredLookupTypeResolution2.ts(14,13): error TS2536: Type '({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]' cannot be used to index type '{ true: "true"; }'. +tests/cases/compiler/deferredLookupTypeResolution2.ts(19,21): error TS2536: Type '({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]]' cannot be used to index type '{ true: "true"; }'. + + +==== tests/cases/compiler/deferredLookupTypeResolution2.ts (2 errors) ==== + // Repro from #17456 + + type StringContains = ({ [K in S]: 'true' } & { [key: string]: 'false'})[L]; + + type ObjectHasKey = StringContains; + + type A = ObjectHasKey; + + type B = ObjectHasKey<[string, number], '1'>; // "true" + type C = ObjectHasKey<[string, number], '2'>; // "false" + type D = A<[string]>; // "true" + + // Error, "false" not handled + type E = { true: 'true' }[ObjectHasKey]; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type '({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]' cannot be used to index type '{ true: "true"; }'. + + type Juxtapose = ({ true: 'otherwise' } & { [k: string]: 'true' })[ObjectHasKey]; + + // Error, "otherwise" is missing + type DeepError = { true: 'true' }[Juxtapose]; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type '({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]]' cannot be used to index type '{ true: "true"; }'. + + type DeepOK = { true: 'true', otherwise: 'false' }[Juxtapose]; + \ No newline at end of file diff --git a/tests/baselines/reference/deferredLookupTypeResolution2.js b/tests/baselines/reference/deferredLookupTypeResolution2.js new file mode 100644 index 00000000000..97289f47f9c --- /dev/null +++ b/tests/baselines/reference/deferredLookupTypeResolution2.js @@ -0,0 +1,55 @@ +//// [deferredLookupTypeResolution2.ts] +// Repro from #17456 + +type StringContains = ({ [K in S]: 'true' } & { [key: string]: 'false'})[L]; + +type ObjectHasKey = StringContains; + +type A = ObjectHasKey; + +type B = ObjectHasKey<[string, number], '1'>; // "true" +type C = ObjectHasKey<[string, number], '2'>; // "false" +type D = A<[string]>; // "true" + +// Error, "false" not handled +type E = { true: 'true' }[ObjectHasKey]; + +type Juxtapose = ({ true: 'otherwise' } & { [k: string]: 'true' })[ObjectHasKey]; + +// Error, "otherwise" is missing +type DeepError = { true: 'true' }[Juxtapose]; + +type DeepOK = { true: 'true', otherwise: 'false' }[Juxtapose]; + + +//// [deferredLookupTypeResolution2.js] +"use strict"; +// Repro from #17456 + + +//// [deferredLookupTypeResolution2.d.ts] +declare type StringContains = ({ + [K in S]: 'true'; +} & { + [key: string]: 'false'; +})[L]; +declare type ObjectHasKey = StringContains; +declare type A = ObjectHasKey; +declare type B = ObjectHasKey<[string, number], '1'>; +declare type C = ObjectHasKey<[string, number], '2'>; +declare type D = A<[string]>; +declare type E = { + true: 'true'; +}[ObjectHasKey]; +declare type Juxtapose = ({ + true: 'otherwise'; +} & { + [k: string]: 'true'; +})[ObjectHasKey]; +declare type DeepError = { + true: 'true'; +}[Juxtapose]; +declare type DeepOK = { + true: 'true'; + otherwise: 'false'; +}[Juxtapose]; diff --git a/tests/cases/compiler/deferredLookupTypeResolution2.ts b/tests/cases/compiler/deferredLookupTypeResolution2.ts new file mode 100644 index 00000000000..4aa18c092ba --- /dev/null +++ b/tests/cases/compiler/deferredLookupTypeResolution2.ts @@ -0,0 +1,24 @@ +// @strict: true +// @declaration: true + +// Repro from #17456 + +type StringContains = ({ [K in S]: 'true' } & { [key: string]: 'false'})[L]; + +type ObjectHasKey = StringContains; + +type A = ObjectHasKey; + +type B = ObjectHasKey<[string, number], '1'>; // "true" +type C = ObjectHasKey<[string, number], '2'>; // "false" +type D = A<[string]>; // "true" + +// Error, "false" not handled +type E = { true: 'true' }[ObjectHasKey]; + +type Juxtapose = ({ true: 'otherwise' } & { [k: string]: 'true' })[ObjectHasKey]; + +// Error, "otherwise" is missing +type DeepError = { true: 'true' }[Juxtapose]; + +type DeepOK = { true: 'true', otherwise: 'false' }[Juxtapose]; From bb34bce4208c8a7b1e875a27b07e67dea0e7fe7a Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 2 Aug 2017 12:40:39 -0700 Subject: [PATCH 19/50] Set a high stack trace limit in command-line and server scenarios (#17464) --- src/compiler/sys.ts | 12 ++++++++++++ src/compiler/tsc.ts | 2 ++ src/server/server.ts | 2 ++ 3 files changed, 16 insertions(+) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 9c6d4bb795d..89cfb074bff 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -4,6 +4,18 @@ declare function setTimeout(handler: (...args: any[]) => void, timeout: number): declare function clearTimeout(handle: any): void; namespace ts { + /** + * Set a high stack trace limit to provide more information in case of an error. + * Called for command-line and server use cases. + * Not called if TypeScript is used as a library. + */ + /* @internal */ + export function setStackTraceLimit() { + if ((Error as any).stackTraceLimit < 100) { // Also tests that we won't set the property if it doesn't exist. + (Error as any).stackTraceLimit = 100; + } + } + export enum FileWatcherEventKind { Created, Changed, diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 8cc2e5c5ef6..db25afe45b6 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -665,6 +665,8 @@ namespace ts { } } +ts.setStackTraceLimit(); + if (ts.Debug.isDebugging) { ts.Debug.enableDebugInfo(); } diff --git a/src/server/server.ts b/src/server/server.ts index 6600b63dcb1..b72cc2f5a81 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -757,6 +757,8 @@ namespace ts.server { validateLocaleAndSetLanguage(localeStr, sys); } + setStackTraceLimit(); + const typingSafeListLocation = findArgument(Arguments.TypingSafeListLocation); const npmLocation = findArgument(Arguments.NpmLocation); From c06a30ae686c49a68c56f91bf8c5a10a3adc6857 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 2 Aug 2017 13:55:14 -0700 Subject: [PATCH 20/50] JSDoc Instantiation Fixes (#17553) * Fix #17383 - issue an error when jsdoc attempts to instantiate a builtin as a generic * Fix comment * Fix #17377 - only get type parameters from reference target if the type is a reference * Fix #17525 - Add SyntaxKind.AsteriskToken to isStartOfType --- src/compiler/checker.ts | 12 ++- src/compiler/parser.ts | 1 + ...docTypeGenericInstantiationAttempt.symbols | 12 +++ ...jsdocTypeGenericInstantiationAttempt.types | 12 +++ ...eNongenericInstantiationAttempt.errors.txt | 97 +++++++++++++++++++ .../jsdocTypeGenericInstantiationAttempt.ts | 10 ++ ...jsdocTypeNongenericInstantiationAttempt.ts | 71 ++++++++++++++ 7 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/jsdocTypeGenericInstantiationAttempt.symbols create mode 100644 tests/baselines/reference/jsdocTypeGenericInstantiationAttempt.types create mode 100644 tests/baselines/reference/jsdocTypeNongenericInstantiationAttempt.errors.txt create mode 100644 tests/cases/compiler/jsdocTypeGenericInstantiationAttempt.ts create mode 100644 tests/cases/compiler/jsdocTypeNongenericInstantiationAttempt.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 998a4451b39..57d6393d927 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18594,7 +18594,17 @@ namespace ts { forEach(node.typeArguments, checkSourceElement); if (produceDiagnostics) { const symbol = getNodeLinks(node).resolvedSymbol; - const typeParameters = symbol.flags & SymbolFlags.TypeAlias ? getSymbolLinks(symbol).typeParameters : (type).target.localTypeParameters; + if (!symbol) { + // There is no resolved symbol cached if the type resolved to a builtin + // via JSDoc type reference resolution (eg, Boolean became boolean), none + // of which are generic when they have no associated symbol + error(node, Diagnostics.Type_0_is_not_generic, typeToString(type)); + return; + } + let typeParameters = symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters; + if (!typeParameters && getObjectFlags(type) & ObjectFlags.Reference) { + typeParameters = (type).target.localTypeParameters; + } checkTypeArgumentConstraints(typeParameters, node.typeArguments); } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 34670cd2834..a7d749ee206 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2700,6 +2700,7 @@ namespace ts { case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.ObjectKeyword: + case SyntaxKind.AsteriskToken: return true; case SyntaxKind.MinusToken: return lookAhead(nextTokenIsNumericLiteral); diff --git a/tests/baselines/reference/jsdocTypeGenericInstantiationAttempt.symbols b/tests/baselines/reference/jsdocTypeGenericInstantiationAttempt.symbols new file mode 100644 index 00000000000..1b17b09fce2 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeGenericInstantiationAttempt.symbols @@ -0,0 +1,12 @@ +=== tests/cases/compiler/index.js === +/** + * @param {Array<*>} list + */ +function thing(list) { +>thing : Symbol(thing, Decl(index.js, 0, 0)) +>list : Symbol(list, Decl(index.js, 3, 15)) + + return list; +>list : Symbol(list, Decl(index.js, 3, 15)) +} + diff --git a/tests/baselines/reference/jsdocTypeGenericInstantiationAttempt.types b/tests/baselines/reference/jsdocTypeGenericInstantiationAttempt.types new file mode 100644 index 00000000000..50231c9d868 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeGenericInstantiationAttempt.types @@ -0,0 +1,12 @@ +=== tests/cases/compiler/index.js === +/** + * @param {Array<*>} list + */ +function thing(list) { +>thing : (list: any[]) => any[] +>list : any[] + + return list; +>list : any[] +} + diff --git a/tests/baselines/reference/jsdocTypeNongenericInstantiationAttempt.errors.txt b/tests/baselines/reference/jsdocTypeNongenericInstantiationAttempt.errors.txt new file mode 100644 index 00000000000..645eca539f4 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeNongenericInstantiationAttempt.errors.txt @@ -0,0 +1,97 @@ +tests/cases/compiler/index.js(2,19): error TS2315: Type 'boolean' is not generic. +tests/cases/compiler/index2.js(2,19): error TS2315: Type 'void' is not generic. +tests/cases/compiler/index3.js(2,19): error TS2315: Type 'undefined' is not generic. +tests/cases/compiler/index4.js(2,19): error TS2315: Type 'Function' is not generic. +tests/cases/compiler/index5.js(2,19): error TS2315: Type 'string' is not generic. +tests/cases/compiler/index6.js(2,19): error TS2315: Type 'number' is not generic. +tests/cases/compiler/index7.js(2,19): error TS2315: Type 'any' is not generic. +tests/cases/compiler/index8.js(4,12): error TS2304: Cannot find name 'fn'. +tests/cases/compiler/index8.js(4,15): error TS2304: Cannot find name 'T'. + + +==== tests/cases/compiler/index.js (1 errors) ==== + /** + * @param {(m: Boolean) => string} somebody + ~~~~~~~~~~ +!!! error TS2315: Type 'boolean' is not generic. + */ + function sayHello(somebody) { + return 'Hello ' + somebody; + } + +==== tests/cases/compiler/index2.js (1 errors) ==== + /** + * @param {(m: Void) => string} somebody + ~~~~~~~ +!!! error TS2315: Type 'void' is not generic. + */ + function sayHello2(somebody) { + return 'Hello ' + somebody; + } + + +==== tests/cases/compiler/index3.js (1 errors) ==== + /** + * @param {(m: Undefined) => string} somebody + ~~~~~~~~~~~~ +!!! error TS2315: Type 'undefined' is not generic. + */ + function sayHello3(somebody) { + return 'Hello ' + somebody; + } + + +==== tests/cases/compiler/index4.js (1 errors) ==== + /** + * @param {(m: Function) => string} somebody + ~~~~~~~~~~~ +!!! error TS2315: Type 'Function' is not generic. + */ + function sayHello4(somebody) { + return 'Hello ' + somebody; + } + + +==== tests/cases/compiler/index5.js (1 errors) ==== + /** + * @param {(m: String) => string} somebody + ~~~~~~~~~ +!!! error TS2315: Type 'string' is not generic. + */ + function sayHello5(somebody) { + return 'Hello ' + somebody; + } + + +==== tests/cases/compiler/index6.js (1 errors) ==== + /** + * @param {(m: Number) => string} somebody + ~~~~~~~~~ +!!! error TS2315: Type 'number' is not generic. + */ + function sayHello6(somebody) { + return 'Hello ' + somebody; + } + + +==== tests/cases/compiler/index7.js (1 errors) ==== + /** + * @param {(m: Object) => string} somebody + ~~~~~~~~~ +!!! error TS2315: Type 'any' is not generic. + */ + function sayHello7(somebody) { + return 'Hello ' + somebody; + } + +==== tests/cases/compiler/index8.js (2 errors) ==== + function fn() {} + + /** + * @param {fn} somebody + ~~ +!!! error TS2304: Cannot find name 'fn'. + ~ +!!! error TS2304: Cannot find name 'T'. + */ + function sayHello8(somebody) { } \ No newline at end of file diff --git a/tests/cases/compiler/jsdocTypeGenericInstantiationAttempt.ts b/tests/cases/compiler/jsdocTypeGenericInstantiationAttempt.ts new file mode 100644 index 00000000000..f7a694f6124 --- /dev/null +++ b/tests/cases/compiler/jsdocTypeGenericInstantiationAttempt.ts @@ -0,0 +1,10 @@ +// @allowJs: true +// @noEmit: true +// @checkJs: true +// @filename: index.js +/** + * @param {Array<*>} list + */ +function thing(list) { + return list; +} diff --git a/tests/cases/compiler/jsdocTypeNongenericInstantiationAttempt.ts b/tests/cases/compiler/jsdocTypeNongenericInstantiationAttempt.ts new file mode 100644 index 00000000000..bf05c7f6de7 --- /dev/null +++ b/tests/cases/compiler/jsdocTypeNongenericInstantiationAttempt.ts @@ -0,0 +1,71 @@ +// @allowJs: true +// @noEmit: true +// @checkJs: true +// @filename: index.js +/** + * @param {(m: Boolean) => string} somebody + */ +function sayHello(somebody) { + return 'Hello ' + somebody; +} + +// @filename: index2.js +/** + * @param {(m: Void) => string} somebody + */ +function sayHello2(somebody) { + return 'Hello ' + somebody; +} + + +// @filename: index3.js +/** + * @param {(m: Undefined) => string} somebody + */ +function sayHello3(somebody) { + return 'Hello ' + somebody; +} + + +// @filename: index4.js +/** + * @param {(m: Function) => string} somebody + */ +function sayHello4(somebody) { + return 'Hello ' + somebody; +} + + +// @filename: index5.js +/** + * @param {(m: String) => string} somebody + */ +function sayHello5(somebody) { + return 'Hello ' + somebody; +} + + +// @filename: index6.js +/** + * @param {(m: Number) => string} somebody + */ +function sayHello6(somebody) { + return 'Hello ' + somebody; +} + + +// @filename: index7.js +/** + * @param {(m: Object) => string} somebody + */ +function sayHello7(somebody) { + return 'Hello ' + somebody; +} + +// @filename: index8.js +function fn() {} + +/** + * @param {fn} somebody + */ +function sayHello8(somebody) { } \ No newline at end of file From 13750d2d654adfe0b58022a2db8cef068d0798bb Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 3 Aug 2017 08:07:07 -0700 Subject: [PATCH 21/50] Only infer from members of object types if the types are possibly related --- src/compiler/checker.ts | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 57d6393d927..6f8a6786906 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10325,6 +10325,19 @@ namespace ts { } } + function isPossiblyAssignableTo(source: Type, target: Type) { + const properties = getPropertiesOfObjectType(target); + for (const targetProp of properties) { + if (!(targetProp.flags & (SymbolFlags.Optional | SymbolFlags.Prototype))) { + const sourceProp = getPropertyOfObjectType(source, targetProp.escapedName); + if (!sourceProp) { + return false; + } + } + } + return true; + } + function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) { let symbolStack: Symbol[]; let visited: Map; @@ -10518,10 +10531,14 @@ namespace ts { return; } } - inferFromProperties(source, target); - inferFromSignatures(source, target, SignatureKind.Call); - inferFromSignatures(source, target, SignatureKind.Construct); - inferFromIndexTypes(source, target); + // Infer from the members of source and target only if the two types are possibly related. We check + // in both directions because we may be inferring for a co-variant or a contra-variant position. + if (isPossiblyAssignableTo(source, target) || isPossiblyAssignableTo(target, source)) { + inferFromProperties(source, target); + inferFromSignatures(source, target, SignatureKind.Call); + inferFromSignatures(source, target, SignatureKind.Construct); + inferFromIndexTypes(source, target); + } } function inferFromProperties(source: Type, target: Type) { From 0d7f0e0e196312bc73ab41c7f625709711d59492 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 3 Aug 2017 09:14:59 -0700 Subject: [PATCH 22/50] Test:infer from related types only --- .../reference/doNotInferUnrelatedTypes.js | 11 ++++++++ .../doNotInferUnrelatedTypes.symbols | 24 ++++++++++++++++++ .../reference/doNotInferUnrelatedTypes.types | 25 +++++++++++++++++++ .../compiler/doNotInferUnrelatedTypes.ts | 6 +++++ 4 files changed, 66 insertions(+) create mode 100644 tests/baselines/reference/doNotInferUnrelatedTypes.js create mode 100644 tests/baselines/reference/doNotInferUnrelatedTypes.symbols create mode 100644 tests/baselines/reference/doNotInferUnrelatedTypes.types create mode 100644 tests/cases/compiler/doNotInferUnrelatedTypes.ts diff --git a/tests/baselines/reference/doNotInferUnrelatedTypes.js b/tests/baselines/reference/doNotInferUnrelatedTypes.js new file mode 100644 index 00000000000..cff708cec89 --- /dev/null +++ b/tests/baselines/reference/doNotInferUnrelatedTypes.js @@ -0,0 +1,11 @@ +//// [doNotInferUnrelatedTypes.ts] +// #16709 +declare function dearray(ara: ReadonlyArray): T; +type LiteralType = "foo" | "bar"; +declare var alt: Array; + +let foo: LiteralType = dearray(alt); + + +//// [doNotInferUnrelatedTypes.js] +var foo = dearray(alt); diff --git a/tests/baselines/reference/doNotInferUnrelatedTypes.symbols b/tests/baselines/reference/doNotInferUnrelatedTypes.symbols new file mode 100644 index 00000000000..ce7351cf78f --- /dev/null +++ b/tests/baselines/reference/doNotInferUnrelatedTypes.symbols @@ -0,0 +1,24 @@ +=== tests/cases/compiler/doNotInferUnrelatedTypes.ts === +// #16709 +declare function dearray(ara: ReadonlyArray): T; +>dearray : Symbol(dearray, Decl(doNotInferUnrelatedTypes.ts, 0, 0)) +>T : Symbol(T, Decl(doNotInferUnrelatedTypes.ts, 1, 25)) +>ara : Symbol(ara, Decl(doNotInferUnrelatedTypes.ts, 1, 28)) +>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.d.ts, --, --)) +>T : Symbol(T, Decl(doNotInferUnrelatedTypes.ts, 1, 25)) +>T : Symbol(T, Decl(doNotInferUnrelatedTypes.ts, 1, 25)) + +type LiteralType = "foo" | "bar"; +>LiteralType : Symbol(LiteralType, Decl(doNotInferUnrelatedTypes.ts, 1, 54)) + +declare var alt: Array; +>alt : Symbol(alt, Decl(doNotInferUnrelatedTypes.ts, 3, 11)) +>Array : Symbol(Array, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>LiteralType : Symbol(LiteralType, Decl(doNotInferUnrelatedTypes.ts, 1, 54)) + +let foo: LiteralType = dearray(alt); +>foo : Symbol(foo, Decl(doNotInferUnrelatedTypes.ts, 5, 3)) +>LiteralType : Symbol(LiteralType, Decl(doNotInferUnrelatedTypes.ts, 1, 54)) +>dearray : Symbol(dearray, Decl(doNotInferUnrelatedTypes.ts, 0, 0)) +>alt : Symbol(alt, Decl(doNotInferUnrelatedTypes.ts, 3, 11)) + diff --git a/tests/baselines/reference/doNotInferUnrelatedTypes.types b/tests/baselines/reference/doNotInferUnrelatedTypes.types new file mode 100644 index 00000000000..5068c04ab1b --- /dev/null +++ b/tests/baselines/reference/doNotInferUnrelatedTypes.types @@ -0,0 +1,25 @@ +=== tests/cases/compiler/doNotInferUnrelatedTypes.ts === +// #16709 +declare function dearray(ara: ReadonlyArray): T; +>dearray : (ara: ReadonlyArray) => T +>T : T +>ara : ReadonlyArray +>ReadonlyArray : ReadonlyArray +>T : T +>T : T + +type LiteralType = "foo" | "bar"; +>LiteralType : LiteralType + +declare var alt: Array; +>alt : LiteralType[] +>Array : T[] +>LiteralType : LiteralType + +let foo: LiteralType = dearray(alt); +>foo : LiteralType +>LiteralType : LiteralType +>dearray(alt) : LiteralType +>dearray : (ara: ReadonlyArray) => T +>alt : LiteralType[] + diff --git a/tests/cases/compiler/doNotInferUnrelatedTypes.ts b/tests/cases/compiler/doNotInferUnrelatedTypes.ts new file mode 100644 index 00000000000..08a7d793430 --- /dev/null +++ b/tests/cases/compiler/doNotInferUnrelatedTypes.ts @@ -0,0 +1,6 @@ +// #16709 +declare function dearray(ara: ReadonlyArray): T; +type LiteralType = "foo" | "bar"; +declare var alt: Array; + +let foo: LiteralType = dearray(alt); From 86d0fa27a27bfa6f6a90053802e04036559603a8 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 3 Aug 2017 16:33:04 -0700 Subject: [PATCH 23/50] Use findAncestor in more places (#17601) --- src/compiler/utilities.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 24e0d59318b..76d242e8b08 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -922,21 +922,11 @@ namespace ts { } export function getContainingFunction(node: Node): FunctionLike { - while (true) { - node = node.parent; - if (!node || isFunctionLike(node)) { - return node; - } - } + return findAncestor(node.parent, isFunctionLike); } export function getContainingClass(node: Node): ClassLikeDeclaration { - while (true) { - node = node.parent; - if (!node || isClassLike(node)) { - return node; - } - } + return findAncestor(node.parent, isClassLike); } export function getThisContainer(node: Node, includeArrowFunctions: boolean): Node { From b747c2dd96ede26c853dee1d8882d73bb6d8ddf8 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 3 Aug 2017 18:30:09 -0700 Subject: [PATCH 24/50] exclude node_modules unless explicitly included --- src/compiler/commandLineParser.ts | 6 +- src/compiler/core.ts | 101 ++++++--- src/harness/unittests/matchFiles.ts | 193 ++++++++++++------ .../nodeModulesMaxDepthExceeded.errors.txt | 2 +- .../nodeModulesMaxDepthExceeded.errors.txt | 2 +- .../maxDepthExceeded/tsconfig.json | 2 +- 6 files changed, 200 insertions(+), 106 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 554b46dcdfe..b0cb7f94029 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1446,14 +1446,10 @@ namespace ts { } } else { - // If no includes were specified, exclude common package folders and the outDir - const specs = includeSpecs ? [] : ["node_modules", "bower_components", "jspm_packages"]; - const outDir = raw["compilerOptions"] && raw["compilerOptions"]["outDir"]; if (outDir) { - specs.push(outDir); + excludeSpecs = [outDir]; } - excludeSpecs = specs; } if (fileNames === undefined && includeSpecs === undefined) { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 728fb433c04..07f979333b6 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1889,14 +1889,54 @@ namespace ts { const reservedCharacterPattern = /[^\w\s\/]/g; const wildcardCharCodes = [CharacterCodes.asterisk, CharacterCodes.question]; - /** - * Matches any single directory segment unless it is the last segment and a .min.js file - * Breakdown: - * [^./] # matches everything up to the first . character (excluding directory seperators) - * (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension - */ - const singleAsteriskRegexFragmentFiles = "([^./]|(\\.(?!min\\.js$))?)*"; - const singleAsteriskRegexFragmentOther = "[^/]*"; + /* @internal */ + export const commonPackageFolders: ReadonlyArray = ["node_modules", "bower_components", "jspm_packages"]; + + const implicitExcludePathRegexPattern = `(?!(${commonPackageFolders.join("|")})(/|$))`; + + interface WildcardMatcher { + singleAsteriskRegexFragment: string; + doubleAsteriskRegexFragment: string; + replaceWildcardCharacter: (match: string) => string; + } + + const filesMatcher: WildcardMatcher = { + /** + * Matches any single directory segment unless it is the last segment and a .min.js file + * Breakdown: + * [^./] # matches everything up to the first . character (excluding directory seperators) + * (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension + */ + singleAsteriskRegexFragment: "([^./]|(\\.(?!min\\.js$))?)*", + /** + * Regex for the ** wildcard. Matches any number of subdirectories. When used for including + * files or directories, does not match subdirectories that start with a . character + */ + doubleAsteriskRegexFragment: `(/${implicitExcludePathRegexPattern}[^/.][^/]*)*?`, + replaceWildcardCharacter: match => replaceWildcardCharacter(match, filesMatcher.singleAsteriskRegexFragment) + }; + + const directoriesMatcher: WildcardMatcher = { + singleAsteriskRegexFragment: "[^/]*", + /** + * Regex for the ** wildcard. Matches any number of subdirectories. When used for including + * files or directories, does not match subdirectories that start with a . character + */ + doubleAsteriskRegexFragment: `(/${implicitExcludePathRegexPattern}[^/.][^/]*)*?`, + replaceWildcardCharacter: match => replaceWildcardCharacter(match, directoriesMatcher.singleAsteriskRegexFragment) + }; + + const excludeMatcher: WildcardMatcher = { + singleAsteriskRegexFragment: "[^/]*", + doubleAsteriskRegexFragment: "(/.+?)?", + replaceWildcardCharacter: match => replaceWildcardCharacter(match, excludeMatcher.singleAsteriskRegexFragment) + }; + + const wildcardMatchers = { + files: filesMatcher, + directories: directoriesMatcher, + exclude: excludeMatcher + }; export function getRegularExpressionForWildcard(specs: ReadonlyArray, basePath: string, usage: "files" | "directories" | "exclude"): string | undefined { const patterns = getRegularExpressionsForWildcards(specs, basePath, usage); @@ -1915,17 +1955,8 @@ namespace ts { return undefined; } - const replaceWildcardCharacter = usage === "files" ? replaceWildCardCharacterFiles : replaceWildCardCharacterOther; - const singleAsteriskRegexFragment = usage === "files" ? singleAsteriskRegexFragmentFiles : singleAsteriskRegexFragmentOther; - - /** - * Regex for the ** wildcard. Matches any number of subdirectories. When used for including - * files or directories, does not match subdirectories that start with a . character - */ - const doubleAsteriskRegexFragment = usage === "exclude" ? "(/.+?)?" : "(/[^/.][^/]*)*?"; - return flatMap(specs, spec => - spec && getSubPatternFromSpec(spec, basePath, usage, singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter)); + spec && getSubPatternFromSpec(spec, basePath, usage, wildcardMatchers[usage])); } /** @@ -1936,7 +1967,7 @@ namespace ts { return !/[.*?]/.test(lastPathComponent); } - function getSubPatternFromSpec(spec: string, basePath: string, usage: "files" | "directories" | "exclude", singleAsteriskRegexFragment: string, doubleAsteriskRegexFragment: string, replaceWildcardCharacter: (match: string) => string): string | undefined { + function getSubPatternFromSpec(spec: string, basePath: string, usage: "files" | "directories" | "exclude", { singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter }: WildcardMatcher): string | undefined { let subpattern = ""; let hasRecursiveDirectoryWildcard = false; let hasWrittenComponent = false; @@ -1975,20 +2006,36 @@ namespace ts { } if (usage !== "exclude") { + let componentPattern = ""; // The * and ? wildcards should not match directories or files that start with . if they // appear first in a component. Dotted directories and files can be included explicitly // like so: **/.*/.* if (component.charCodeAt(0) === CharacterCodes.asterisk) { - subpattern += "([^./]" + singleAsteriskRegexFragment + ")?"; + componentPattern += "([^./]" + singleAsteriskRegexFragment + ")?"; component = component.substr(1); } else if (component.charCodeAt(0) === CharacterCodes.question) { - subpattern += "[^./]"; + componentPattern += "[^./]"; component = component.substr(1); } - } - subpattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter); + componentPattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter); + + // Patterns should not include subfolders like node_modules unless they are + // explicitly included as part of the path. + // + // As an optimization, if the component pattern is the same as the component, + // then there definitely were no wildcard characters and we do not need to + // add the exclusion pattern. + if (componentPattern !== component) { + subpattern += implicitExcludePathRegexPattern; + } + + subpattern += componentPattern; + } + else { + subpattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter); + } } hasWrittenComponent = true; @@ -2002,14 +2049,6 @@ namespace ts { return subpattern; } - function replaceWildCardCharacterFiles(match: string) { - return replaceWildcardCharacter(match, singleAsteriskRegexFragmentFiles); - } - - function replaceWildCardCharacterOther(match: string) { - return replaceWildcardCharacter(match, singleAsteriskRegexFragmentOther); - } - function replaceWildcardCharacter(match: string, singleAsteriskRegexFragment: string) { return match === "*" ? singleAsteriskRegexFragment : match === "?" ? "[^/]" : "\\" + match; } diff --git a/src/harness/unittests/matchFiles.ts b/src/harness/unittests/matchFiles.ts index e0454671930..71b1bfff11a 100644 --- a/src/harness/unittests/matchFiles.ts +++ b/src/harness/unittests/matchFiles.ts @@ -73,6 +73,7 @@ namespace ts { "c:/dev/a.d.ts", "c:/dev/a.js", "c:/dev/b.ts", + "c:/dev/x/a.ts", "c:/dev/node_modules/a.ts", "c:/dev/bower_components/a.ts", "c:/dev/jspm_packages/a.ts" @@ -141,7 +142,8 @@ namespace ts { errors: [], fileNames: [ "c:/dev/a.ts", - "c:/dev/b.ts" + "c:/dev/b.ts", + "c:/dev/x/a.ts" ], wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive @@ -462,7 +464,6 @@ namespace ts { }; validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - it("same named declarations are excluded", () => { const json = { include: [ @@ -651,71 +652,127 @@ namespace ts { }; validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - it("with common package folders and no exclusions", () => { - const json = { - include: [ - "**/a.ts" - ] - }; - const expected: ts.ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/bower_components/a.ts", - "c:/dev/jspm_packages/a.ts", - "c:/dev/node_modules/a.ts" - ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("with common package folders and exclusions", () => { - const json = { - include: [ - "**/a.ts" - ], - exclude: [ - "a.ts" - ] - }; - const expected: ts.ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/bower_components/a.ts", - "c:/dev/jspm_packages/a.ts", - "c:/dev/node_modules/a.ts" - ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("with common package folders and empty exclude", () => { - const json = { - include: [ - "**/a.ts" - ], - exclude: [] - }; - const expected: ts.ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/bower_components/a.ts", - "c:/dev/jspm_packages/a.ts", - "c:/dev/node_modules/a.ts" - ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + describe("with common package folders", () => { + it("and no exclusions", () => { + const json = { + include: [ + "**/a.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/x/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); + it("and exclusions", () => { + const json = { + include: [ + "**/?.ts" + ], + exclude: [ + "a.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/b.ts", + "c:/dev/x/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); + it("and empty exclude", () => { + const json = { + include: [ + "**/a.ts" + ], + exclude: [] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/x/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); + it("and explicit recursive include", () => { + const json = { + include: [ + "**/a.ts", + "**/node_modules/a.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/x/a.ts", + "c:/dev/node_modules/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); + it("and wildcard include", () => { + const json = { + include: [ + "*/a.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/x/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); + it("and explicit wildcard include", () => { + const json = { + include: [ + "*/a.ts", + "node_modules/a.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/x/a.ts", + "c:/dev/node_modules/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); }); it("exclude .js files when allowJs=false", () => { const json = { @@ -1066,6 +1123,7 @@ namespace ts { }; validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); + describe("with trailing recursive directory", () => { it("in includes", () => { const json = { @@ -1264,6 +1322,7 @@ namespace ts { }); }); }); + describe("with files or folders that begin with a .", () => { it("that are not explicitly included", () => { const json = { diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/amd/nodeModulesMaxDepthExceeded.errors.txt b/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/amd/nodeModulesMaxDepthExceeded.errors.txt index a1b170ce665..bd2dd6231f3 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/amd/nodeModulesMaxDepthExceeded.errors.txt +++ b/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/amd/nodeModulesMaxDepthExceeded.errors.txt @@ -9,7 +9,7 @@ maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it i "maxNodeModuleJsDepth": 1, // Note: Module m1 is already included as a root file "outDir": "built" }, - "include": ["**/*"], + "include": ["**/*", "node_modules/**/*"], "exclude": ["node_modules/m2/**/*"] } diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.errors.txt b/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.errors.txt index a1b170ce665..bd2dd6231f3 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.errors.txt +++ b/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.errors.txt @@ -9,7 +9,7 @@ maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it i "maxNodeModuleJsDepth": 1, // Note: Module m1 is already included as a root file "outDir": "built" }, - "include": ["**/*"], + "include": ["**/*", "node_modules/**/*"], "exclude": ["node_modules/m2/**/*"] } diff --git a/tests/cases/projects/NodeModulesSearch/maxDepthExceeded/tsconfig.json b/tests/cases/projects/NodeModulesSearch/maxDepthExceeded/tsconfig.json index 52633bb5a98..b2ee28482ba 100644 --- a/tests/cases/projects/NodeModulesSearch/maxDepthExceeded/tsconfig.json +++ b/tests/cases/projects/NodeModulesSearch/maxDepthExceeded/tsconfig.json @@ -4,6 +4,6 @@ "maxNodeModuleJsDepth": 1, // Note: Module m1 is already included as a root file "outDir": "built" }, - "include": ["**/*"], + "include": ["**/*", "node_modules/**/*"], "exclude": ["node_modules/m2/**/*"] } From d7fff8ebe9d3bab5ccb23a817a2798b9c8d26f85 Mon Sep 17 00:00:00 2001 From: Yui Date: Fri, 4 Aug 2017 19:12:13 -0700 Subject: [PATCH 25/50] [Master] fix 12985 emit leading and trailing comment around binary operator (#16584) * Emit leading and trailing on binary operator * Add tests and baselines * Update baselines --- src/compiler/emitter.ts | 2 ++ .../reference/commentOnBinaryOperator1.js | 25 ++++++++++++++++ .../commentOnBinaryOperator1.symbols | 19 ++++++++++++ .../reference/commentOnBinaryOperator1.types | 29 +++++++++++++++++++ .../reference/commentOnBinaryOperator2.js | 22 ++++++++++++++ .../commentOnBinaryOperator2.symbols | 19 ++++++++++++ .../reference/commentOnBinaryOperator2.types | 29 +++++++++++++++++++ .../commentsArgumentsOfCallExpression2.js | 2 +- .../reference/parser15.4.4.14-9-2.js | 6 ++-- .../parserGreaterThanTokenAmbiguity10.js | 3 +- .../parserGreaterThanTokenAmbiguity15.js | 3 +- .../parserGreaterThanTokenAmbiguity20.js | 3 +- .../parserGreaterThanTokenAmbiguity5.js | 3 +- .../typeGuardsInConditionalExpression.js | 2 +- .../compiler/commentOnBinaryOperator1.ts | 12 ++++++++ .../compiler/commentOnBinaryOperator2.ts | 13 +++++++++ 16 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 tests/baselines/reference/commentOnBinaryOperator1.js create mode 100644 tests/baselines/reference/commentOnBinaryOperator1.symbols create mode 100644 tests/baselines/reference/commentOnBinaryOperator1.types create mode 100644 tests/baselines/reference/commentOnBinaryOperator2.js create mode 100644 tests/baselines/reference/commentOnBinaryOperator2.symbols create mode 100644 tests/baselines/reference/commentOnBinaryOperator2.types create mode 100644 tests/cases/compiler/commentOnBinaryOperator1.ts create mode 100644 tests/cases/compiler/commentOnBinaryOperator2.ts diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index de08b209d3e..4f07331a98e 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1346,7 +1346,9 @@ namespace ts { emitExpression(node.left); increaseIndentIf(indentBeforeOperator, isCommaOperator ? " " : undefined); + emitLeadingCommentsOfPosition(node.operatorToken.pos); writeTokenNode(node.operatorToken); + emitTrailingCommentsOfPosition(node.operatorToken.end); increaseIndentIf(indentAfterOperator, " "); emitExpression(node.right); decreaseIndentIf(indentBeforeOperator, indentAfterOperator); diff --git a/tests/baselines/reference/commentOnBinaryOperator1.js b/tests/baselines/reference/commentOnBinaryOperator1.js new file mode 100644 index 00000000000..daad6f5bdde --- /dev/null +++ b/tests/baselines/reference/commentOnBinaryOperator1.js @@ -0,0 +1,25 @@ +//// [commentOnBinaryOperator1.ts] +var a = 'some' + // comment + + 'text'; + +var b = 'some' + /* comment */ + + 'text'; + +var c = 'some' + /* comment */ + + /*comment1*/ + 'text'; + +//// [commentOnBinaryOperator1.js] +var a = 'some' + // comment + + 'text'; +var b = 'some' + /* comment */ + + 'text'; +var c = 'some' + /* comment */ + +/*comment1*/ + 'text'; diff --git a/tests/baselines/reference/commentOnBinaryOperator1.symbols b/tests/baselines/reference/commentOnBinaryOperator1.symbols new file mode 100644 index 00000000000..db92e4e4fc5 --- /dev/null +++ b/tests/baselines/reference/commentOnBinaryOperator1.symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/commentOnBinaryOperator1.ts === +var a = 'some' +>a : Symbol(a, Decl(commentOnBinaryOperator1.ts, 0, 3)) + + // comment + + 'text'; + +var b = 'some' +>b : Symbol(b, Decl(commentOnBinaryOperator1.ts, 4, 3)) + + /* comment */ + + 'text'; + +var c = 'some' +>c : Symbol(c, Decl(commentOnBinaryOperator1.ts, 8, 3)) + + /* comment */ + + /*comment1*/ + 'text'; diff --git a/tests/baselines/reference/commentOnBinaryOperator1.types b/tests/baselines/reference/commentOnBinaryOperator1.types new file mode 100644 index 00000000000..59724d42f51 --- /dev/null +++ b/tests/baselines/reference/commentOnBinaryOperator1.types @@ -0,0 +1,29 @@ +=== tests/cases/compiler/commentOnBinaryOperator1.ts === +var a = 'some' +>a : string +>'some' // comment + 'text' : string +>'some' : "some" + + // comment + + 'text'; +>'text' : "text" + +var b = 'some' +>b : string +>'some' /* comment */ + 'text' : string +>'some' : "some" + + /* comment */ + + 'text'; +>'text' : "text" + +var c = 'some' +>c : string +>'some' /* comment */ + /*comment1*/ 'text' : string +>'some' : "some" + + /* comment */ + + /*comment1*/ + 'text'; +>'text' : "text" + diff --git a/tests/baselines/reference/commentOnBinaryOperator2.js b/tests/baselines/reference/commentOnBinaryOperator2.js new file mode 100644 index 00000000000..5d87ddccbef --- /dev/null +++ b/tests/baselines/reference/commentOnBinaryOperator2.js @@ -0,0 +1,22 @@ +//// [commentOnBinaryOperator2.ts] +var a = 'some' + // comment + + 'text'; + +var b = 'some' + /* comment */ + + 'text'; + +var c = 'some' + /* comment */ + + /*comment1*/ + 'text'; + +//// [commentOnBinaryOperator2.js] +var a = 'some' + + 'text'; +var b = 'some' + + 'text'; +var c = 'some' + + + 'text'; diff --git a/tests/baselines/reference/commentOnBinaryOperator2.symbols b/tests/baselines/reference/commentOnBinaryOperator2.symbols new file mode 100644 index 00000000000..10a0e94dd36 --- /dev/null +++ b/tests/baselines/reference/commentOnBinaryOperator2.symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/commentOnBinaryOperator2.ts === +var a = 'some' +>a : Symbol(a, Decl(commentOnBinaryOperator2.ts, 0, 3)) + + // comment + + 'text'; + +var b = 'some' +>b : Symbol(b, Decl(commentOnBinaryOperator2.ts, 4, 3)) + + /* comment */ + + 'text'; + +var c = 'some' +>c : Symbol(c, Decl(commentOnBinaryOperator2.ts, 8, 3)) + + /* comment */ + + /*comment1*/ + 'text'; diff --git a/tests/baselines/reference/commentOnBinaryOperator2.types b/tests/baselines/reference/commentOnBinaryOperator2.types new file mode 100644 index 00000000000..411c8c69b8a --- /dev/null +++ b/tests/baselines/reference/commentOnBinaryOperator2.types @@ -0,0 +1,29 @@ +=== tests/cases/compiler/commentOnBinaryOperator2.ts === +var a = 'some' +>a : string +>'some' // comment + 'text' : string +>'some' : "some" + + // comment + + 'text'; +>'text' : "text" + +var b = 'some' +>b : string +>'some' /* comment */ + 'text' : string +>'some' : "some" + + /* comment */ + + 'text'; +>'text' : "text" + +var c = 'some' +>c : string +>'some' /* comment */ + /*comment1*/ 'text' : string +>'some' : "some" + + /* comment */ + + /*comment1*/ + 'text'; +>'text' : "text" + diff --git a/tests/baselines/reference/commentsArgumentsOfCallExpression2.js b/tests/baselines/reference/commentsArgumentsOfCallExpression2.js index a7065410ff4..e05256b86b6 100644 --- a/tests/baselines/reference/commentsArgumentsOfCallExpression2.js +++ b/tests/baselines/reference/commentsArgumentsOfCallExpression2.js @@ -14,7 +14,7 @@ foo( function foo(/*c1*/ x, /*d1*/ y, /*e1*/ w) { } var a, b; foo(/*c2*/ 1, /*d2*/ 1 + 2, /*e1*/ a + b); -foo(/*c3*/ function () { }, /*d2*/ function () { }, /*e2*/ a + b); +foo(/*c3*/ function () { }, /*d2*/ function () { }, /*e2*/ a +/*e3*/ b); foo(/*c3*/ function () { }, /*d3*/ function () { }, /*e3*/ (a + b)); foo( /*c4*/ function () { }, diff --git a/tests/baselines/reference/parser15.4.4.14-9-2.js b/tests/baselines/reference/parser15.4.4.14-9-2.js index 0f533cd9a26..e24da870d3e 100644 --- a/tests/baselines/reference/parser15.4.4.14-9-2.js +++ b/tests/baselines/reference/parser15.4.4.14-9-2.js @@ -41,9 +41,9 @@ function testcase() { var one = 1; var _float = -(4 / 3); var a = new Array(false, undefined, null, "0", obj, -1.3333333333333, "str", -0, true, +0, one, 1, 0, false, _float, -(4 / 3)); - if (a.indexOf(-(4 / 3)) === 14 && - a.indexOf(0) === 7 && - a.indexOf(-0) === 7 && + if (a.indexOf(-(4 / 3)) === 14 &&// a[14]=_float===-(4/3) + a.indexOf(0) === 7 &&// a[7] = +0, 0===+0 + a.indexOf(-0) === 7 &&// a[7] = +0, -0===+0 a.indexOf(1) === 10) { return true; } diff --git a/tests/baselines/reference/parserGreaterThanTokenAmbiguity10.js b/tests/baselines/reference/parserGreaterThanTokenAmbiguity10.js index 862722b1a73..7ff9e380dcf 100644 --- a/tests/baselines/reference/parserGreaterThanTokenAmbiguity10.js +++ b/tests/baselines/reference/parserGreaterThanTokenAmbiguity10.js @@ -6,5 +6,6 @@ //// [parserGreaterThanTokenAmbiguity10.js] 1 - >>> + // before + >>>// after 2; diff --git a/tests/baselines/reference/parserGreaterThanTokenAmbiguity15.js b/tests/baselines/reference/parserGreaterThanTokenAmbiguity15.js index b6f905e12e7..03e6211ae15 100644 --- a/tests/baselines/reference/parserGreaterThanTokenAmbiguity15.js +++ b/tests/baselines/reference/parserGreaterThanTokenAmbiguity15.js @@ -6,5 +6,6 @@ //// [parserGreaterThanTokenAmbiguity15.js] 1 - >>= + // before + >>=// after 2; diff --git a/tests/baselines/reference/parserGreaterThanTokenAmbiguity20.js b/tests/baselines/reference/parserGreaterThanTokenAmbiguity20.js index 01d1d6401f2..ba5e380043d 100644 --- a/tests/baselines/reference/parserGreaterThanTokenAmbiguity20.js +++ b/tests/baselines/reference/parserGreaterThanTokenAmbiguity20.js @@ -6,5 +6,6 @@ //// [parserGreaterThanTokenAmbiguity20.js] 1 - >>>= + // Before + >>>=// after 2; diff --git a/tests/baselines/reference/parserGreaterThanTokenAmbiguity5.js b/tests/baselines/reference/parserGreaterThanTokenAmbiguity5.js index c65b76f504a..e240746caa4 100644 --- a/tests/baselines/reference/parserGreaterThanTokenAmbiguity5.js +++ b/tests/baselines/reference/parserGreaterThanTokenAmbiguity5.js @@ -6,5 +6,6 @@ //// [parserGreaterThanTokenAmbiguity5.js] 1 - >> + // before + >>// after 2; diff --git a/tests/baselines/reference/typeGuardsInConditionalExpression.js b/tests/baselines/reference/typeGuardsInConditionalExpression.js index 9aade91612a..8be83a887b1 100644 --- a/tests/baselines/reference/typeGuardsInConditionalExpression.js +++ b/tests/baselines/reference/typeGuardsInConditionalExpression.js @@ -138,7 +138,7 @@ function foo8(x) { var b; return typeof x === "string" ? x === "hello" - : ((b = x) && + : ((b = x) &&// number | boolean (typeof x === "boolean" ? x // boolean : x == 10)); // boolean diff --git a/tests/cases/compiler/commentOnBinaryOperator1.ts b/tests/cases/compiler/commentOnBinaryOperator1.ts new file mode 100644 index 00000000000..29de3410c32 --- /dev/null +++ b/tests/cases/compiler/commentOnBinaryOperator1.ts @@ -0,0 +1,12 @@ +var a = 'some' + // comment + + 'text'; + +var b = 'some' + /* comment */ + + 'text'; + +var c = 'some' + /* comment */ + + /*comment1*/ + 'text'; \ No newline at end of file diff --git a/tests/cases/compiler/commentOnBinaryOperator2.ts b/tests/cases/compiler/commentOnBinaryOperator2.ts new file mode 100644 index 00000000000..023655e16c0 --- /dev/null +++ b/tests/cases/compiler/commentOnBinaryOperator2.ts @@ -0,0 +1,13 @@ +// @removeComments: true +var a = 'some' + // comment + + 'text'; + +var b = 'some' + /* comment */ + + 'text'; + +var c = 'some' + /* comment */ + + /*comment1*/ + 'text'; \ No newline at end of file From 48d5485379add6e2f80edfc5ed81fa0d01d5680d Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 4 Aug 2017 20:01:19 -0700 Subject: [PATCH 26/50] Accept JSDoc cast comment baseline --- tests/baselines/reference/jsdocTypeTagCast.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/baselines/reference/jsdocTypeTagCast.js b/tests/baselines/reference/jsdocTypeTagCast.js index c2df50cd6dd..ffe59d0138d 100644 --- a/tests/baselines/reference/jsdocTypeTagCast.js +++ b/tests/baselines/reference/jsdocTypeTagCast.js @@ -97,7 +97,7 @@ var a; /** @type {string} */ var s; var a = ("" + 4); -var s = "" + (4); +var s = "" +/** @type {*} */ (4); var SomeBase = (function () { function SomeBase() { this.p = 42; @@ -128,19 +128,19 @@ var someBase = new SomeBase(); var someDerived = new SomeDerived(); var someOther = new SomeOther(); var someFakeClass = new SomeFakeClass(); -someBase = (someDerived); -someBase = (someBase); -someBase = (someOther); // Error -someDerived = (someDerived); -someDerived = (someBase); -someDerived = (someOther); // Error -someOther = (someDerived); // Error -someOther = (someBase); // Error -someOther = (someOther); +someBase =/** @type {SomeBase} */ (someDerived); +someBase =/** @type {SomeBase} */ (someBase); +someBase =/** @type {SomeBase} */ (someOther); // Error +someDerived =/** @type {SomeDerived} */ (someDerived); +someDerived =/** @type {SomeDerived} */ (someBase); +someDerived =/** @type {SomeDerived} */ (someOther); // Error +someOther =/** @type {SomeOther} */ (someDerived); // Error +someOther =/** @type {SomeOther} */ (someBase); // Error +someOther =/** @type {SomeOther} */ (someOther); someFakeClass = someBase; someFakeClass = someDerived; someBase = someFakeClass; // Error -someBase = (someFakeClass); +someBase =/** @type {SomeBase} */ (someFakeClass); // Type assertion cannot be a type-predicate type /** @type {number | string} */ var numOrStr; From 44a6c6cc6ff0f3c9bfedb9e0a69d68232c10fdcf Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Aug 2017 10:09:44 -0700 Subject: [PATCH 27/50] { [P in K]: T } is related to { [x: string]: U } if T is related to U --- src/compiler/checker.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7913520bfdb..2cbaf0e1992 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9614,6 +9614,11 @@ namespace ts { if (sourceInfo) { return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors); } + if (isGenericMappedType(source)) { + // A generic mapped type { [P in K]: T } is related to an index signature { [x: string]: U } + // if T is related to U. + return kind === IndexKind.String && isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, reportErrors); + } if (isObjectLiteralType(source)) { let related = Ternary.True; if (kind === IndexKind.String) { From c938a2acdc4f7cfe19b628b074faa9b4e07ae736 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Aug 2017 10:17:20 -0700 Subject: [PATCH 28/50] Add tests --- .../indexSignatureAndMappedType.errors.txt | 47 ++++++++++++ .../reference/indexSignatureAndMappedType.js | 73 +++++++++++++++++++ .../compiler/indexSignatureAndMappedType.ts | 35 +++++++++ 3 files changed, 155 insertions(+) create mode 100644 tests/baselines/reference/indexSignatureAndMappedType.errors.txt create mode 100644 tests/baselines/reference/indexSignatureAndMappedType.js create mode 100644 tests/cases/compiler/indexSignatureAndMappedType.ts diff --git a/tests/baselines/reference/indexSignatureAndMappedType.errors.txt b/tests/baselines/reference/indexSignatureAndMappedType.errors.txt new file mode 100644 index 00000000000..c8ae0494015 --- /dev/null +++ b/tests/baselines/reference/indexSignatureAndMappedType.errors.txt @@ -0,0 +1,47 @@ +tests/cases/compiler/indexSignatureAndMappedType.ts(6,5): error TS2322: Type '{ [key: string]: T; }' is not assignable to type 'Record'. +tests/cases/compiler/indexSignatureAndMappedType.ts(15,5): error TS2322: Type 'Record' is not assignable to type '{ [key: string]: T; }'. + Type 'U' is not assignable to type 'T'. +tests/cases/compiler/indexSignatureAndMappedType.ts(16,5): error TS2322: Type '{ [key: string]: T; }' is not assignable to type 'Record'. + + +==== tests/cases/compiler/indexSignatureAndMappedType.ts (3 errors) ==== + // A mapped type { [P in K]: X }, where K is a generic type, is related to + // { [key: string]: Y } if X is related to Y. + + function f1(x: { [key: string]: T }, y: Record) { + x = y; + y = x; // Error + ~ +!!! error TS2322: Type '{ [key: string]: T; }' is not assignable to type 'Record'. + } + + function f2(x: { [key: string]: T }, y: Record) { + x = y; + y = x; + } + + function f3(x: { [key: string]: T }, y: Record) { + x = y; // Error + ~ +!!! error TS2322: Type 'Record' is not assignable to type '{ [key: string]: T; }'. +!!! error TS2322: Type 'U' is not assignable to type 'T'. + y = x; // Error + ~ +!!! error TS2322: Type '{ [key: string]: T; }' is not assignable to type 'Record'. + } + + // Repro from #14548 + + type Dictionary = { + [key: string]: string; + }; + + interface IBaseEntity { + name: string; + properties: Dictionary; + } + + interface IEntity extends IBaseEntity { + properties: Record; + } + \ No newline at end of file diff --git a/tests/baselines/reference/indexSignatureAndMappedType.js b/tests/baselines/reference/indexSignatureAndMappedType.js new file mode 100644 index 00000000000..c35da4f4931 --- /dev/null +++ b/tests/baselines/reference/indexSignatureAndMappedType.js @@ -0,0 +1,73 @@ +//// [indexSignatureAndMappedType.ts] +// A mapped type { [P in K]: X }, where K is a generic type, is related to +// { [key: string]: Y } if X is related to Y. + +function f1(x: { [key: string]: T }, y: Record) { + x = y; + y = x; // Error +} + +function f2(x: { [key: string]: T }, y: Record) { + x = y; + y = x; +} + +function f3(x: { [key: string]: T }, y: Record) { + x = y; // Error + y = x; // Error +} + +// Repro from #14548 + +type Dictionary = { + [key: string]: string; +}; + +interface IBaseEntity { + name: string; + properties: Dictionary; +} + +interface IEntity extends IBaseEntity { + properties: Record; +} + + +//// [indexSignatureAndMappedType.js] +"use strict"; +// A mapped type { [P in K]: X }, where K is a generic type, is related to +// { [key: string]: Y } if X is related to Y. +function f1(x, y) { + x = y; + y = x; // Error +} +function f2(x, y) { + x = y; + y = x; +} +function f3(x, y) { + x = y; // Error + y = x; // Error +} + + +//// [indexSignatureAndMappedType.d.ts] +declare function f1(x: { + [key: string]: T; +}, y: Record): void; +declare function f2(x: { + [key: string]: T; +}, y: Record): void; +declare function f3(x: { + [key: string]: T; +}, y: Record): void; +declare type Dictionary = { + [key: string]: string; +}; +interface IBaseEntity { + name: string; + properties: Dictionary; +} +interface IEntity extends IBaseEntity { + properties: Record; +} diff --git a/tests/cases/compiler/indexSignatureAndMappedType.ts b/tests/cases/compiler/indexSignatureAndMappedType.ts new file mode 100644 index 00000000000..1070472a241 --- /dev/null +++ b/tests/cases/compiler/indexSignatureAndMappedType.ts @@ -0,0 +1,35 @@ +// @strict: true +// @declaration: true + +// A mapped type { [P in K]: X }, where K is a generic type, is related to +// { [key: string]: Y } if X is related to Y. + +function f1(x: { [key: string]: T }, y: Record) { + x = y; + y = x; // Error +} + +function f2(x: { [key: string]: T }, y: Record) { + x = y; + y = x; +} + +function f3(x: { [key: string]: T }, y: Record) { + x = y; // Error + y = x; // Error +} + +// Repro from #14548 + +type Dictionary = { + [key: string]: string; +}; + +interface IBaseEntity { + name: string; + properties: Dictionary; +} + +interface IEntity extends IBaseEntity { + properties: Record; +} From d0a195a3c5c6718f393071cc9a827905ac7a2f4c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Aug 2017 12:32:56 -0700 Subject: [PATCH 29/50] Propagate type comparer function in contextual signature instantiation --- src/compiler/checker.ts | 15 ++++++++------- src/compiler/core.ts | 14 -------------- src/compiler/types.ts | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1c0f15e27aa..f31c2573e36 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8017,7 +8017,7 @@ namespace ts { function cloneTypeMapper(mapper: TypeMapper): TypeMapper { return mapper && isInferenceContext(mapper) ? - createInferenceContext(mapper.signature, mapper.flags | InferenceFlags.NoDefault, mapper.inferences) : + createInferenceContext(mapper.signature, mapper.flags | InferenceFlags.NoDefault, mapper.compareTypes, mapper.inferences) : mapper; } @@ -8458,7 +8458,7 @@ namespace ts { ignoreReturnTypes: boolean, reportErrors: boolean, errorReporter: ErrorReporter, - compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary): Ternary { + compareTypes: TypeComparer): Ternary { // TODO (drosen): De-duplicate code between related functions. if (source === target) { return Ternary.True; @@ -8468,7 +8468,7 @@ namespace ts { } if (source.typeParameters) { - source = instantiateSignatureInContextOf(source, target); + source = instantiateSignatureInContextOf(source, target, /*contextualMapper*/ undefined, compareTypes); } let result = Ternary.True; @@ -10216,13 +10216,14 @@ namespace ts { } } - function createInferenceContext(signature: Signature, flags: InferenceFlags, baseInferences?: InferenceInfo[]): InferenceContext { + function createInferenceContext(signature: Signature, flags: InferenceFlags, compareTypes?: TypeComparer, baseInferences?: InferenceInfo[]): InferenceContext { const inferences = baseInferences ? map(baseInferences, cloneInferenceInfo) : map(signature.typeParameters, createInferenceInfo); const context = mapper as InferenceContext; context.mappedTypes = signature.typeParameters; context.signature = signature; context.inferences = inferences; context.flags = flags; + context.compareTypes = compareTypes || compareTypesAssignable; return context; function mapper(t: Type): Type { @@ -10670,7 +10671,7 @@ namespace ts { const constraint = getConstraintOfTypeParameter(context.signature.typeParameters[index]); if (constraint) { const instantiatedConstraint = instantiateType(constraint, context); - if (!isTypeAssignableTo(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { + if (!context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { inference.inferredType = inferredType = instantiatedConstraint; } } @@ -15071,8 +15072,8 @@ namespace ts { } // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) - function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper?: TypeMapper): Signature { - const context = createInferenceContext(signature, InferenceFlags.InferUnionTypes); + function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper?: TypeMapper, compareTypes?: TypeComparer): Signature { + const context = createInferenceContext(signature, InferenceFlags.InferUnionTypes, compareTypes); forEachMatchingParameterType(contextualSignature, signature, (source, target) => { // Type parameters from outer context referenced by source type are fixed by instantiation of the source type inferTypes(context.inferences, instantiateType(source, contextualMapper || identityMapper), target); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 728fb433c04..262564813e9 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -11,20 +11,6 @@ namespace ts { /* @internal */ namespace ts { - /** - * Ternary values are defined such that - * x & y is False if either x or y is False. - * x & y is Maybe if either x or y is Maybe, but neither x or y is False. - * x & y is True if both x and y are True. - * x | y is False if both x and y are False. - * x | y is Maybe if either x or y is Maybe, but neither x or y is True. - * x | y is True if either x or y is True. - */ - export const enum Ternary { - False = 0, - Maybe = 1, - True = -1 - } // More efficient to create a collator once and use its `compare` than to call `a.localeCompare(b)` many times. export const collator: { compare(a: string, b: string): number } = typeof Intl === "object" && typeof Intl.Collator === "function" ? new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "accent" }) : undefined; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 09f1b5cfcc5..3b57608abc5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3425,11 +3425,29 @@ namespace ts { AnyDefault = 1 << 2, // Infer anyType for no inferences (otherwise emptyObjectType) } + /** + * Ternary values are defined such that + * x & y is False if either x or y is False. + * x & y is Maybe if either x or y is Maybe, but neither x or y is False. + * x & y is True if both x and y are True. + * x | y is False if both x and y are False. + * x | y is Maybe if either x or y is Maybe, but neither x or y is True. + * x | y is True if either x or y is True. + */ + export const enum Ternary { + False = 0, + Maybe = 1, + True = -1 + } + + export type TypeComparer = (s: Type, t: Type, reportErrors?: boolean) => Ternary; + /* @internal */ export interface InferenceContext extends TypeMapper { signature: Signature; // Generic signature for which inferences are made inferences: InferenceInfo[]; // Inferences made for each type parameter flags: InferenceFlags; // Inference flags + compareTypes: TypeComparer; // Type comparer function } /* @internal */ From a4a37ea086abdad84c0a7aa882832c288ea7d59b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Aug 2017 12:40:40 -0700 Subject: [PATCH 30/50] Add regression test --- ...reInstantiationWithRecursiveConstraints.js | 30 ++++++++++++++++++ ...tantiationWithRecursiveConstraints.symbols | 30 ++++++++++++++++++ ...nstantiationWithRecursiveConstraints.types | 31 +++++++++++++++++++ ...reInstantiationWithRecursiveConstraints.ts | 13 ++++++++ 4 files changed, 104 insertions(+) create mode 100644 tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.js create mode 100644 tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.symbols create mode 100644 tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.types create mode 100644 tests/cases/compiler/signatureInstantiationWithRecursiveConstraints.ts diff --git a/tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.js b/tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.js new file mode 100644 index 00000000000..0be7fa1444d --- /dev/null +++ b/tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.js @@ -0,0 +1,30 @@ +//// [signatureInstantiationWithRecursiveConstraints.ts] +// Repro from #17148 + +class Foo { + myFunc(arg: T) {} +} + +class Bar { + myFunc(arg: T) {} +} + +const myVar: Foo = new Bar(); + + +//// [signatureInstantiationWithRecursiveConstraints.js] +"use strict"; +// Repro from #17148 +var Foo = (function () { + function Foo() { + } + Foo.prototype.myFunc = function (arg) { }; + return Foo; +}()); +var Bar = (function () { + function Bar() { + } + Bar.prototype.myFunc = function (arg) { }; + return Bar; +}()); +var myVar = new Bar(); diff --git a/tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.symbols b/tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.symbols new file mode 100644 index 00000000000..ebc1b625d9e --- /dev/null +++ b/tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.symbols @@ -0,0 +1,30 @@ +=== tests/cases/compiler/signatureInstantiationWithRecursiveConstraints.ts === +// Repro from #17148 + +class Foo { +>Foo : Symbol(Foo, Decl(signatureInstantiationWithRecursiveConstraints.ts, 0, 0)) + + myFunc(arg: T) {} +>myFunc : Symbol(Foo.myFunc, Decl(signatureInstantiationWithRecursiveConstraints.ts, 2, 11)) +>T : Symbol(T, Decl(signatureInstantiationWithRecursiveConstraints.ts, 3, 9)) +>Foo : Symbol(Foo, Decl(signatureInstantiationWithRecursiveConstraints.ts, 0, 0)) +>arg : Symbol(arg, Decl(signatureInstantiationWithRecursiveConstraints.ts, 3, 24)) +>T : Symbol(T, Decl(signatureInstantiationWithRecursiveConstraints.ts, 3, 9)) +} + +class Bar { +>Bar : Symbol(Bar, Decl(signatureInstantiationWithRecursiveConstraints.ts, 4, 1)) + + myFunc(arg: T) {} +>myFunc : Symbol(Bar.myFunc, Decl(signatureInstantiationWithRecursiveConstraints.ts, 6, 11)) +>T : Symbol(T, Decl(signatureInstantiationWithRecursiveConstraints.ts, 7, 9)) +>Bar : Symbol(Bar, Decl(signatureInstantiationWithRecursiveConstraints.ts, 4, 1)) +>arg : Symbol(arg, Decl(signatureInstantiationWithRecursiveConstraints.ts, 7, 24)) +>T : Symbol(T, Decl(signatureInstantiationWithRecursiveConstraints.ts, 7, 9)) +} + +const myVar: Foo = new Bar(); +>myVar : Symbol(myVar, Decl(signatureInstantiationWithRecursiveConstraints.ts, 10, 5)) +>Foo : Symbol(Foo, Decl(signatureInstantiationWithRecursiveConstraints.ts, 0, 0)) +>Bar : Symbol(Bar, Decl(signatureInstantiationWithRecursiveConstraints.ts, 4, 1)) + diff --git a/tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.types b/tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.types new file mode 100644 index 00000000000..2368835be08 --- /dev/null +++ b/tests/baselines/reference/signatureInstantiationWithRecursiveConstraints.types @@ -0,0 +1,31 @@ +=== tests/cases/compiler/signatureInstantiationWithRecursiveConstraints.ts === +// Repro from #17148 + +class Foo { +>Foo : Foo + + myFunc(arg: T) {} +>myFunc : (arg: T) => void +>T : T +>Foo : Foo +>arg : T +>T : T +} + +class Bar { +>Bar : Bar + + myFunc(arg: T) {} +>myFunc : (arg: T) => void +>T : T +>Bar : Bar +>arg : T +>T : T +} + +const myVar: Foo = new Bar(); +>myVar : Foo +>Foo : Foo +>new Bar() : Bar +>Bar : typeof Bar + diff --git a/tests/cases/compiler/signatureInstantiationWithRecursiveConstraints.ts b/tests/cases/compiler/signatureInstantiationWithRecursiveConstraints.ts new file mode 100644 index 00000000000..4f25446aad8 --- /dev/null +++ b/tests/cases/compiler/signatureInstantiationWithRecursiveConstraints.ts @@ -0,0 +1,13 @@ +// @strict: true + +// Repro from #17148 + +class Foo { + myFunc(arg: T) {} +} + +class Bar { + myFunc(arg: T) {} +} + +const myVar: Foo = new Bar(); From a453eff575a18a94a22bd3c908a6df0e1c3dc392 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 7 Aug 2017 09:16:12 -0700 Subject: [PATCH 31/50] Restrict parsing of literals and their expressions a _lot_ more (#17628) --- src/compiler/parser.ts | 34 +++++-- .../expressionTypeNodeShouldError.errors.txt | 90 +++++++++++++++++++ .../expressionTypeNodeShouldError.js | 85 ++++++++++++++++++ .../compiler/expressionTypeNodeShouldError.ts | 45 ++++++++++ 4 files changed, 247 insertions(+), 7 deletions(-) create mode 100644 tests/baselines/reference/expressionTypeNodeShouldError.errors.txt create mode 100644 tests/baselines/reference/expressionTypeNodeShouldError.js create mode 100644 tests/cases/compiler/expressionTypeNodeShouldError.ts diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index a7d749ee206..49b682c7302 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2061,7 +2061,7 @@ namespace ts { return fragment; } - function parseLiteralLikeNode(kind: SyntaxKind): LiteralLikeNode { + function parseLiteralLikeNode(kind: SyntaxKind): LiteralExpression | LiteralLikeNode { const node = createNode(kind); const text = scanner.getTokenValue(); node.text = text; @@ -2611,11 +2611,31 @@ namespace ts { return token() === SyntaxKind.DotToken ? undefined : node; } - function parseLiteralTypeNode(): LiteralTypeNode { - const node = createNode(SyntaxKind.LiteralType); - node.literal = parseSimpleUnaryExpression(); - finishNode(node); - return node; + function parseLiteralTypeNode(negative?: boolean): LiteralTypeNode { + const node = createNode(SyntaxKind.LiteralType) as LiteralTypeNode; + let unaryMinusExpression: PrefixUnaryExpression; + if (negative) { + unaryMinusExpression = createNode(SyntaxKind.PrefixUnaryExpression) as PrefixUnaryExpression; + unaryMinusExpression.operator = SyntaxKind.MinusToken; + nextToken(); + } + let expression: UnaryExpression; + switch (token()) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + expression = parseLiteralLikeNode(token()) as LiteralExpression; + break; + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + expression = parseTokenNode(); + } + if (negative) { + unaryMinusExpression.operand = expression; + finishNode(unaryMinusExpression); + expression = unaryMinusExpression; + } + node.literal = expression; + return finishNode(node); } function nextTokenIsNumericLiteral() { @@ -2650,7 +2670,7 @@ namespace ts { case SyntaxKind.FalseKeyword: return parseLiteralTypeNode(); case SyntaxKind.MinusToken: - return lookAhead(nextTokenIsNumericLiteral) ? parseLiteralTypeNode() : parseTypeReference(); + return lookAhead(nextTokenIsNumericLiteral) ? parseLiteralTypeNode(/*negative*/ true) : parseTypeReference(); case SyntaxKind.VoidKeyword: case SyntaxKind.NullKeyword: return parseTokenNode(); diff --git a/tests/baselines/reference/expressionTypeNodeShouldError.errors.txt b/tests/baselines/reference/expressionTypeNodeShouldError.errors.txt new file mode 100644 index 00000000000..86a592779ca --- /dev/null +++ b/tests/baselines/reference/expressionTypeNodeShouldError.errors.txt @@ -0,0 +1,90 @@ +tests/cases/compiler/base.d.ts(1,23): error TS1005: ',' expected. +tests/cases/compiler/base.d.ts(1,34): error TS1005: '=' expected. +tests/cases/compiler/boolean.ts(7,23): error TS1005: ',' expected. +tests/cases/compiler/boolean.ts(7,24): error TS1134: Variable declaration expected. +tests/cases/compiler/boolean.ts(11,16): error TS2304: Cannot find name 'document'. +tests/cases/compiler/boolean.ts(12,22): error TS1005: ';' expected. +tests/cases/compiler/number.ts(7,26): error TS1005: ',' expected. +tests/cases/compiler/number.ts(7,27): error TS1134: Variable declaration expected. +tests/cases/compiler/number.ts(11,16): error TS2304: Cannot find name 'document'. +tests/cases/compiler/number.ts(12,20): error TS1005: ';' expected. +tests/cases/compiler/string.ts(7,20): error TS1005: ',' expected. +tests/cases/compiler/string.ts(7,21): error TS1134: Variable declaration expected. +tests/cases/compiler/string.ts(11,15): error TS2304: Cannot find name 'document'. +tests/cases/compiler/string.ts(12,19): error TS1005: ';' expected. + + +==== tests/cases/compiler/base.d.ts (2 errors) ==== + declare const x: "foo".charCodeAt(0); + ~ +!!! error TS1005: ',' expected. + ~ +!!! error TS1005: '=' expected. + +==== tests/cases/compiler/string.ts (4 errors) ==== + interface String { + typeof(x: T): T; + } + + class C { + foo() { + const x: "".typeof(this.foo); + ~ +!!! error TS1005: ',' expected. + ~~~~~~ +!!! error TS1134: Variable declaration expected. + } + } + + const nodes = document.getElementsByTagName("li"); + ~~~~~~~~ +!!! error TS2304: Cannot find name 'document'. + type ItemType = "".typeof(nodes.item(0)); + ~ +!!! error TS1005: ';' expected. + +==== tests/cases/compiler/number.ts (4 errors) ==== + interface Number { + typeof(x: T): T; + } + + class C2 { + foo() { + const x: 3.141592.typeof(this.foo); + ~ +!!! error TS1005: ',' expected. + ~~~~~~ +!!! error TS1134: Variable declaration expected. + } + } + + const nodes2 = document.getElementsByTagName("li"); + ~~~~~~~~ +!!! error TS2304: Cannot find name 'document'. + type ItemType2 = 4..typeof(nodes.item(0)); + ~ +!!! error TS1005: ';' expected. + +==== tests/cases/compiler/boolean.ts (4 errors) ==== + interface Boolean { + typeof(x: T): T; + } + + class C3 { + foo() { + const x: false.typeof(this.foo); + ~ +!!! error TS1005: ',' expected. + ~~~~~~ +!!! error TS1134: Variable declaration expected. + } + } + + const nodes3 = document.getElementsByTagName("li"); + ~~~~~~~~ +!!! error TS2304: Cannot find name 'document'. + type ItemType3 = true.typeof(nodes.item(0)); + ~ +!!! error TS1005: ';' expected. + + \ No newline at end of file diff --git a/tests/baselines/reference/expressionTypeNodeShouldError.js b/tests/baselines/reference/expressionTypeNodeShouldError.js new file mode 100644 index 00000000000..80f9f82145a --- /dev/null +++ b/tests/baselines/reference/expressionTypeNodeShouldError.js @@ -0,0 +1,85 @@ +//// [tests/cases/compiler/expressionTypeNodeShouldError.ts] //// + +//// [base.d.ts] +declare const x: "foo".charCodeAt(0); + +//// [string.ts] +interface String { + typeof(x: T): T; +} + +class C { + foo() { + const x: "".typeof(this.foo); + } +} + +const nodes = document.getElementsByTagName("li"); +type ItemType = "".typeof(nodes.item(0)); + +//// [number.ts] +interface Number { + typeof(x: T): T; +} + +class C2 { + foo() { + const x: 3.141592.typeof(this.foo); + } +} + +const nodes2 = document.getElementsByTagName("li"); +type ItemType2 = 4..typeof(nodes.item(0)); + +//// [boolean.ts] +interface Boolean { + typeof(x: T): T; +} + +class C3 { + foo() { + const x: false.typeof(this.foo); + } +} + +const nodes3 = document.getElementsByTagName("li"); +type ItemType3 = true.typeof(nodes.item(0)); + + + +//// [string.js] +var C = (function () { + function C() { + } + C.prototype.foo = function () { + var x; + typeof (this.foo); + }; + return C; +}()); +var nodes = document.getElementsByTagName("li"); +typeof (nodes.item(0)); +//// [number.js] +var C2 = (function () { + function C2() { + } + C2.prototype.foo = function () { + var x; + typeof (this.foo); + }; + return C2; +}()); +var nodes2 = document.getElementsByTagName("li"); +typeof (nodes.item(0)); +//// [boolean.js] +var C3 = (function () { + function C3() { + } + C3.prototype.foo = function () { + var x; + typeof (this.foo); + }; + return C3; +}()); +var nodes3 = document.getElementsByTagName("li"); +typeof (nodes.item(0)); diff --git a/tests/cases/compiler/expressionTypeNodeShouldError.ts b/tests/cases/compiler/expressionTypeNodeShouldError.ts new file mode 100644 index 00000000000..0f6463f454b --- /dev/null +++ b/tests/cases/compiler/expressionTypeNodeShouldError.ts @@ -0,0 +1,45 @@ +// @Filename: base.d.ts +declare const x: "foo".charCodeAt(0); + +// @filename: string.ts +interface String { + typeof(x: T): T; +} + +class C { + foo() { + const x: "".typeof(this.foo); + } +} + +const nodes = document.getElementsByTagName("li"); +type ItemType = "".typeof(nodes.item(0)); + +// @filename: number.ts +interface Number { + typeof(x: T): T; +} + +class C2 { + foo() { + const x: 3.141592.typeof(this.foo); + } +} + +const nodes2 = document.getElementsByTagName("li"); +type ItemType2 = 4..typeof(nodes.item(0)); + +// @filename: boolean.ts +interface Boolean { + typeof(x: T): T; +} + +class C3 { + foo() { + const x: false.typeof(this.foo); + } +} + +const nodes3 = document.getElementsByTagName("li"); +type ItemType3 = true.typeof(nodes.item(0)); + From a282cbb07e18f0ef1bfd21633f8b461fa961e42f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 7 Aug 2017 10:56:18 -0700 Subject: [PATCH 32/50] Weak type errors for signature-only types too Now source types that only have a call signature (like functions) or construct signature will get a weak type error too. This is really good for catching uncalled functions: ```ts functionTakingWeakType(returnWeakType); // OOPS. Forgot to call `returnWeakType()`. That's an error! ``` --- src/compiler/checker.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a8af11e7803..990bfd546a3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8938,7 +8938,9 @@ namespace ts { !(target.flags & TypeFlags.Union) && !isIntersectionConstituent && source !== globalObjectType && - getPropertiesOfType(source).length > 0 && + (getPropertiesOfType(source).length > 0 || + getSignaturesOfType(source, SignatureKind.Call).length > 0 || + getSignaturesOfType(source, SignatureKind.Construct).length > 0) && isWeakType(target) && !hasCommonProperties(source, target)) { if (reportErrors) { From 068cb8d5d05e5f46decd3e205b2b09d7455369da Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 7 Aug 2017 10:58:07 -0700 Subject: [PATCH 33/50] Update weakType test + baselines --- tests/baselines/reference/weakType.errors.txt | 28 +++++++++++++------ tests/baselines/reference/weakType.js | 10 ++++--- tests/cases/compiler/weakType.ts | 7 +++-- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/tests/baselines/reference/weakType.errors.txt b/tests/baselines/reference/weakType.errors.txt index 441ef70ac16..b08dcc33980 100644 --- a/tests/baselines/reference/weakType.errors.txt +++ b/tests/baselines/reference/weakType.errors.txt @@ -1,14 +1,17 @@ -tests/cases/compiler/weakType.ts(16,13): error TS2559: Type '12' has no properties in common with type 'Settings'. -tests/cases/compiler/weakType.ts(17,13): error TS2559: Type '"completely wrong"' has no properties in common with type 'Settings'. -tests/cases/compiler/weakType.ts(18,13): error TS2559: Type 'false' has no properties in common with type 'Settings'. -tests/cases/compiler/weakType.ts(35,18): error TS2559: Type '{ error?: number; }' has no properties in common with type 'ChangeOptions'. -tests/cases/compiler/weakType.ts(60,5): error TS2322: Type '{ properties: { wrong: string; }; }' is not assignable to type 'Weak & Spoiler'. +tests/cases/compiler/weakType.ts(15,13): error TS2559: Type '() => { timeout: number; }' has no properties in common with type 'Settings'. +tests/cases/compiler/weakType.ts(16,13): error TS2559: Type '() => void' has no properties in common with type 'Settings'. +tests/cases/compiler/weakType.ts(17,13): error TS2559: Type 'CtorOnly' has no properties in common with type 'Settings'. +tests/cases/compiler/weakType.ts(18,13): error TS2559: Type '12' has no properties in common with type 'Settings'. +tests/cases/compiler/weakType.ts(19,13): error TS2559: Type '"completely wrong"' has no properties in common with type 'Settings'. +tests/cases/compiler/weakType.ts(20,13): error TS2559: Type 'false' has no properties in common with type 'Settings'. +tests/cases/compiler/weakType.ts(37,18): error TS2559: Type '{ error?: number; }' has no properties in common with type 'ChangeOptions'. +tests/cases/compiler/weakType.ts(62,5): error TS2322: Type '{ properties: { wrong: string; }; }' is not assignable to type 'Weak & Spoiler'. Type '{ properties: { wrong: string; }; }' is not assignable to type 'Weak'. Types of property 'properties' are incompatible. Type '{ wrong: string; }' has no properties in common with type '{ b?: number; }'. -==== tests/cases/compiler/weakType.ts (5 errors) ==== +==== tests/cases/compiler/weakType.ts (8 errors) ==== interface Settings { timeout?: number; onError?(): void; @@ -17,13 +20,21 @@ tests/cases/compiler/weakType.ts(60,5): error TS2322: Type '{ properties: { wron function getDefaultSettings() { return { timeout: 1000 }; } + interface CtorOnly { + new(s: string): string + } function doSomething(settings: Settings) { /* ... */ } // forgot to call `getDefaultSettings` - // but it is not caught because we don't check for call signatures doSomething(getDefaultSettings); - // same for arrow expressions: + ~~~~~~~~~~~~~~~~~~ +!!! error TS2559: Type '() => { timeout: number; }' has no properties in common with type 'Settings'. doSomething(() => { }); + ~~~~~~~~~ +!!! error TS2559: Type '() => void' has no properties in common with type 'Settings'. + doSomething(null as CtorOnly); + ~~~~~~~~~~~~~~~~ +!!! error TS2559: Type 'CtorOnly' has no properties in common with type 'Settings'. doSomething(12); ~~ !!! error TS2559: Type '12' has no properties in common with type 'Settings'. @@ -82,4 +93,5 @@ tests/cases/compiler/weakType.ts(60,5): error TS2322: Type '{ properties: { wron !!! error TS2322: Type '{ properties: { wrong: string; }; }' is not assignable to type 'Weak'. !!! error TS2322: Types of property 'properties' are incompatible. !!! error TS2322: Type '{ wrong: string; }' has no properties in common with type '{ b?: number; }'. + \ No newline at end of file diff --git a/tests/baselines/reference/weakType.js b/tests/baselines/reference/weakType.js index 5637271ccec..999269384dc 100644 --- a/tests/baselines/reference/weakType.js +++ b/tests/baselines/reference/weakType.js @@ -7,13 +7,15 @@ interface Settings { function getDefaultSettings() { return { timeout: 1000 }; } +interface CtorOnly { + new(s: string): string +} function doSomething(settings: Settings) { /* ... */ } // forgot to call `getDefaultSettings` -// but it is not caught because we don't check for call signatures doSomething(getDefaultSettings); -// same for arrow expressions: doSomething(() => { }); +doSomething(null as CtorOnly); doSomething(12); doSomething('completely wrong'); doSomething(false); @@ -59,6 +61,7 @@ declare let unknown: { } } let weak: Weak & Spoiler = unknown + //// [weakType.js] @@ -67,10 +70,9 @@ function getDefaultSettings() { } function doSomething(settings) { } // forgot to call `getDefaultSettings` -// but it is not caught because we don't check for call signatures doSomething(getDefaultSettings); -// same for arrow expressions: doSomething(function () { }); +doSomething(null); doSomething(12); doSomething('completely wrong'); doSomething(false); diff --git a/tests/cases/compiler/weakType.ts b/tests/cases/compiler/weakType.ts index ffe51205e53..8fda5df9166 100644 --- a/tests/cases/compiler/weakType.ts +++ b/tests/cases/compiler/weakType.ts @@ -6,13 +6,15 @@ interface Settings { function getDefaultSettings() { return { timeout: 1000 }; } +interface CtorOnly { + new(s: string): string +} function doSomething(settings: Settings) { /* ... */ } // forgot to call `getDefaultSettings` -// but it is not caught because we don't check for call signatures doSomething(getDefaultSettings); -// same for arrow expressions: doSomething(() => { }); +doSomething(null as CtorOnly); doSomething(12); doSomething('completely wrong'); doSomething(false); @@ -58,3 +60,4 @@ declare let unknown: { } } let weak: Weak & Spoiler = unknown + From 3efeb1e27f60a95dad66c148295bfa5a65f56146 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 7 Aug 2017 13:59:52 -0700 Subject: [PATCH 34/50] Address CR feedback --- .../reference/indexSignatureAndMappedType.errors.txt | 2 +- tests/baselines/reference/indexSignatureAndMappedType.js | 4 ++-- tests/cases/compiler/indexSignatureAndMappedType.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/indexSignatureAndMappedType.errors.txt b/tests/baselines/reference/indexSignatureAndMappedType.errors.txt index c8ae0494015..623fcd11bd0 100644 --- a/tests/baselines/reference/indexSignatureAndMappedType.errors.txt +++ b/tests/baselines/reference/indexSignatureAndMappedType.errors.txt @@ -15,7 +15,7 @@ tests/cases/compiler/indexSignatureAndMappedType.ts(16,5): error TS2322: Type '{ !!! error TS2322: Type '{ [key: string]: T; }' is not assignable to type 'Record'. } - function f2(x: { [key: string]: T }, y: Record) { + function f2(x: { [key: string]: T }, y: Record) { x = y; y = x; } diff --git a/tests/baselines/reference/indexSignatureAndMappedType.js b/tests/baselines/reference/indexSignatureAndMappedType.js index c35da4f4931..be58286fb46 100644 --- a/tests/baselines/reference/indexSignatureAndMappedType.js +++ b/tests/baselines/reference/indexSignatureAndMappedType.js @@ -7,7 +7,7 @@ function f1(x: { [key: string]: T }, y: Record) { y = x; // Error } -function f2(x: { [key: string]: T }, y: Record) { +function f2(x: { [key: string]: T }, y: Record) { x = y; y = x; } @@ -55,7 +55,7 @@ function f3(x, y) { declare function f1(x: { [key: string]: T; }, y: Record): void; -declare function f2(x: { +declare function f2(x: { [key: string]: T; }, y: Record): void; declare function f3(x: { diff --git a/tests/cases/compiler/indexSignatureAndMappedType.ts b/tests/cases/compiler/indexSignatureAndMappedType.ts index 1070472a241..b5f9e8a0030 100644 --- a/tests/cases/compiler/indexSignatureAndMappedType.ts +++ b/tests/cases/compiler/indexSignatureAndMappedType.ts @@ -9,7 +9,7 @@ function f1(x: { [key: string]: T }, y: Record) { y = x; // Error } -function f2(x: { [key: string]: T }, y: Record) { +function f2(x: { [key: string]: T }, y: Record) { x = y; y = x; } From b07aa0d97143cbcb5f15f005b00d0f4b36dbb981 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 7 Aug 2017 17:58:32 -0700 Subject: [PATCH 35/50] fix lint errors --- src/compiler/core.ts | 6 +++--- src/harness/unittests/matchFiles.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 07f979333b6..45602616640 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2021,16 +2021,16 @@ namespace ts { componentPattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter); - // Patterns should not include subfolders like node_modules unless they are + // Patterns should not include subfolders like node_modules unless they are // explicitly included as part of the path. // - // As an optimization, if the component pattern is the same as the component, + // As an optimization, if the component pattern is the same as the component, // then there definitely were no wildcard characters and we do not need to // add the exclusion pattern. if (componentPattern !== component) { subpattern += implicitExcludePathRegexPattern; } - + subpattern += componentPattern; } else { diff --git a/src/harness/unittests/matchFiles.ts b/src/harness/unittests/matchFiles.ts index 71b1bfff11a..9e2a5883754 100644 --- a/src/harness/unittests/matchFiles.ts +++ b/src/harness/unittests/matchFiles.ts @@ -1322,7 +1322,7 @@ namespace ts { }); }); }); - + describe("with files or folders that begin with a .", () => { it("that are not explicitly included", () => { const json = { From 813aaf40c01c4e46cb9a5477dcf1a65c031745d9 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 7 Aug 2017 18:20:57 -0700 Subject: [PATCH 36/50] fix lint errors --- src/harness/harness.ts | 2 +- src/lib/es2015.symbol.wellknown.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 55a8f3ebb4d..7122f55cae2 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -420,7 +420,7 @@ namespace Utils { const maxHarnessFrames = 1; - export function filterStack(error: Error, stackTraceLimit: number = Infinity) { + export function filterStack(error: Error, stackTraceLimit = Infinity) { const stack = (error).stack; if (stack) { const lines = stack.split(/\r\n?|\n/g); diff --git a/src/lib/es2015.symbol.wellknown.d.ts b/src/lib/es2015.symbol.wellknown.d.ts index 578cf0acbc2..b7c2610e652 100644 --- a/src/lib/es2015.symbol.wellknown.d.ts +++ b/src/lib/es2015.symbol.wellknown.d.ts @@ -110,7 +110,7 @@ interface Map { readonly [Symbol.toStringTag]: "Map"; } -interface WeakMap{ +interface WeakMap { readonly [Symbol.toStringTag]: "WeakMap"; } From 9ea2350a6d8119156674759327333e6e2082ad10 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Aug 2017 07:31:21 -0700 Subject: [PATCH 37/50] Simplify parameters to updateProjectStructure and updateErrorCheck (#17175) --- src/server/session.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index 074ba4d6ca1..8fa580d7225 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -337,7 +337,7 @@ namespace ts.server { case ContextEvent: const { project, fileName } = event.data; this.projectService.logger.info(`got context event, updating diagnostics for ${fileName}`); - this.errorCheck.startNew(next => this.updateErrorCheck(next, [{ fileName, project }], this.changeSeq, (n) => n === this.changeSeq, 100)); + this.errorCheck.startNew(next => this.updateErrorCheck(next, [{ fileName, project }], 100)); break; case ConfigFileDiagEvent: const { triggerFile, configFileName, diagnostics } = event.data; @@ -453,22 +453,23 @@ namespace ts.server { } } - private updateProjectStructure(seq: number, matchSeq: (seq: number) => boolean, ms = 1500) { + private updateProjectStructure() { + const ms = 1500; + const seq = this.changeSeq; this.host.setTimeout(() => { - if (matchSeq(seq)) { + if (this.changeSeq === seq) { this.projectService.refreshInferredProjects(); } }, ms); } - private updateErrorCheck(next: NextStep, checkList: PendingErrorCheck[], seq: number, matchSeq: (seq: number) => boolean, ms = 1500, followMs = 200, requireOpen = true) { - if (followMs > ms) { - followMs = ms; - } + private updateErrorCheck(next: NextStep, checkList: PendingErrorCheck[], ms: number, requireOpen = true) { + const seq = this.changeSeq; + const followMs = Math.min(ms, 200); let index = 0; const checkOne = () => { - if (matchSeq(seq)) { + if (this.changeSeq === seq) { const checkSpec = checkList[index]; index++; if (checkSpec.project.containsFile(checkSpec.fileName, requireOpen)) { @@ -483,7 +484,7 @@ namespace ts.server { } }; - if ((checkList.length > index) && (matchSeq(seq))) { + if (checkList.length > index && this.changeSeq === seq) { next.delay(ms, checkOne); } } @@ -1262,14 +1263,14 @@ namespace ts.server { } private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void { - const checkList = mapDefined(fileNames, uncheckedFileName => { + const checkList = mapDefined(fileNames, uncheckedFileName => { const fileName = toNormalizedPath(uncheckedFileName); const project = this.projectService.getDefaultProjectForFile(fileName, /*refreshInferredProjects*/ true); return project && { fileName, project }; }); if (checkList.length > 0) { - this.updateErrorCheck(next, checkList, this.changeSeq, (n) => n === this.changeSeq, delay); + this.updateErrorCheck(next, checkList, delay); } } @@ -1283,7 +1284,7 @@ namespace ts.server { scriptInfo.editContent(start, end, args.insertString); this.changeSeq++; } - this.updateProjectStructure(this.changeSeq, n => n === this.changeSeq); + this.updateProjectStructure(); } } @@ -1638,7 +1639,7 @@ namespace ts.server { const checkList = fileNamesInProject.map(fileName => ({ fileName, project })); // Project level error analysis runs on background files too, therefore // doesn't require the file to be opened - this.updateErrorCheck(next, checkList, this.changeSeq, (n) => n === this.changeSeq, delay, 200, /*requireOpen*/ false); + this.updateErrorCheck(next, checkList, delay, /*requireOpen*/ false); } } From 382785a5282f6d49dae0f6af483c8acdaf89ed78 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Aug 2017 07:54:08 -0700 Subject: [PATCH 38/50] Fix logging of module resolution errors (#17144) --- src/compiler/moduleNameResolver.ts | 2 +- src/harness/harnessLanguageService.ts | 2 +- src/server/project.ts | 3 ++- src/server/server.ts | 2 -- src/server/types.ts | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 513c741ba09..be867db9743 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -678,7 +678,7 @@ namespace ts { const { resolvedModule, failedLookupLocations } = nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ts.ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, /*jsOnly*/ true); if (!resolvedModule) { - throw new Error(`Could not resolve JS module ${moduleName} starting at ${initialDir}. Looked in: ${failedLookupLocations.join(", ")}`); + throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations.join(", ")}`); } return resolvedModule.resolvedFileName; } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index c604b224656..994ebe67e0c 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -795,7 +795,7 @@ namespace Harness.LanguageService { default: return { module: undefined, - error: "Could not resolve module" + error: new Error("Could not resolve module") }; } diff --git a/src/server/project.ts b/src/server/project.ts index 524b6c4d28d..5a92e6505e0 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -170,7 +170,8 @@ namespace ts.server { log(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`); const result = host.require(resolvedPath, moduleName); if (result.error) { - log(`Failed to load module: ${JSON.stringify(result.error)}`); + const err = result.error.stack || result.error.message || JSON.stringify(result.error); + log(`Failed to load module '${moduleName}': ${err}`); return undefined; } return result.module; diff --git a/src/server/server.ts b/src/server/server.ts index b72cc2f5a81..d0044153b91 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -116,8 +116,6 @@ namespace ts.server { birthtime: Date; } - type RequireResult = { module: {}, error: undefined } | { module: undefined, error: {} }; - const readline: { createInterface(options: ReadLineOptions): NodeJS.EventEmitter; } = require("readline"); diff --git a/src/server/types.ts b/src/server/types.ts index 07b94fe827e..4fc4356a4a9 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -9,7 +9,7 @@ declare namespace ts.server { data: any; } - type RequireResult = { module: {}, error: undefined } | { module: undefined, error: {} }; + type RequireResult = { module: {}, error: undefined } | { module: undefined, error: { stack?: string, message?: string } }; export interface ServerHost extends System { setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; clearTimeout(timeoutId: any): void; From a9a30d76fb39a55d91f633b3555d90c4f438d9ae Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Aug 2017 07:55:03 -0700 Subject: [PATCH 39/50] Fix parsing of globalPlugins and pluginProbeLocations: Don't include empty string (#17143) --- src/server/server.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/server/server.ts b/src/server/server.ts index d0044153b91..2bf10964fa3 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -760,8 +760,16 @@ namespace ts.server { const typingSafeListLocation = findArgument(Arguments.TypingSafeListLocation); const npmLocation = findArgument(Arguments.NpmLocation); - const globalPlugins = (findArgument("--globalPlugins") || "").split(","); - const pluginProbeLocations = (findArgument("--pluginProbeLocations") || "").split(","); + function parseStringArray(argName: string): string[] { + const arg = findArgument(argName); + if (arg === undefined) { + return emptyArray as string[]; // TODO: https://github.com/Microsoft/TypeScript/issues/16312 + } + return arg.split(",").filter(name => name !== ""); + } + + const globalPlugins = parseStringArray("--globalPlugins"); + const pluginProbeLocations = parseStringArray("--pluginProbeLocations"); const allowLocalPluginLoads = hasArgument("--allowLocalPluginLoads"); const useSingleInferredProject = hasArgument("--useSingleInferredProject"); From ceae613e4c0ba36829a2381687883ecdc6b169c3 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Aug 2017 07:56:14 -0700 Subject: [PATCH 40/50] Add lint rule to check that `Debug.assert` calls do not eagerly interpolate strings (#17125) * And lint rule to check that `Debug.assert` calls do not eagerly interpolate strings * Use more specific 'assert' functions to avoid callbacks * Respond to PR feedback --- scripts/tslint/booleanTriviaRule.ts | 3 +- scripts/tslint/debugAssertRule.ts | 45 +++++++++++++++++++++++++ src/compiler/core.ts | 37 ++++++++++++++++---- src/compiler/transformers/generators.ts | 2 +- src/server/project.ts | 2 +- src/services/classifier.ts | 4 +-- src/services/services.ts | 2 +- src/services/signatureHelp.ts | 12 +++++-- src/services/transpile.ts | 4 +-- tslint.json | 1 + 10 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 scripts/tslint/debugAssertRule.ts diff --git a/scripts/tslint/booleanTriviaRule.ts b/scripts/tslint/booleanTriviaRule.ts index 189dafac77e..c498131be16 100644 --- a/scripts/tslint/booleanTriviaRule.ts +++ b/scripts/tslint/booleanTriviaRule.ts @@ -34,6 +34,7 @@ function walk(ctx: Lint.WalkContext): void { switch (methodName) { case "apply": case "assert": + case "assertEqual": case "call": case "equal": case "fail": @@ -69,7 +70,7 @@ function walk(ctx: Lint.WalkContext): void { const ranges = ts.getTrailingCommentRanges(sourceFile.text, arg.pos) || ts.getLeadingCommentRanges(sourceFile.text, arg.pos); if (ranges === undefined || ranges.length !== 1 || ranges[0].kind !== ts.SyntaxKind.MultiLineCommentTrivia) { - ctx.addFailureAtNode(arg, "Tag boolean argument with parameter name"); + ctx.addFailureAtNode(arg, "Tag argument with parameter name"); return; } diff --git a/scripts/tslint/debugAssertRule.ts b/scripts/tslint/debugAssertRule.ts new file mode 100644 index 00000000000..933b27697b0 --- /dev/null +++ b/scripts/tslint/debugAssertRule.ts @@ -0,0 +1,45 @@ +import * as Lint from "tslint/lib"; +import * as ts from "typescript"; + +export class Rule extends Lint.Rules.AbstractRule { + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithFunction(sourceFile, ctx => walk(ctx)); + } +} + +function walk(ctx: Lint.WalkContext): void { + ts.forEachChild(ctx.sourceFile, function recur(node) { + if (ts.isCallExpression(node)) { + checkCall(node); + } + ts.forEachChild(node, recur); + }); + + function checkCall(node: ts.CallExpression) { + if (!isDebugAssert(node.expression) || node.arguments.length < 2) { + return; + } + + const message = node.arguments[1]; + if (!ts.isStringLiteral(message)) { + ctx.addFailureAtNode(message, "Second argument to 'Debug.assert' should be a string literal."); + } + + if (node.arguments.length < 3) { + return; + } + + const message2 = node.arguments[2]; + if (!ts.isStringLiteral(message2) && !ts.isArrowFunction(message2)) { + ctx.addFailureAtNode(message, "Third argument to 'Debug.assert' should be a string literal or arrow function."); + } + } + + function isDebugAssert(expr: ts.Node): boolean { + return ts.isPropertyAccessExpression(expr) && isName(expr.expression, "Debug") && isName(expr.name, "assert"); + } + + function isName(expr: ts.Node, text: string): boolean { + return ts.isIdentifier(expr) && expr.text === text; + } +} diff --git a/src/compiler/core.ts b/src/compiler/core.ts index f7ea0583670..bc131b201fd 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1290,12 +1290,12 @@ namespace ts { export function createFileDiagnostic(file: SourceFile, start: number, length: number, message: DiagnosticMessage): Diagnostic { const end = start + length; - Debug.assert(start >= 0, "start must be non-negative, is " + start); - Debug.assert(length >= 0, "length must be non-negative, is " + length); + Debug.assertGreaterThanOrEqual(start, 0); + Debug.assertGreaterThanOrEqual(length, 0); if (file) { - Debug.assert(start <= file.text.length, `start must be within the bounds of the file. ${start} > ${file.text.length}`); - Debug.assert(end <= file.text.length, `end must be the bounds of the file. ${end} > ${file.text.length}`); + Debug.assertLessThanOrEqual(start, file.text.length); + Debug.assertLessThanOrEqual(end, file.text.length); } let text = getLocaleSpecificMessage(message); @@ -2389,15 +2389,40 @@ namespace ts { return currentAssertionLevel >= level; } - export function assert(expression: boolean, message?: string, verboseDebugInfo?: () => string, stackCrawlMark?: Function): void { + export function assert(expression: boolean, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: Function): void { if (!expression) { if (verboseDebugInfo) { - message += "\r\nVerbose Debug Information: " + verboseDebugInfo(); + message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); } fail(message ? "False expression: " + message : "False expression.", stackCrawlMark || assert); } } + export function assertEqual(a: T, b: T, msg?: string, msg2?: string): void { + if (a !== b) { + const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; + fail(`Expected ${a} === ${b}. ${message}`); + } + } + + export function assertLessThan(a: number, b: number, msg?: string): void { + if (a >= b) { + fail(`Expected ${a} < ${b}. ${msg || ""}`); + } + } + + export function assertLessThanOrEqual(a: number, b: number): void { + if (a > b) { + fail(`Expected ${a} <= ${b}`); + } + } + + export function assertGreaterThanOrEqual(a: number, b: number): void { + if (a < b) { + fail(`Expected ${a} >= ${b}`); + } + } + export function fail(message?: string, stackCrawlMark?: Function): void { debugger; const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index 41edf24fe9e..5e2016ab590 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -2448,7 +2448,7 @@ namespace ts { * @param location An optional source map location for the statement. */ function createInlineBreak(label: Label, location?: TextRange): ReturnStatement { - Debug.assert(label > 0, `Invalid label: ${label}`); + Debug.assertLessThan(0, label, "Invalid label"); return setTextRange( createReturn( createArrayLiteral([ diff --git a/src/server/project.ts b/src/server/project.ts index 5a92e6505e0..844ded761d8 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -363,7 +363,7 @@ namespace ts.server { return map(this.program.getSourceFiles(), sourceFile => { const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.path); if (!scriptInfo) { - Debug.assert(false, `scriptInfo for a file '${sourceFile.fileName}' is missing.`); + Debug.fail(`scriptInfo for a file '${sourceFile.fileName}' is missing.`); } return scriptInfo; }); diff --git a/src/services/classifier.ts b/src/services/classifier.ts index dc5d99bc490..4552d8bf985 100644 --- a/src/services/classifier.ts +++ b/src/services/classifier.ts @@ -260,11 +260,11 @@ namespace ts { templateStack.pop(); } else { - Debug.assert(token === SyntaxKind.TemplateMiddle, "Should have been a template middle. Was " + token); + Debug.assertEqual(token, SyntaxKind.TemplateMiddle, "Should have been a template middle."); } } else { - Debug.assert(lastTemplateStackToken === SyntaxKind.OpenBraceToken, "Should have been an open brace. Was: " + token); + Debug.assertEqual(lastTemplateStackToken, SyntaxKind.OpenBraceToken, "Should have been an open brace"); templateStack.pop(); } } diff --git a/src/services/services.ts b/src/services/services.ts index 6a171ad9166..874c2feb107 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1258,7 +1258,7 @@ namespace ts { // We do not support the scenario where a host can modify a registered // file's script kind, i.e. in one project some file is treated as ".ts" // and in another as ".js" - Debug.assert(hostFileInformation.scriptKind === oldSourceFile.scriptKind, "Registered script kind (" + oldSourceFile.scriptKind + ") should match new script kind (" + hostFileInformation.scriptKind + ") for file: " + path); + Debug.assertEqual(hostFileInformation.scriptKind, oldSourceFile.scriptKind, "Registered script kind should match new script kind.", path); return documentRegistry.updateDocumentWithKey(fileName, path, newSettings, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind); } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index f008c829116..2976b0d28ee 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -136,7 +136,9 @@ namespace ts.SignatureHelp { const kind = invocation.typeArguments && invocation.typeArguments.pos === list.pos ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments; const argumentCount = getArgumentCount(list); - Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); + if (argumentIndex !== 0) { + Debug.assertLessThan(argumentIndex, argumentCount); + } const argumentsSpan = getApplicableSpanForArguments(list, sourceFile); return { kind, invocation, argumentsSpan, argumentIndex, argumentCount }; } @@ -270,7 +272,9 @@ namespace ts.SignatureHelp { ? 1 : (tagExpression.template).templateSpans.length + 1; - Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); + if (argumentIndex !== 0) { + Debug.assertLessThan(argumentIndex, argumentCount); + } return { kind: ArgumentListKind.TaggedTemplateArguments, invocation: tagExpression, @@ -402,7 +406,9 @@ namespace ts.SignatureHelp { }; }); - Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); + if (argumentIndex !== 0) { + Debug.assertLessThan(argumentIndex, argumentCount); + } const selectedItemIndex = candidates.indexOf(resolvedSignature); Debug.assert(selectedItemIndex !== -1); // If candidates is non-empty it should always include bestSignature. We check for an empty candidates before calling this function. diff --git a/src/services/transpile.ts b/src/services/transpile.ts index 561c188c6cd..79a69b886d9 100644 --- a/src/services/transpile.ts +++ b/src/services/transpile.ts @@ -78,11 +78,11 @@ namespace ts { getSourceFile: (fileName) => fileName === normalizePath(inputFileName) ? sourceFile : undefined, writeFile: (name, text) => { if (fileExtensionIs(name, ".map")) { - Debug.assert(sourceMapText === undefined, `Unexpected multiple source map outputs for the file '${name}'`); + Debug.assertEqual(sourceMapText, undefined, "Unexpected multiple source map outputs, file:", name); sourceMapText = text; } else { - Debug.assert(outputText === undefined, `Unexpected multiple outputs for the file: '${name}'`); + Debug.assertEqual(outputText, undefined, "Unexpected multiple outputs, file:", name); outputText = text; } }, diff --git a/tslint.json b/tslint.json index bcd4dfa2223..de60ad7683a 100644 --- a/tslint.json +++ b/tslint.json @@ -7,6 +7,7 @@ "check-space" ], "curly":[true, "ignore-same-line"], + "debug-assert": true, "indent": [true, "spaces" ], From e1802f49660a30df702c160f4ed001ccacdb33e3 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Aug 2017 10:49:49 -0700 Subject: [PATCH 41/50] MultistepOperation: Don't need 'completed', just use `requestId === undefined` (#17173) * MultistepOperation: Don't need 'completed', just use `requestId === undefined` * Check for `requestId !== undefined` --- src/server/session.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index 8fa580d7225..5c9d210939c 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -163,26 +163,22 @@ namespace ts.server { * Scheduling is done via instance of NextStep. If on current step subsequent step was not scheduled - operation is assumed to be completed. */ class MultistepOperation implements NextStep { - private requestId: number; + private requestId: number | undefined; private timerHandle: any; - private immediateId: any; - private completed = true; + private immediateId: number | undefined; constructor(private readonly operationHost: MultistepOperationHost) {} public startNew(action: (next: NextStep) => void) { this.complete(); this.requestId = this.operationHost.getCurrentRequestId(); - this.completed = false; this.executeAction(action); } private complete() { - if (!this.completed) { - if (this.requestId) { - this.operationHost.sendRequestCompletedEvent(this.requestId); - } - this.completed = true; + if (this.requestId !== undefined) { + this.operationHost.sendRequestCompletedEvent(this.requestId); + this.requestId = undefined; } this.setTimerHandle(undefined); this.setImmediateId(undefined); From f69ce5c0c8930a4a4912014e41fe6487410acb3d Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Aug 2017 10:54:18 -0700 Subject: [PATCH 42/50] Convert two arrays to readonly (#17685) --- src/server/editorServices.ts | 4 ++-- src/server/server.ts | 8 ++++---- src/server/session.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 4ffdf5d6c5d..5bb1da251ea 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -326,8 +326,8 @@ namespace ts.server { typingsInstaller: ITypingsInstaller; eventHandler?: ProjectServiceEventHandler; throttleWaitMilliseconds?: number; - globalPlugins?: string[]; - pluginProbeLocations?: string[]; + globalPlugins?: ReadonlyArray; + pluginProbeLocations?: ReadonlyArray; allowLocalPluginLoads?: boolean; } diff --git a/src/server/server.ts b/src/server/server.ts index 2bf10964fa3..6b89019632f 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -15,8 +15,8 @@ namespace ts.server { typingSafeListLocation: string; npmLocation: string | undefined; telemetryEnabled: boolean; - globalPlugins: string[]; - pluginProbeLocations: string[]; + globalPlugins: ReadonlyArray; + pluginProbeLocations: ReadonlyArray; allowLocalPluginLoads: boolean; } @@ -760,10 +760,10 @@ namespace ts.server { const typingSafeListLocation = findArgument(Arguments.TypingSafeListLocation); const npmLocation = findArgument(Arguments.NpmLocation); - function parseStringArray(argName: string): string[] { + function parseStringArray(argName: string): ReadonlyArray { const arg = findArgument(argName); if (arg === undefined) { - return emptyArray as string[]; // TODO: https://github.com/Microsoft/TypeScript/issues/16312 + return emptyArray; } return arg.split(",").filter(name => name !== ""); } diff --git a/src/server/session.ts b/src/server/session.ts index 5c9d210939c..d8e0f695def 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -255,8 +255,8 @@ namespace ts.server { eventHandler?: ProjectServiceEventHandler; throttleWaitMilliseconds?: number; - globalPlugins?: string[]; - pluginProbeLocations?: string[]; + globalPlugins?: ReadonlyArray; + pluginProbeLocations?: ReadonlyArray; allowLocalPluginLoads?: boolean; } From 5141ce751d9887a8b402a34d66c63581c17aed00 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Aug 2017 11:02:10 -0700 Subject: [PATCH 43/50] Deduplicate unresolvedImports (#17248) * Deduplicate unresolvedImports * Add `isNonDuplicateInSortedArray` helper --- src/compiler/core.ts | 8 ++++---- src/server/project.ts | 4 ++-- src/server/utilities.ts | 9 +++++++++ src/services/jsTyping.ts | 2 +- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index bc131b201fd..d5c8396d06d 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -361,11 +361,11 @@ namespace ts { return false; } - export function filterMutate(array: T[], f: (x: T) => boolean): void { + export function filterMutate(array: T[], f: (x: T, i: number, array: T[]) => boolean): void { let outIndex = 0; - for (const item of array) { - if (f(item)) { - array[outIndex] = item; + for (let i = 0; i < array.length; i++) { + if (f(array[i], i, array)) { + array[outIndex] = array[i]; outIndex++; } } diff --git a/src/server/project.ts b/src/server/project.ts index 844ded761d8..97c8eb706f3 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -495,7 +495,7 @@ namespace ts.server { this.projectStateVersion++; } - private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: string[]) { + private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: Push) { const cached = this.cachedUnresolvedImportsPerFile.get(file.path); if (cached) { // found cached result - use it and return @@ -555,7 +555,7 @@ namespace ts.server { for (const sourceFile of this.program.getSourceFiles()) { this.extractUnresolvedImportsFromSourceFile(sourceFile, result); } - this.lastCachedUnresolvedImportsList = toSortedArray(result); + this.lastCachedUnresolvedImportsList = toDeduplicatedSortedArray(result); } unresolvedImports = this.lastCachedUnresolvedImportsList; diff --git a/src/server/utilities.ts b/src/server/utilities.ts index fc88f11408a..5efb20d074f 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -262,6 +262,15 @@ namespace ts.server { return arr as SortedArray; } + export function toDeduplicatedSortedArray(arr: string[]): SortedArray { + arr.sort(); + filterMutate(arr, isNonDuplicateInSortedArray); + return arr as SortedArray; + } + function isNonDuplicateInSortedArray(value: T, index: number, array: T[]) { + return index === 0 || value !== array[index - 1]; + } + export function enumerateInsertsAndDeletes(newItems: SortedReadonlyArray, oldItems: SortedReadonlyArray, inserted: (newItem: T) => void, deleted: (oldItem: T) => void, compare?: Comparer) { compare = compare || compareValues; let newIndex = 0; diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 4de7bb3191d..5e7b7d424f8 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -35,7 +35,7 @@ namespace ts.JsTyping { "crypto", "stream", "util", "assert", "tty", "domain", "constants", "process", "v8", "timers", "console"]; - const nodeCoreModules = arrayToMap(nodeCoreModuleList, x => x); + const nodeCoreModules = arrayToSet(nodeCoreModuleList); /** * A map of loose file names to library names that we are confident require typings From eb8bcd77cba70b2dcafaaa687aeac06500f717d8 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Aug 2017 11:02:53 -0700 Subject: [PATCH 44/50] tsserverProjectSystem.ts: Remove unnecessary 'export's (#17201) * tsserverProjectSystem.ts: Remove unnecessary 'export's * Export `PostExecAction` --- .../unittests/tsserverProjectSystem.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 4a7cafa245b..e2b516ddae6 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -45,7 +45,7 @@ namespace ts.projectSystem { getLogFileName: (): string => undefined }; - export const { content: libFileContent } = Harness.getDefaultLibraryFile(Harness.IO); + const { content: libFileContent } = Harness.getDefaultLibraryFile(Harness.IO); export const libFile: FileOrFolder = { path: "/a/lib/lib.d.ts", content: libFileContent @@ -118,7 +118,7 @@ namespace ts.projectSystem { return JSON.stringify({ dependencies }); } - export function getExecutingFilePathFromLibFile(): string { + function getExecutingFilePathFromLibFile(): string { return combinePaths(getDirectoryPath(libFile.path), "tsc.js"); } @@ -130,7 +130,7 @@ namespace ts.projectSystem { return map(fileNames, toExternalFile); } - export class TestServerEventManager { + class TestServerEventManager { public events: server.ProjectServiceEvent[] = []; handler: server.ProjectServiceEventHandler = (event: server.ProjectServiceEvent) => { @@ -143,7 +143,7 @@ namespace ts.projectSystem { } } - export interface TestServerHostCreationParameters { + interface TestServerHostCreationParameters { useCaseSensitiveFileNames?: boolean; executingFilePath?: string; currentDirectory?: string; @@ -205,7 +205,7 @@ namespace ts.projectSystem { return new TestSession(opts); } - export interface CreateProjectServiceParameters { + interface CreateProjectServiceParameters { cancellationToken?: HostCancellationToken; logger?: server.Logger; useSingleInferredProject?: boolean; @@ -253,15 +253,15 @@ namespace ts.projectSystem { entries: FSEntry[]; } - export function isFolder(s: FSEntry): s is Folder { + function isFolder(s: FSEntry): s is Folder { return isArray((s).entries); } - export function isFile(s: FSEntry): s is File { + function isFile(s: FSEntry): s is File { return typeof (s).content === "string"; } - export function addFolder(fullPath: string, toPath: (s: string) => Path, fs: Map): Folder { + function addFolder(fullPath: string, toPath: (s: string) => Path, fs: Map): Folder { const path = toPath(fullPath); if (fs.has(path)) { Debug.assert(isFolder(fs.get(path))); @@ -279,29 +279,29 @@ namespace ts.projectSystem { return entry; } - export function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { + function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map`); for (const name of expectedKeys) { assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`); } } - export function checkFileNames(caption: string, actualFileNames: string[], expectedFileNames: string[]) { + function checkFileNames(caption: string, actualFileNames: string[], expectedFileNames: string[]) { assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected ${JSON.stringify(expectedFileNames)}, got ${actualFileNames}`); for (const f of expectedFileNames) { assert.isTrue(contains(actualFileNames, f), `${caption}: expected to find ${f} in ${JSON.stringify(actualFileNames)}`); } } - export function checkNumberOfConfiguredProjects(projectService: server.ProjectService, expected: number) { + function checkNumberOfConfiguredProjects(projectService: server.ProjectService, expected: number) { assert.equal(projectService.configuredProjects.length, expected, `expected ${expected} configured project(s)`); } - export function checkNumberOfExternalProjects(projectService: server.ProjectService, expected: number) { + function checkNumberOfExternalProjects(projectService: server.ProjectService, expected: number) { assert.equal(projectService.externalProjects.length, expected, `expected ${expected} external project(s)`); } - export function checkNumberOfInferredProjects(projectService: server.ProjectService, expected: number) { + function checkNumberOfInferredProjects(projectService: server.ProjectService, expected: number) { assert.equal(projectService.inferredProjects.length, expected, `expected ${expected} inferred project(s)`); } @@ -315,7 +315,7 @@ namespace ts.projectSystem { checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles); } - export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[]) { + function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[]) { checkMapKeys("watchedDirectories", host.watchedDirectories, expectedDirectories); } @@ -323,11 +323,11 @@ namespace ts.projectSystem { checkFileNames(`${server.ProjectKind[project.projectKind]} project, actual files`, project.getFileNames(), expectedFiles); } - export function checkProjectRootFiles(project: server.Project, expectedFiles: string[]) { + function checkProjectRootFiles(project: server.Project, expectedFiles: string[]) { checkFileNames(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles); } - export class Callbacks { + class Callbacks { private map: TimeOutCallback[] = []; private nextId = 1; @@ -363,7 +363,7 @@ namespace ts.projectSystem { } } - export type TimeOutCallback = () => any; + type TimeOutCallback = () => any; export class TestServerHost implements server.ServerHost { args: string[] = []; From 94518e853303b4a4a5cb178976593a6c4e319082 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Aug 2017 11:18:20 -0700 Subject: [PATCH 45/50] Don't count self-reference when setting `isReferenced` (#17495) * Don't count self-reference when setting `isReferenced` * Improve comment --- src/compiler/checker.ts | 25 ++-------- .../noUnusedLocals_selfReference.errors.txt | 28 +++++++++++ .../reference/noUnusedLocals_selfReference.js | 49 +++++++++++++++++++ ...LocalsAndParametersTypeAliases2.errors.txt | 5 +- .../compiler/noUnusedLocals_selfReference.ts | 17 +++++++ tests/webTestServer.ts | 16 ------ 6 files changed, 102 insertions(+), 38 deletions(-) create mode 100644 tests/baselines/reference/noUnusedLocals_selfReference.errors.txt create mode 100644 tests/baselines/reference/noUnusedLocals_selfReference.js create mode 100644 tests/cases/compiler/noUnusedLocals_selfReference.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 10400a3853a..1113d131a03 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1086,7 +1086,10 @@ namespace ts { location = location.parent; } - if (result && nameNotFoundMessage && noUnusedIdentifiers) { + // We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`. + // If `result === lastLocation.symbol`, that means that we are somewhere inside `lastLocation` looking up a name, and resolving to `lastLocation` itself. + // That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used. + if (result && nameNotFoundMessage && noUnusedIdentifiers && result !== lastLocation.symbol) { result.isReferenced = true; } @@ -10800,17 +10803,6 @@ namespace ts { return undefined; } - function getLeftmostIdentifierOrThis(node: Node): Node { - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.ThisKeyword: - return node; - case SyntaxKind.PropertyAccessExpression: - return getLeftmostIdentifierOrThis((node).expression); - } - return undefined; - } - function getBindingElementNameText(element: BindingElement): string | undefined { if (element.parent.kind === SyntaxKind.ObjectBindingPattern) { const name = element.propertyName || element.name; @@ -18520,15 +18512,6 @@ namespace ts { return forEachChild(n, containsSuperCall); } - function markThisReferencesAsErrors(n: Node): void { - if (n.kind === SyntaxKind.ThisKeyword) { - error(n, Diagnostics.this_cannot_be_referenced_in_current_location); - } - else if (n.kind !== SyntaxKind.FunctionExpression && n.kind !== SyntaxKind.FunctionDeclaration) { - forEachChild(n, markThisReferencesAsErrors); - } - } - function isInstancePropertyWithInitializer(n: Node): boolean { return n.kind === SyntaxKind.PropertyDeclaration && !(getModifierFlags(n) & ModifierFlags.Static) && diff --git a/tests/baselines/reference/noUnusedLocals_selfReference.errors.txt b/tests/baselines/reference/noUnusedLocals_selfReference.errors.txt new file mode 100644 index 00000000000..af40081e71c --- /dev/null +++ b/tests/baselines/reference/noUnusedLocals_selfReference.errors.txt @@ -0,0 +1,28 @@ +tests/cases/compiler/noUnusedLocals_selfReference.ts(3,10): error TS6133: 'f' is declared but never used. +tests/cases/compiler/noUnusedLocals_selfReference.ts(4,7): error TS6133: 'C' is declared but never used. +tests/cases/compiler/noUnusedLocals_selfReference.ts(7,6): error TS6133: 'E' is declared but never used. + + +==== tests/cases/compiler/noUnusedLocals_selfReference.ts (3 errors) ==== + export {}; // Make this a module scope, so these are local variables. + + function f() { f; } + ~ +!!! error TS6133: 'f' is declared but never used. + class C { + ~ +!!! error TS6133: 'C' is declared but never used. + m() { C; } + } + enum E { A = 0, B = E.A } + ~ +!!! error TS6133: 'E' is declared but never used. + + // Does not detect mutual recursion. + function g() { D; } + class D { m() { g; } } + + // Does not work on private methods. + class P { private m() { this.m; } } + P; + \ No newline at end of file diff --git a/tests/baselines/reference/noUnusedLocals_selfReference.js b/tests/baselines/reference/noUnusedLocals_selfReference.js new file mode 100644 index 00000000000..74a39923d57 --- /dev/null +++ b/tests/baselines/reference/noUnusedLocals_selfReference.js @@ -0,0 +1,49 @@ +//// [noUnusedLocals_selfReference.ts] +export {}; // Make this a module scope, so these are local variables. + +function f() { f; } +class C { + m() { C; } +} +enum E { A = 0, B = E.A } + +// Does not detect mutual recursion. +function g() { D; } +class D { m() { g; } } + +// Does not work on private methods. +class P { private m() { this.m; } } +P; + + +//// [noUnusedLocals_selfReference.js] +"use strict"; +exports.__esModule = true; +function f() { f; } +var C = (function () { + function C() { + } + C.prototype.m = function () { C; }; + return C; +}()); +var E; +(function (E) { + E[E["A"] = 0] = "A"; + E[E["B"] = 0] = "B"; +})(E || (E = {})); +// Does not detect mutual recursion. +function g() { D; } +var D = (function () { + function D() { + } + D.prototype.m = function () { g; }; + return D; +}()); +// Does not work on private methods. +var P = (function () { + function P() { + } + P.prototype.m = function () { this.m; }; + return P; +}()); +P; diff --git a/tests/baselines/reference/unusedLocalsAndParametersTypeAliases2.errors.txt b/tests/baselines/reference/unusedLocalsAndParametersTypeAliases2.errors.txt index 528ca9f9e73..e5c6f476480 100644 --- a/tests/baselines/reference/unusedLocalsAndParametersTypeAliases2.errors.txt +++ b/tests/baselines/reference/unusedLocalsAndParametersTypeAliases2.errors.txt @@ -1,8 +1,9 @@ tests/cases/compiler/unusedLocalsAndParametersTypeAliases2.ts(2,6): error TS6133: 'handler1' is declared but never used. +tests/cases/compiler/unusedLocalsAndParametersTypeAliases2.ts(5,10): error TS6133: 'foo' is declared but never used. tests/cases/compiler/unusedLocalsAndParametersTypeAliases2.ts(6,10): error TS6133: 'handler2' is declared but never used. -==== tests/cases/compiler/unusedLocalsAndParametersTypeAliases2.ts (2 errors) ==== +==== tests/cases/compiler/unusedLocalsAndParametersTypeAliases2.ts (3 errors) ==== // unused type handler1 = () => void; ~~~~~~~~ @@ -10,6 +11,8 @@ tests/cases/compiler/unusedLocalsAndParametersTypeAliases2.ts(6,10): error TS613 function foo() { + ~~~ +!!! error TS6133: 'foo' is declared but never used. type handler2 = () => void; ~~~~~~~~ !!! error TS6133: 'handler2' is declared but never used. diff --git a/tests/cases/compiler/noUnusedLocals_selfReference.ts b/tests/cases/compiler/noUnusedLocals_selfReference.ts new file mode 100644 index 00000000000..8eb528743c0 --- /dev/null +++ b/tests/cases/compiler/noUnusedLocals_selfReference.ts @@ -0,0 +1,17 @@ +// @noUnusedLocals: true + +export {}; // Make this a module scope, so these are local variables. + +function f() { f; } +class C { + m() { C; } +} +enum E { A = 0, B = E.A } + +// Does not detect mutual recursion. +function g() { D; } +class D { m() { g; } } + +// Does not work on private methods. +class P { private m() { this.m; } } +P; diff --git a/tests/webTestServer.ts b/tests/webTestServer.ts index abfd71a8fff..20228f9a741 100644 --- a/tests/webTestServer.ts +++ b/tests/webTestServer.ts @@ -125,22 +125,6 @@ function dir(dirPath: string, spec?: string, options?: any) { } } -// fs.rmdirSync won't delete directories with files in it -function deleteFolderRecursive(dirPath: string) { - if (fs.existsSync(dirPath)) { - fs.readdirSync(dirPath).forEach((file) => { - const curPath = path.join(dirPath, file); - if (fs.statSync(curPath).isDirectory()) { // recurse - deleteFolderRecursive(curPath); - } - else { // delete file - fs.unlinkSync(curPath); - } - }); - fs.rmdirSync(dirPath); - } -}; - function writeFile(path: string, data: any) { ensureDirectoriesExist(getDirectoryPath(path)); fs.writeFileSync(path, data); From d99a492ddd7f30450268ed49fa1be308ee43690d Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Aug 2017 11:22:22 -0700 Subject: [PATCH 46/50] Simplify server logger (#17271) * Simplify server logger * Move function printProjects out of inner closure --- src/harness/harnessLanguageService.ts | 13 ++-- .../unittests/cachingInServerLSHost.ts | 14 +--- src/harness/unittests/session.ts | 18 +----- .../unittests/tsserverProjectSystem.ts | 13 ++-- src/server/editorServices.ts | 30 ++++----- src/server/server.ts | 64 ++++++++++--------- src/server/session.ts | 4 +- src/server/utilities.ts | 15 +---- 8 files changed, 67 insertions(+), 104 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 994ebe67e0c..af5a998df99 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -681,11 +681,11 @@ namespace Harness.LanguageService { } info(message: string): void { - return this.host.log(message); + this.host.log(message); } - msg(message: string) { - return this.host.log(message); + err(message: string): void { + this.host.log(message); } loggingEnabled() { @@ -700,17 +700,12 @@ namespace Harness.LanguageService { return false; } - - endGroup(): void { - } + group() { throw ts.notImplemented(); } perftrc(message: string): void { return this.host.log(message); } - startGroup(): void { - } - setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any { return setTimeout(callback, ms, args); } diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index eb2907e89de..92c10c1ff6d 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -52,21 +52,9 @@ namespace ts { } function createProject(rootFile: string, serverHost: server.ServerHost): { project: server.Project, rootScriptInfo: server.ScriptInfo } { - const logger: server.Logger = { - close: noop, - hasLevel: () => false, - loggingEnabled: () => false, - perftrc: noop, - info: noop, - startGroup: noop, - endGroup: noop, - msg: noop, - getLogFileName: (): string => undefined - }; - const svcOpts: server.ProjectServiceOptions = { host: serverHost, - logger, + logger: projectSystem.nullLogger, cancellationToken: { isCancellationRequested: () => false }, useSingleInferredProject: false, typingsInstaller: undefined diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index 862ebee4b03..1ce5792a81b 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -28,18 +28,6 @@ namespace ts.server { createHash: Harness.LanguageService.mockHash, }; - const mockLogger: Logger = { - close: noop, - hasLevel(): boolean { return false; }, - loggingEnabled(): boolean { return false; }, - perftrc: noop, - info: noop, - startGroup: noop, - endGroup: noop, - msg: noop, - getLogFileName: (): string => undefined - }; - class TestSession extends Session { getProjectService() { return this.projectService; @@ -58,7 +46,7 @@ namespace ts.server { typingsInstaller: undefined, byteLength: Utils.byteLength, hrtime: process.hrtime, - logger: mockLogger, + logger: projectSystem.nullLogger, canUseEvents: true }; return new TestSession(opts); @@ -408,7 +396,7 @@ namespace ts.server { typingsInstaller: undefined, byteLength: Utils.byteLength, hrtime: process.hrtime, - logger: mockLogger, + logger: projectSystem.nullLogger, canUseEvents: true }); this.addProtocolHandler(this.customHandler, () => { @@ -475,7 +463,7 @@ namespace ts.server { typingsInstaller: undefined, byteLength: Utils.byteLength, hrtime: process.hrtime, - logger: mockLogger, + logger: projectSystem.nullLogger, canUseEvents: true }); this.addProtocolHandler("echo", (req: protocol.Request) => ({ diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index e2b516ddae6..6c60160740e 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -34,14 +34,13 @@ namespace ts.projectSystem { } export const nullLogger: server.Logger = { - close: () => void 0, - hasLevel: () => void 0, + close: noop, + hasLevel: () => false, loggingEnabled: () => false, - perftrc: () => void 0, - info: () => void 0, - startGroup: () => void 0, - endGroup: () => void 0, - msg: () => void 0, + perftrc: noop, + info: noop, + err: noop, + group: noop, getLogFileName: (): string => undefined }; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5bb1da251ea..554ac337920 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -928,26 +928,24 @@ namespace ts.server { return; } - this.logger.startGroup(); + this.logger.group(info => { + let counter = 0; + counter = printProjects(this.externalProjects, info, counter); + counter = printProjects(this.configuredProjects, info, counter); + printProjects(this.inferredProjects, info, counter); - let counter = 0; - counter = printProjects(this.logger, this.externalProjects, counter); - counter = printProjects(this.logger, this.configuredProjects, counter); - counter = printProjects(this.logger, this.inferredProjects, counter); + info("Open files: "); + for (const rootFile of this.openFiles) { + info(`\t${rootFile.fileName}`); + } + }); - this.logger.info("Open files: "); - for (const rootFile of this.openFiles) { - this.logger.info(`\t${rootFile.fileName}`); - } - - this.logger.endGroup(); - - function printProjects(logger: Logger, projects: Project[], counter: number) { + function printProjects(projects: Project[], info: (msg: string) => void, counter: number): number { for (const project of projects) { project.updateGraph(); - logger.info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`); - logger.info(project.filesToString()); - logger.info("-----------------------------------------------"); + info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`); + info(project.filesToString()); + info("-----------------------------------------------"); counter++; } return counter; diff --git a/src/server/server.ts b/src/server/server.ts index 6b89019632f..47a858c0efd 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -139,8 +139,6 @@ namespace ts.server { class Logger implements server.Logger { private fd = -1; private seq = 0; - private inGroup = false; - private firstInGroup = true; constructor(private readonly logFilename: string, private readonly traceToConsole: boolean, @@ -170,22 +168,24 @@ namespace ts.server { } perftrc(s: string) { - this.msg(s, Msg.Perf); + this.msg(s, "Perf"); } info(s: string) { - this.msg(s, Msg.Info); + this.msg(s, "Info"); } - startGroup() { - this.inGroup = true; - this.firstInGroup = true; + err(s: string) { + this.msg(s, "Err"); } - endGroup() { - this.inGroup = false; + group(logGroupEntries: (log: (msg: string) => void) => void) { + let firstInGroup = false; + logGroupEntries(s => { + this.msg(s, "Info", /*inGroup*/ true, firstInGroup); + firstInGroup = false; + }); this.seq++; - this.firstInGroup = true; } loggingEnabled() { @@ -196,26 +196,32 @@ namespace ts.server { return this.loggingEnabled() && this.level >= level; } - msg(s: string, type: Msg.Types = Msg.Err) { - if (this.fd >= 0 || this.traceToConsole) { - s = `[${nowString()}] ${s}\n`; + private msg(s: string, type: string, inGroup = false, firstInGroup = false) { + if (!this.canWrite) return; + + s = `[${nowString()}] ${s}\n`; + if (!inGroup || firstInGroup) { const prefix = Logger.padStringRight(type + " " + this.seq.toString(), " "); - if (this.firstInGroup) { - s = prefix + s; - this.firstInGroup = false; - } - if (!this.inGroup) { - this.seq++; - this.firstInGroup = true; - } - if (this.fd >= 0) { - const buf = new Buffer(s); - // tslint:disable-next-line no-null-keyword - fs.writeSync(this.fd, buf, 0, buf.length, /*position*/ null); - } - if (this.traceToConsole) { - console.warn(s); - } + s = prefix + s; + } + this.write(s); + if (!inGroup) { + this.seq++; + } + } + + private get canWrite() { + return this.fd >= 0 || this.traceToConsole; + } + + private write(s: string) { + if (this.fd >= 0) { + const buf = new Buffer(s); + // tslint:disable-next-line no-null-keyword + fs.writeSync(this.fd, buf, 0, buf.length, /*position*/ null); + } + if (this.traceToConsole) { + console.warn(s); } } } diff --git a/src/server/session.ts b/src/server/session.ts index d8e0f695def..3c9c1b714de 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -366,7 +366,7 @@ namespace ts.server { msg += "\n" + (err).stack; } } - this.logger.msg(msg, Msg.Err); + this.logger.err(msg); } public send(msg: protocol.Message) { @@ -1946,7 +1946,7 @@ namespace ts.server { return this.executeWithRequestId(request.seq, () => handler(request)); } else { - this.logger.msg(`Unrecognized JSON command: ${JSON.stringify(request)}`, Msg.Err); + this.logger.err(`Unrecognized JSON command: ${JSON.stringify(request)}`); this.output(undefined, CommandNames.Unknown, request.seq, `Unrecognized JSON command: ${request.command}`); return { responseRequired: false }; } diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 5efb20d074f..0d4bc101ff6 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -17,22 +17,11 @@ namespace ts.server { loggingEnabled(): boolean; perftrc(s: string): void; info(s: string): void; - startGroup(): void; - endGroup(): void; - msg(s: string, type?: Msg.Types): void; + err(s: string): void; + group(logGroupEntries: (log: (msg: string) => void) => void): void; getLogFileName(): string; } - export namespace Msg { - export type Err = "Err"; - export const Err: Err = "Err"; - export type Info = "Info"; - export const Info: Info = "Info"; - export type Perf = "Perf"; - export const Perf: Perf = "Perf"; - export type Types = Err | Info | Perf; - } - function getProjectRootPath(project: Project): Path { switch (project.projectKind) { case ProjectKind.Configured: From 7ff1d8e797afee9b20ce233a1fbe15a19af2f56c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 8 Aug 2017 11:25:32 -0700 Subject: [PATCH 47/50] Add specific weak type error for callable types "Did you mean to call it?" --- src/compiler/checker.ts | 10 +++++++++- src/compiler/diagnosticMessages.json | 4 ++++ tests/baselines/reference/weakType.errors.txt | 18 +++++++++--------- tests/baselines/reference/weakType.js | 6 +++--- tests/cases/compiler/weakType.ts | 4 ++-- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4dd2058d4d3..2b910e6abb8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8944,7 +8944,15 @@ namespace ts { isWeakType(target) && !hasCommonProperties(source, target)) { if (reportErrors) { - reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, typeToString(source), typeToString(target)); + const calls = getSignaturesOfType(source, SignatureKind.Call); + const constructs = getSignaturesOfType(source, SignatureKind.Construct); + if (calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, /*reportErrors*/ false) || + constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, /*reportErrors*/ false)) { + reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, typeToString(source), typeToString(target)); + } + else { + reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, typeToString(source), typeToString(target)); + } } return Ternary.False; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 3b4b0ddf667..03164c54ffd 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1908,6 +1908,10 @@ "category": "Error", "code": 2559 }, + "Value of type '{0}' has no properties in common with type '{1}'. Did you mean to call it?": { + "category": "Error", + "code": 2560 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 diff --git a/tests/baselines/reference/weakType.errors.txt b/tests/baselines/reference/weakType.errors.txt index b08dcc33980..ffc1d237593 100644 --- a/tests/baselines/reference/weakType.errors.txt +++ b/tests/baselines/reference/weakType.errors.txt @@ -1,6 +1,6 @@ -tests/cases/compiler/weakType.ts(15,13): error TS2559: Type '() => { timeout: number; }' has no properties in common with type 'Settings'. -tests/cases/compiler/weakType.ts(16,13): error TS2559: Type '() => void' has no properties in common with type 'Settings'. -tests/cases/compiler/weakType.ts(17,13): error TS2559: Type 'CtorOnly' has no properties in common with type 'Settings'. +tests/cases/compiler/weakType.ts(15,13): error TS2560: Value of type '() => { timeout: number; }' has no properties in common with type 'Settings'. Did you mean to call it? +tests/cases/compiler/weakType.ts(16,13): error TS2560: Value of type '() => { timeout: number; }' has no properties in common with type 'Settings'. Did you mean to call it? +tests/cases/compiler/weakType.ts(17,13): error TS2560: Value of type 'CtorOnly' has no properties in common with type 'Settings'. Did you mean to call it? tests/cases/compiler/weakType.ts(18,13): error TS2559: Type '12' has no properties in common with type 'Settings'. tests/cases/compiler/weakType.ts(19,13): error TS2559: Type '"completely wrong"' has no properties in common with type 'Settings'. tests/cases/compiler/weakType.ts(20,13): error TS2559: Type 'false' has no properties in common with type 'Settings'. @@ -21,20 +21,20 @@ tests/cases/compiler/weakType.ts(62,5): error TS2322: Type '{ properties: { wron return { timeout: 1000 }; } interface CtorOnly { - new(s: string): string + new(s: string): { timeout: 1000 } } function doSomething(settings: Settings) { /* ... */ } // forgot to call `getDefaultSettings` doSomething(getDefaultSettings); ~~~~~~~~~~~~~~~~~~ -!!! error TS2559: Type '() => { timeout: number; }' has no properties in common with type 'Settings'. - doSomething(() => { }); - ~~~~~~~~~ -!!! error TS2559: Type '() => void' has no properties in common with type 'Settings'. +!!! error TS2560: Value of type '() => { timeout: number; }' has no properties in common with type 'Settings'. Did you mean to call it? + doSomething(() => ({ timeout: 1000 })); + ~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2560: Value of type '() => { timeout: number; }' has no properties in common with type 'Settings'. Did you mean to call it? doSomething(null as CtorOnly); ~~~~~~~~~~~~~~~~ -!!! error TS2559: Type 'CtorOnly' has no properties in common with type 'Settings'. +!!! error TS2560: Value of type 'CtorOnly' has no properties in common with type 'Settings'. Did you mean to call it? doSomething(12); ~~ !!! error TS2559: Type '12' has no properties in common with type 'Settings'. diff --git a/tests/baselines/reference/weakType.js b/tests/baselines/reference/weakType.js index 999269384dc..2a1dc4ca0e4 100644 --- a/tests/baselines/reference/weakType.js +++ b/tests/baselines/reference/weakType.js @@ -8,13 +8,13 @@ function getDefaultSettings() { return { timeout: 1000 }; } interface CtorOnly { - new(s: string): string + new(s: string): { timeout: 1000 } } function doSomething(settings: Settings) { /* ... */ } // forgot to call `getDefaultSettings` doSomething(getDefaultSettings); -doSomething(() => { }); +doSomething(() => ({ timeout: 1000 })); doSomething(null as CtorOnly); doSomething(12); doSomething('completely wrong'); @@ -71,7 +71,7 @@ function getDefaultSettings() { function doSomething(settings) { } // forgot to call `getDefaultSettings` doSomething(getDefaultSettings); -doSomething(function () { }); +doSomething(function () { return ({ timeout: 1000 }); }); doSomething(null); doSomething(12); doSomething('completely wrong'); diff --git a/tests/cases/compiler/weakType.ts b/tests/cases/compiler/weakType.ts index 8fda5df9166..08c9d95e672 100644 --- a/tests/cases/compiler/weakType.ts +++ b/tests/cases/compiler/weakType.ts @@ -7,13 +7,13 @@ function getDefaultSettings() { return { timeout: 1000 }; } interface CtorOnly { - new(s: string): string + new(s: string): { timeout: 1000 } } function doSomething(settings: Settings) { /* ... */ } // forgot to call `getDefaultSettings` doSomething(getDefaultSettings); -doSomething(() => { }); +doSomething(() => ({ timeout: 1000 })); doSomething(null as CtorOnly); doSomething(12); doSomething('completely wrong'); From 85f59098d3e16010bff6f82a1e9b7ee33b9a6859 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 8 Aug 2017 11:38:41 -0700 Subject: [PATCH 48/50] validateSpecs: Use array helpers (#17275) * validateSpecs: Use array helpers * Make filter predicate smaller * forEach -> for-of --- src/compiler/commandLineParser.ts | 34 ++++++++++++++++--------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index b0cb7f94029..e82e70dc764 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -2011,23 +2011,13 @@ namespace ts { } function validateSpecs(specs: ReadonlyArray, errors: Push, allowTrailingRecursion: boolean, jsonSourceFile: JsonSourceFile, specKey: string) { - const validSpecs: string[] = []; - for (const spec of specs) { - if (!allowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) { - errors.push(createDiagnostic(Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec)); + return specs.filter(spec => { + const diag = specToDiagnostic(spec, allowTrailingRecursion); + if (diag !== undefined) { + errors.push(createDiagnostic(diag, spec)); } - else if (invalidMultipleRecursionPatterns.test(spec)) { - errors.push(createDiagnostic(Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, spec)); - } - else if (invalidDotDotAfterRecursiveWildcardPattern.test(spec)) { - errors.push(createDiagnostic(Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec)); - } - else { - validSpecs.push(spec); - } - } - - return validSpecs; + return diag === undefined; + }); function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic { if (jsonSourceFile && jsonSourceFile.jsonObject) { @@ -2045,6 +2035,18 @@ namespace ts { } } + function specToDiagnostic(spec: string, allowTrailingRecursion: boolean): ts.DiagnosticMessage | undefined { + if (!allowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) { + return Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0; + } + else if (invalidMultipleRecursionPatterns.test(spec)) { + return Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0; + } + else if (invalidDotDotAfterRecursiveWildcardPattern.test(spec)) { + return Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0; + } + } + /** * Gets directories in a set of include patterns that should be watched for changes. */ From af20adb13732ee1e50d38e397699016b85d1f84d Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 8 Aug 2017 13:06:12 -0700 Subject: [PATCH 49/50] Add tests for #15358 (#17664) --- ...ckTypePredicateForRedundantProperties.errors.txt | 13 +++++++++++++ .../checkTypePredicateForRedundantProperties.js | 10 ++++++++++ .../checkTypePredicateForRedundantProperties.ts | 3 +++ 3 files changed, 26 insertions(+) create mode 100644 tests/baselines/reference/checkTypePredicateForRedundantProperties.errors.txt create mode 100644 tests/baselines/reference/checkTypePredicateForRedundantProperties.js create mode 100644 tests/cases/compiler/checkTypePredicateForRedundantProperties.ts diff --git a/tests/baselines/reference/checkTypePredicateForRedundantProperties.errors.txt b/tests/baselines/reference/checkTypePredicateForRedundantProperties.errors.txt new file mode 100644 index 00000000000..a5cb9b0a098 --- /dev/null +++ b/tests/baselines/reference/checkTypePredicateForRedundantProperties.errors.txt @@ -0,0 +1,13 @@ +tests/cases/compiler/checkTypePredicateForRedundantProperties.ts(1,35): error TS2300: Duplicate identifier 'a'. +tests/cases/compiler/checkTypePredicateForRedundantProperties.ts(1,46): error TS2300: Duplicate identifier 'a'. + + +==== tests/cases/compiler/checkTypePredicateForRedundantProperties.ts (2 errors) ==== + function addProp2(x: any): x is { a: string; a: string; } { + ~ +!!! error TS2300: Duplicate identifier 'a'. + ~ +!!! error TS2300: Duplicate identifier 'a'. + return true; + } + \ No newline at end of file diff --git a/tests/baselines/reference/checkTypePredicateForRedundantProperties.js b/tests/baselines/reference/checkTypePredicateForRedundantProperties.js new file mode 100644 index 00000000000..8f7be2bfbbc --- /dev/null +++ b/tests/baselines/reference/checkTypePredicateForRedundantProperties.js @@ -0,0 +1,10 @@ +//// [checkTypePredicateForRedundantProperties.ts] +function addProp2(x: any): x is { a: string; a: string; } { + return true; +} + + +//// [checkTypePredicateForRedundantProperties.js] +function addProp2(x) { + return true; +} diff --git a/tests/cases/compiler/checkTypePredicateForRedundantProperties.ts b/tests/cases/compiler/checkTypePredicateForRedundantProperties.ts new file mode 100644 index 00000000000..35222f1e9db --- /dev/null +++ b/tests/cases/compiler/checkTypePredicateForRedundantProperties.ts @@ -0,0 +1,3 @@ +function addProp2(x: any): x is { a: string; a: string; } { + return true; +} From a46d6bde974ead83b5bb4bfbfa76d085d391137d Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 8 Aug 2017 13:07:27 -0700 Subject: [PATCH 50/50] Add a seperate cache for the all attributes version of the jsx attributes type (#17620) --- src/compiler/checker.ts | 12 +++++------- src/compiler/types.ts | 1 + 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 579a234cb95..c164833ca47 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14107,11 +14107,8 @@ namespace ts { */ function resolveCustomJsxElementAttributesType(openingLikeElement: JsxOpeningLikeElement, shouldIncludeAllStatelessAttributesType: boolean, - elementType?: Type, + elementType: Type = checkExpression(openingLikeElement.tagName), elementClassType?: Type): Type { - if (!elementType) { - elementType = checkExpression(openingLikeElement.tagName); - } if (elementType.flags & TypeFlags.Union) { const types = (elementType as UnionType).types; @@ -14245,11 +14242,12 @@ namespace ts { */ function getCustomJsxElementAttributesType(node: JsxOpeningLikeElement, shouldIncludeAllStatelessAttributesType: boolean): Type { const links = getNodeLinks(node); - if (!links.resolvedJsxElementAttributesType) { + const linkLocation = shouldIncludeAllStatelessAttributesType ? "resolvedJsxElementAllAttributesType" : "resolvedJsxElementAttributesType"; + if (!links[linkLocation]) { const elemClassType = getJsxGlobalElementClassType(); - return links.resolvedJsxElementAttributesType = resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, /*elementType*/ undefined, elemClassType); + return links[linkLocation] = resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, /*elementType*/ undefined, elemClassType); } - return links.resolvedJsxElementAttributesType; + return links[linkLocation]; } /** diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3b57608abc5..0eb7f03e3ec 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3067,6 +3067,7 @@ namespace ts { hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element + resolvedJsxElementAllAttributesType?: Type; // resolved all element attributes type of a JSX openinglike element hasSuperCall?: boolean; // recorded result when we try to find super-call. We only try to find one if this flag is undefined, indicating that we haven't made an attempt. superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing switchTypes?: Type[]; // Cached array of switch case expression types