From 5b255243c995baaff5a4011238034925be4acce9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 11 Aug 2014 12:21:26 -0700 Subject: [PATCH 01/46] Adding support for tuple types (e.g. [number, string]) --- src/compiler/checker.ts | 129 +++++++++++++++++++++++------ src/compiler/parser.ts | 20 +++++ src/compiler/types.ts | 17 +++- tests/cases/compiler/tupleTypes.ts | 53 ++++++++++++ 4 files changed, 189 insertions(+), 30 deletions(-) create mode 100644 tests/cases/compiler/tupleTypes.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 13781b271c9..3e8cd2cd435 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -56,6 +56,7 @@ module ts { var globalBooleanType: ObjectType; var globalRegExpType: ObjectType; + var tupleTypes: Map = {}; var stringLiteralTypes: Map = {}; var fullTypeCheck = false; @@ -619,15 +620,13 @@ module ts { } function isOptionalProperty(propertySymbol: Symbol): boolean { - if (propertySymbol.flags & SymbolFlags.Prototype) { - return false; - } // class C { // constructor(public x?) { } // } // // x is an optional parameter, but it is a required property. - return (propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark) && propertySymbol.valueDeclaration.kind !== SyntaxKind.Parameter; + return propertySymbol.valueDeclaration && propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark && + propertySymbol.valueDeclaration.kind !== SyntaxKind.Parameter; } function forEachSymbolTableInScope(enclosingDeclaration: Node, callback: (symbolTable: SymbolTable) => T): T { @@ -843,6 +842,9 @@ module ts { else if (type.flags & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.Enum | TypeFlags.TypeParameter)) { writer.writeSymbol(type.symbol, enclosingDeclaration, SymbolFlags.Type); } + else if (type.flags & TypeFlags.Tuple) { + writeTupleType(type); + } else if (type.flags & TypeFlags.Anonymous) { writeAnonymousType(type, allowFunctionOrConstructorTypeLiteral); } @@ -855,6 +857,15 @@ module ts { } } + function writeTypeList(types: Type[]) { + for (var i = 0; i < types.length; i++) { + if (i > 0) { + writer.write(", "); + } + writeType(types[i], /*allowFunctionOrConstructorTypeLiteral*/ true); + } + } + function writeTypeReference(type: TypeReference) { if (type.target === globalArrayType && !(flags & TypeFormatFlags.WriteArrayAsGenericType)) { // If we are writing array element type the arrow style signatures are not allowed as @@ -865,16 +876,17 @@ module ts { else { writer.writeSymbol(type.target.symbol, enclosingDeclaration, SymbolFlags.Type); writer.write("<"); - for (var i = 0; i < type.typeArguments.length; i++) { - if (i > 0) { - writer.write(", "); - } - writeType(type.typeArguments[i], /*allowFunctionOrConstructorTypeLiteral*/ true); - } + writeTypeList(type.typeArguments); writer.write(">"); } } + function writeTupleType(type: TupleType) { + writer.write("["); + writeTypeList(type.elementTypes); + writer.write("]"); + } + function writeAnonymousType(type: ObjectType, allowFunctionOrConstructorTypeLiteral: boolean) { // Always use 'typeof T' for type of class, enum, and module objects if (type.symbol && type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { @@ -1649,6 +1661,23 @@ module ts { return [createSignature(undefined, classType.typeParameters, emptyArray, classType, 0, false, false)]; } + function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable { + var members: SymbolTable = {}; + for (var i = 0; i < memberTypes.length; i++) { + var symbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "" + i); + symbol.type = memberTypes[i]; + members[i] = symbol; + } + return members; + } + + function resolveTupleTypeMembers(type: TupleType) { + var arrayType = resolveObjectTypeMembers(createArrayType(getBestCommonType(type.elementTypes))); + var members = createTupleTypeMemberSymbols(type.elementTypes); + addInheritedMembers(members, arrayType.properties); + setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType); + } + function resolveAnonymousTypeMembers(type: ObjectType) { var symbol = type.symbol; var members = emptySymbols; @@ -1682,6 +1711,9 @@ module ts { else if (type.flags & TypeFlags.Anonymous) { resolveAnonymousTypeMembers(type); } + else if (type.flags & TypeFlags.Tuple) { + resolveTupleTypeMembers(type); + } else { resolveTypeReferenceMembers(type); } @@ -2123,6 +2155,24 @@ module ts { return links.resolvedType; } + function createTupleType(elementTypes: Type[]) { + var id = getTypeListId(elementTypes); + var type = tupleTypes[id]; + if (!type) { + type = tupleTypes[id] = createObjectType(TypeFlags.Tuple); + type.elementTypes = elementTypes; + } + return type; + } + + function getTypeFromTupleTypeNode(node: TupleTypeNode): Type { + var links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = createTupleType(map(node.elementTypes, t => getTypeFromTypeNode(t))); + } + return links.resolvedType; + } + function getTypeFromTypeLiteralNode(node: TypeLiteralNode): Type { var links = getNodeLinks(node); if (!links.resolvedType) { @@ -2172,6 +2222,8 @@ module ts { return getTypeFromTypeQueryNode(node); case SyntaxKind.ArrayType: return getTypeFromArrayTypeNode(node); + case SyntaxKind.TupleType: + return getTypeFromTupleTypeNode(node); case SyntaxKind.TypeLiteral: return getTypeFromTypeLiteralNode(node); default: @@ -2327,6 +2379,9 @@ module ts { if (type.flags & TypeFlags.Reference) { return createTypeReference((type).target, instantiateList((type).typeArguments, mapper, instantiateType)); } + if (type.flags & TypeFlags.Tuple) { + return createTupleType(instantiateList((type).elementTypes, mapper, instantiateType)); + } } return type; } @@ -3015,20 +3070,16 @@ module ts { while (isArrayType(type)) { type = (type).typeArguments[0]; } - return type; } function getWidenedTypeOfArrayLiteral(type: Type): Type { var elementType = (type).typeArguments[0]; var widenedType = getWidenedType(elementType); - type = elementType !== widenedType ? createArrayType(widenedType) : type; - return type; } - /* If we are widening on a literal, then we may need to the 'node' parameter for reporting purposes */ function getWidenedType(type: Type): Type { if (type.flags & (TypeFlags.Undefined | TypeFlags.Null)) { return anyType; @@ -3125,9 +3176,9 @@ module ts { inferFromTypes(sourceTypes[i], targetTypes[i]); } } - else if (source.flags & TypeFlags.ObjectType && (target.flags & TypeFlags.Reference || (target.flags & TypeFlags.Anonymous) && - target.symbol && target.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral))) { - // If source is an object type, and target is a type reference, the type of a method, or a type literal, infer from members + else if (source.flags & TypeFlags.ObjectType && (target.flags & (TypeFlags.Reference | TypeFlags.Tuple) || + (target.flags & TypeFlags.Anonymous) && target.symbol && target.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral))) { + // If source is an object type, and target is a type reference, a tuple type, the type of a method, or a type literal, infer from members if (!isInProcess(source, target) && isWithinDepthLimit(source, sourceStack) && isWithinDepthLimit(target, targetStack)) { if (depth === 0) { sourceStack = []; @@ -3574,7 +3625,19 @@ module ts { function getContextualTypeForElementExpression(node: Expression): Type { var arrayLiteral = node.parent; var type = getContextualType(arrayLiteral); - return type ? getIndexTypeOfType(type, IndexKind.Number) : undefined; + if (type) { + if (type.flags & TypeFlags.Tuple) { + var index = indexOf(arrayLiteral.elements, node); + if (index >= 0) { + var prop = getPropertyOfType(type, "" + index); + if (prop) { + return getTypeOfSymbol(prop); + } + } + } + return getIndexTypeOfType(type, IndexKind.Number); + } + return undefined; } function getContextualTypeForConditionalOperand(node: Expression): Type { @@ -3633,17 +3696,23 @@ module ts { } function checkArrayLiteral(node: ArrayLiteral, contextualMapper?: TypeMapper): Type { + var contextualType = getContextualType(node); + var isTupleLiteral = contextualType && (contextualType.flags & TypeFlags.Tuple) !== 0; var elementTypes: Type[] = []; forEach(node.elements, element => { - if (element.kind !== SyntaxKind.OmittedExpression) { - var type = checkExpression(element, contextualMapper); - if (!contains(elementTypes, type)) elementTypes.push(type); + var type = element.kind !== SyntaxKind.OmittedExpression ? checkExpression(element, contextualMapper) : undefinedType; + if (isTupleLiteral || !contains(elementTypes, type)) { + elementTypes.push(type); } }); - var contextualType = isInferentialContext(contextualMapper) ? undefined : getContextualType(node); - var contextualElementType = contextualType && getIndexTypeOfType(contextualType, IndexKind.Number); + if (isTupleLiteral) { + return createTupleType(elementTypes); + } + var contextualElementType = contextualType && !isInferentialContext(contextualMapper) ? getIndexTypeOfType(contextualType, IndexKind.Number) : undefined; var elementType = getBestCommonType(elementTypes, contextualElementType, true); - if (!elementType) elementType = elementTypes.length ? emptyObjectType : undefinedType; + if (!elementType) { + elementType = elementTypes.length ? emptyObjectType : undefinedType; + } return createArrayType(elementType); } @@ -3711,11 +3780,11 @@ module ts { } function getDeclarationKindFromSymbol(s: Symbol) { - return s.flags & SymbolFlags.Prototype ? SyntaxKind.Property : s.valueDeclaration.kind; + return s.valueDeclaration ? s.valueDeclaration.kind : SyntaxKind.Property; } function getDeclarationFlagsFromSymbol(s: Symbol) { - return s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : s.valueDeclaration.flags; + return s.valueDeclaration ? s.valueDeclaration.flags : s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : 0; } function checkPropertyAccess(node: PropertyAccess) { @@ -4991,7 +5060,11 @@ module ts { } function checkArrayType(node: ArrayTypeNode) { - getTypeFromArrayTypeNode(node); + checkSourceElement(node.elementType); + } + + function checkTupleType(node: TupleTypeNode) { + forEach(node.elementTypes, checkSourceElement); } function isPrivateWithinAmbient(node: Node): boolean { @@ -6197,6 +6270,8 @@ module ts { return checkTypeLiteral(node); case SyntaxKind.ArrayType: return checkArrayType(node); + case SyntaxKind.TupleType: + return checkTupleType(node); case SyntaxKind.FunctionDeclaration: return checkFunctionDeclaration(node); case SyntaxKind.Block: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 91a30143047..95182051e58 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -201,6 +201,8 @@ module ts { return children((node).members); case SyntaxKind.ArrayType: return child((node).elementType); + case SyntaxKind.TupleType: + return children((node).elementTypes); case SyntaxKind.ArrayLiteral: return children((node).elements); case SyntaxKind.ObjectLiteral: @@ -352,6 +354,7 @@ module ts { Parameters, // Parameters in parameter list TypeParameters, // Type parameters in type parameter list TypeArguments, // Type arguments in type argument list + TupleElementTypes, // Element types in tuple element type list Count // Number of parsing contexts } @@ -379,6 +382,7 @@ module ts { case ParsingContext.Parameters: return Diagnostics.Parameter_declaration_expected; case ParsingContext.TypeParameters: return Diagnostics.Type_parameter_declaration_expected; case ParsingContext.TypeArguments: return Diagnostics.Type_argument_expected; + case ParsingContext.TupleElementTypes: return Diagnostics.Type_expected; } }; @@ -837,6 +841,7 @@ module ts { case ParsingContext.Parameters: return isParameter(); case ParsingContext.TypeArguments: + case ParsingContext.TupleElementTypes: return isType(); } @@ -872,6 +877,7 @@ module ts { // Tokens other than ')' are here for better error recovery return token === SyntaxKind.CloseParenToken || token === SyntaxKind.SemicolonToken; case ParsingContext.ArrayLiteralMembers: + case ParsingContext.TupleElementTypes: return token === SyntaxKind.CloseBracketToken; case ParsingContext.Parameters: // Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery @@ -1390,6 +1396,17 @@ module ts { return finishNode(node); } + function parseTupleType(): TupleTypeNode { + var node = createNode(SyntaxKind.TupleType); + var startTokenPos = scanner.getTokenPos(); + var startErrorCount = file.syntacticErrors.length; + node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); + if (!node.elementTypes.length && file.syntacticErrors.length === startErrorCount) { + grammarErrorAtPos(startTokenPos, scanner.getStartPos() - startTokenPos, Diagnostics.Type_argument_list_cannot_be_empty); + } + return finishNode(node); + } + function parseFunctionType(signatureKind: SyntaxKind): TypeLiteralNode { var node = createNode(SyntaxKind.TypeLiteral); var member = createNode(signatureKind); @@ -1420,6 +1437,8 @@ module ts { return parseTypeQuery(); case SyntaxKind.OpenBraceToken: return parseTypeLiteral(); + case SyntaxKind.OpenBracketToken: + return parseTupleType(); case SyntaxKind.OpenParenToken: case SyntaxKind.LessThanToken: return parseFunctionType(SyntaxKind.CallSignature); @@ -1443,6 +1462,7 @@ module ts { case SyntaxKind.VoidKeyword: case SyntaxKind.TypeOfKeyword: case SyntaxKind.OpenBraceToken: + case SyntaxKind.OpenBracketToken: case SyntaxKind.LessThanToken: case SyntaxKind.NewKeyword: return true; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5cb80687d80..272f5ec8a7f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -149,6 +149,7 @@ module ts { TypeQuery, TypeLiteral, ArrayType, + TupleType, // Expression ArrayLiteral, ObjectLiteral, @@ -316,6 +317,10 @@ module ts { elementType: TypeNode; } + export interface TupleTypeNode extends TypeNode { + elementTypes: NodeArray; + } + export interface StringLiteralTypeNode extends TypeNode { text: string; } @@ -791,13 +796,14 @@ module ts { Class = 0x00000400, // Class Interface = 0x00000800, // Interface Reference = 0x00001000, // Generic type reference - Anonymous = 0x00002000, // Anonymous - FromSignature = 0x00004000, // Created for signature assignment check + Tuple = 0x00002000, // Tuple + Anonymous = 0x00004000, // Anonymous + FromSignature = 0x00008000, // Created for signature assignment check Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null, StringLike = String | StringLiteral, NumberLike = Number | Enum, - ObjectType = Class | Interface | Reference | Anonymous + ObjectType = Class | Interface | Reference | Tuple | Anonymous } // Properties common to all types @@ -850,6 +856,11 @@ module ts { openReferenceChecks: Map; // Open type reference check cache } + export interface TupleType extends ObjectType { + elementTypes: Type[]; // Element types + baseArrayType: TypeReference; // Array where T is best common type of element types + } + // Resolved object type export interface ResolvedObjectType extends ObjectType { members: SymbolTable; // Properties by name diff --git a/tests/cases/compiler/tupleTypes.ts b/tests/cases/compiler/tupleTypes.ts new file mode 100644 index 00000000000..3b22c284cb1 --- /dev/null +++ b/tests/cases/compiler/tupleTypes.ts @@ -0,0 +1,53 @@ +var v1: []; // Error +var v2: [number]; +var v3: [number, string]; +var v4: [number, [string, string]]; + +var t: [number, string]; +var t0 = t[0]; // number +var t0: number; +var t1 = t[1]; // string +var t1: string; +var t2 = t[2]; // {} +var t2: {}; + +t = []; // Error +t = [1]; // Error +t = [1, "hello"]; // Ok +t = ["hello", 1]; // Error +t = [1, "hello", 2]; // Ok + +var tf: [string, (x: string) => number] = ["hello", x => x.length]; + +declare function ff(a: T, b: [T, (x: T) => U]): U; +var ff1 = ff("hello", ["foo", x => x.length]); +var ff1: number; + +function tuple2(item0: T0, item1: T1): [T0, T1]{ + return [item0, item1]; +} + +var tt = tuple2(1, "string"); +var tt0 = tt[0]; +var tt0: number; +var tt1 = tt[1]; +var tt1: string; +var tt2 = tt[2]; +var tt2: {}; + +tt = tuple2(1, undefined); +tt = [1, undefined]; +tt = [undefined, undefined]; +tt = []; // Error + +var a: number[]; +var a1: [number, string]; +var a2: [number, number]; +var a3: [number, {}]; +a = a1; // Error +a = a2; +a = a3; // Error +a1 = a2; // Error +a1 = a3; // Error +a3 = a1; +a3 = a2; From 3b1dbadb88b8d30c741c4a1cf482e46a64dfb5cc Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 11 Aug 2014 14:52:32 -0700 Subject: [PATCH 02/46] Addressing CR feedback, adding baselines. --- src/compiler/checker.ts | 7 +- .../baselines/reference/tupleTypes.errors.txt | 89 +++++++++++++++++++ .../baselines/reference/typeName1.errors.txt | 4 +- 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/tupleTypes.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3e8cd2cd435..8a1b9872411 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -625,7 +625,8 @@ module ts { // } // // x is an optional parameter, but it is a required property. - return propertySymbol.valueDeclaration && propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark && + return propertySymbol.valueDeclaration && + propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark && propertySymbol.valueDeclaration.kind !== SyntaxKind.Parameter; } @@ -2069,7 +2070,7 @@ module ts { if (type.flags & (TypeFlags.Class | TypeFlags.Interface) && type.flags & TypeFlags.Reference) { var typeParameters = (type).typeParameters; if (node.typeArguments && node.typeArguments.length === typeParameters.length) { - type = createTypeReference(type, map(node.typeArguments, t => getTypeFromTypeNode(t))); + type = createTypeReference(type, map(node.typeArguments, getTypeFromTypeNode)); } else { error(node, Diagnostics.Generic_type_0_requires_1_type_argument_s, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType), typeParameters.length); @@ -2168,7 +2169,7 @@ module ts { function getTypeFromTupleTypeNode(node: TupleTypeNode): Type { var links = getNodeLinks(node); if (!links.resolvedType) { - links.resolvedType = createTupleType(map(node.elementTypes, t => getTypeFromTypeNode(t))); + links.resolvedType = createTupleType(map(node.elementTypes, getTypeFromTypeNode)); } return links.resolvedType; } diff --git a/tests/baselines/reference/tupleTypes.errors.txt b/tests/baselines/reference/tupleTypes.errors.txt new file mode 100644 index 00000000000..b10a098b9a7 --- /dev/null +++ b/tests/baselines/reference/tupleTypes.errors.txt @@ -0,0 +1,89 @@ +==== tests/cases/compiler/tupleTypes.ts (9 errors) ==== + var v1: []; // Error + ~~ +!!! Type argument list cannot be empty. + var v2: [number]; + var v3: [number, string]; + var v4: [number, [string, string]]; + + var t: [number, string]; + var t0 = t[0]; // number + var t0: number; + var t1 = t[1]; // string + var t1: string; + var t2 = t[2]; // {} + var t2: {}; + + t = []; // Error + ~ +!!! Type '[]' is not assignable to type '[number, string]': +!!! Property '0' is missing in type '[]'. + t = [1]; // Error + ~ +!!! Type '[number]' is not assignable to type '[number, string]': +!!! Property '1' is missing in type '[number]'. + t = [1, "hello"]; // Ok + t = ["hello", 1]; // Error + ~ +!!! Type '[string, number]' is not assignable to type '[number, string]': +!!! Types of property '0' are incompatible: +!!! Type 'string' is not assignable to type 'number'. + t = [1, "hello", 2]; // Ok + + var tf: [string, (x: string) => number] = ["hello", x => x.length]; + + declare function ff(a: T, b: [T, (x: T) => U]): U; + var ff1 = ff("hello", ["foo", x => x.length]); + var ff1: number; + + function tuple2(item0: T0, item1: T1): [T0, T1]{ + return [item0, item1]; + } + + var tt = tuple2(1, "string"); + var tt0 = tt[0]; + var tt0: number; + var tt1 = tt[1]; + var tt1: string; + var tt2 = tt[2]; + var tt2: {}; + + tt = tuple2(1, undefined); + tt = [1, undefined]; + tt = [undefined, undefined]; + tt = []; // Error + ~~ +!!! Type '[]' is not assignable to type '[number, string]'. + + var a: number[]; + var a1: [number, string]; + var a2: [number, number]; + var a3: [number, {}]; + a = a1; // Error + ~ +!!! Type '[number, string]' is not assignable to type 'number[]': +!!! Types of property 'concat' are incompatible: +!!! Type '{ (...items: U[]): {}[]; (...items: {}[]): {}[]; }' is not assignable to type '{ (...items: U[]): number[]; (...items: number[]): number[]; }': +!!! Type '{}[]' is not assignable to type 'number[]': +!!! Type '{}' is not assignable to type 'number'. + a = a2; + a = a3; // Error + ~ +!!! Type '[number, {}]' is not assignable to type 'number[]': +!!! Types of property 'concat' are incompatible: +!!! Type '{ (...items: U[]): {}[]; (...items: {}[]): {}[]; }' is not assignable to type '{ (...items: U[]): number[]; (...items: number[]): number[]; }': +!!! Type '{}[]' is not assignable to type 'number[]': +!!! Type '{}' is not assignable to type 'number'. + a1 = a2; // Error + ~~ +!!! Type '[number, number]' is not assignable to type '[number, string]': +!!! Types of property '1' are incompatible: +!!! Type 'number' is not assignable to type 'string'. + a1 = a3; // Error + ~~ +!!! Type '[number, {}]' is not assignable to type '[number, string]': +!!! Types of property '1' are incompatible: +!!! Type '{}' is not assignable to type 'string'. + a3 = a1; + a3 = a2; + \ No newline at end of file diff --git a/tests/baselines/reference/typeName1.errors.txt b/tests/baselines/reference/typeName1.errors.txt index 26a744f7d2d..ac729f4724a 100644 --- a/tests/baselines/reference/typeName1.errors.txt +++ b/tests/baselines/reference/typeName1.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/compiler/typeName1.ts (16 errors) ==== +==== tests/cases/compiler/typeName1.ts (17 errors) ==== interface I { k; } @@ -55,6 +55,8 @@ ~~~ !!! Type 'number' is not assignable to type '{ z: I; x: boolean; y: (s: string) => boolean; w: { (): boolean; [x: string]: { x: any; y: any; }; [x: number]: { x: any; y: any; }; z: I; }; }[][]': !!! Property 'concat' is missing in type 'Number'. + ~~~~ +!!! Property 'z' of type 'I' is not assignable to string index type '{ x: any; y: any; }'. var x13:{ new(): number; new(n:number):number; x: string; w: {y: number;}; (): {}; } = 3; ~~~ !!! Type 'number' is not assignable to type '{ (): {}; new (): number; new (n: number): number; x: string; w: { y: number; }; }': From ef52312644b4830ad4cb6d5bd7c5273f25e292fb Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 13 Aug 2014 07:15:13 -0700 Subject: [PATCH 03/46] Addressing CR feedback. --- src/compiler/checker.ts | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8a1b9872411..440408d1f91 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3549,6 +3549,10 @@ module ts { return undefined; } + // In a variable, parameter or property declaration with a type annotation, the contextual type of an initializer + // expression is the type of the variable, parameter or property. In a parameter declaration of a contextually + // typed function expression, the contextual type of an initializer expression is the contextual type of the + // parameter. function getContextualTypeForInitializerExpression(node: Expression): Type { var declaration = node.parent; if (node === declaration.initializer) { @@ -3580,6 +3584,7 @@ module ts { return undefined; } + // In a typed function call, an argument expression is contextually typed by the type of the corresponding parameter. function getContextualTypeForArgument(node: Expression): Type { var callExpression = node.parent; var argIndex = indexOf(callExpression.arguments, node); @@ -3594,11 +3599,14 @@ module ts { var binaryExpression = node.parent; var operator = binaryExpression.operator; if (operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment) { + // In an assignment expression, the right operand is contextually typed by the type of the left operand. if (node === binaryExpression.right) { return checkExpression(binaryExpression.left); } } else if (operator === SyntaxKind.BarBarToken) { + // When an || expression has a contextual type, the operands are contextually typed by that type. When an || + // expression has no contextual type, the right operand is contextually typed by the type of the left operand. var type = getContextualType(binaryExpression); if (!type && node === binaryExpression.right) { type = checkExpression(binaryExpression.left); @@ -3608,6 +3616,9 @@ module ts { return undefined; } + // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of + // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one + // exists. Otherwise, it is the type of the string index signature in T, if one exists. function getContextualTypeForPropertyExpression(node: Expression): Type { var declaration = node.parent; var objectLiteral = declaration.parent; @@ -3623,17 +3634,18 @@ module ts { return undefined; } + // In an array literal contextually typed by a type T, the contextual type of an element expression is the corresponding + // tuple element type in T, if one exists and T is a tuple type. Otherwise, it is the type of the numeric index signature + // in T, if one exists. function getContextualTypeForElementExpression(node: Expression): Type { var arrayLiteral = node.parent; var type = getContextualType(arrayLiteral); if (type) { if (type.flags & TypeFlags.Tuple) { var index = indexOf(arrayLiteral.elements, node); - if (index >= 0) { - var prop = getPropertyOfType(type, "" + index); - if (prop) { - return getTypeOfSymbol(prop); - } + var prop = getPropertyOfType(type, "" + index); + if (prop) { + return getTypeOfSymbol(prop); } } return getIndexTypeOfType(type, IndexKind.Number); @@ -3641,11 +3653,14 @@ module ts { return undefined; } + // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type. function getContextualTypeForConditionalOperand(node: Expression): Type { var conditional = node.parent; return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional) : undefined; } + // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily + // be "pushed" onto a node using the contextualType property. function getContextualType(node: Expression): Type { if (node.contextualType) { return node.contextualType; @@ -3780,6 +3795,8 @@ module ts { } } + // If a symbol is a synthesized symbol with no value declaration, we assume it is a property. Example of this are the synthesized + // '.prototype' property as well as synthesized tuple index properties. function getDeclarationKindFromSymbol(s: Symbol) { return s.valueDeclaration ? s.valueDeclaration.kind : SyntaxKind.Property; } From 92b367741b0085b4abb5f6eddc10dd3708a80ce4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 13 Aug 2014 11:13:24 -0700 Subject: [PATCH 04/46] Adding error message for empty tuple types. --- src/compiler/diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 10 +++++++--- src/compiler/parser.ts | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index cc789bcca11..0f13f5e6838 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -106,6 +106,7 @@ module ts { An_object_literal_cannot_have_property_and_accessor_with_the_same_name: { code: 1119, category: DiagnosticCategory.Error, key: "An object literal cannot have property and accessor with the same name." }, An_export_assignment_cannot_have_modifiers: { code: 1120, category: DiagnosticCategory.Error, key: "An export assignment cannot have modifiers." }, Octal_literals_are_not_allowed_in_strict_mode: { code: 1121, category: DiagnosticCategory.Error, key: "Octal literals are not allowed in strict mode." }, + A_tuple_type_element_list_cannot_be_empty: { code: 1122, category: DiagnosticCategory.Error, key: "A tuple type element list cannot be empty." }, Duplicate_identifier_0: { code: 2000, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." }, Extends_clause_of_exported_class_0_has_or_is_using_private_name_1: { code: 2018, category: DiagnosticCategory.Error, key: "Extends clause of exported class '{0}' has or is using private name '{1}'." }, Implements_clause_of_exported_class_0_has_or_is_using_private_name_1: { code: 2019, category: DiagnosticCategory.Error, key: "Implements clause of exported class '{0}' has or is using private name '{1}'." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d60fd2e5c4f..b913f76f58c 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -416,6 +416,10 @@ "category": "Error", "code": 1121 }, + "A tuple type element list cannot be empty.": { + "category": "Error", + "code": 1122 + }, "Duplicate identifier '{0}'.": { "category": "Error", "code": 2000 @@ -1042,7 +1046,7 @@ "File change detected. Compiling...": { "category": "Message", "code": 6032 - }, + }, "STRING": { "category": "Message", "code": 6033 @@ -1078,8 +1082,8 @@ "Additional locations:": { "category": "Message", "code": 6041 - }, - "Compilation complete. Watching for file changes.": { + }, + "Compilation complete. Watching for file changes.": { "category": "Message", "code": 6042 }, diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 95182051e58..4a24d1c0265 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1402,7 +1402,7 @@ module ts { var startErrorCount = file.syntacticErrors.length; node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); if (!node.elementTypes.length && file.syntacticErrors.length === startErrorCount) { - grammarErrorAtPos(startTokenPos, scanner.getStartPos() - startTokenPos, Diagnostics.Type_argument_list_cannot_be_empty); + grammarErrorAtPos(startTokenPos, scanner.getStartPos() - startTokenPos, Diagnostics.A_tuple_type_element_list_cannot_be_empty); } return finishNode(node); } From f0b33b345ba6d7aae5bc2168fe6b3796f2ff5556 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 13 Aug 2014 15:45:43 -0700 Subject: [PATCH 05/46] Accepting new baselines. --- tests/baselines/reference/tupleTypes.errors.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/baselines/reference/tupleTypes.errors.txt b/tests/baselines/reference/tupleTypes.errors.txt index b10a098b9a7..69c6a9073ab 100644 --- a/tests/baselines/reference/tupleTypes.errors.txt +++ b/tests/baselines/reference/tupleTypes.errors.txt @@ -1,7 +1,7 @@ ==== tests/cases/compiler/tupleTypes.ts (9 errors) ==== var v1: []; // Error ~~ -!!! Type argument list cannot be empty. +!!! A tuple type element list cannot be empty. var v2: [number]; var v3: [number, string]; var v4: [number, [string, string]]; From c0e802deb5e83232d05978aa0995771641a034ef Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 16 Aug 2014 11:15:31 -0700 Subject: [PATCH 06/46] Accepting new baselines after merge. --- tests/baselines/reference/tupleTypes.errors.txt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/baselines/reference/tupleTypes.errors.txt b/tests/baselines/reference/tupleTypes.errors.txt index 69c6a9073ab..44cb22daf0f 100644 --- a/tests/baselines/reference/tupleTypes.errors.txt +++ b/tests/baselines/reference/tupleTypes.errors.txt @@ -62,18 +62,16 @@ a = a1; // Error ~ !!! Type '[number, string]' is not assignable to type 'number[]': -!!! Types of property 'concat' are incompatible: -!!! Type '{ (...items: U[]): {}[]; (...items: {}[]): {}[]; }' is not assignable to type '{ (...items: U[]): number[]; (...items: number[]): number[]; }': -!!! Type '{}[]' is not assignable to type 'number[]': -!!! Type '{}' is not assignable to type 'number'. +!!! Types of property 'pop' are incompatible: +!!! Type '() => {}' is not assignable to type '() => number': +!!! Type '{}' is not assignable to type 'number'. a = a2; a = a3; // Error ~ !!! Type '[number, {}]' is not assignable to type 'number[]': -!!! Types of property 'concat' are incompatible: -!!! Type '{ (...items: U[]): {}[]; (...items: {}[]): {}[]; }' is not assignable to type '{ (...items: U[]): number[]; (...items: number[]): number[]; }': -!!! Type '{}[]' is not assignable to type 'number[]': -!!! Type '{}' is not assignable to type 'number'. +!!! Types of property 'pop' are incompatible: +!!! Type '() => {}' is not assignable to type '() => number': +!!! Type '{}' is not assignable to type 'number'. a1 = a2; // Error ~~ !!! Type '[number, number]' is not assignable to type '[number, string]': From 63b83e7c3fdd673ecdbfb6a4a533b0c963e55773 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 16 Aug 2014 12:06:51 -0700 Subject: [PATCH 07/46] Contextual typing of array literals is now based on the presence or absence of numerically named properties and doesn't directly test for tuple types. --- src/compiler/checker.ts | 35 ++++++++++--------- src/compiler/core.ts | 26 ++++++++------ .../baselines/reference/tupleTypes.errors.txt | 6 ++-- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c91489b3d88..6a2fab1312b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3758,19 +3758,17 @@ module ts { return undefined; } - // In an array literal contextually typed by a type T, the contextual type of an element expression is the corresponding - // tuple element type in T, if one exists and T is a tuple type. Otherwise, it is the type of the numeric index signature - // in T, if one exists. + // In an array literal contextually typed by a type T, the contextual type of an element expression at index N is + // the type of the property with the numeric name N in T, if one exists. Otherwise, it is the type of the numeric + // index signature in T, if one exists. function getContextualTypeForElementExpression(node: Expression): Type { var arrayLiteral = node.parent; var type = getContextualType(arrayLiteral); if (type) { - if (type.flags & TypeFlags.Tuple) { - var index = indexOf(arrayLiteral.elements, node); - var prop = getPropertyOfType(type, "" + index); - if (prop) { - return getTypeOfSymbol(prop); - } + var index = indexOf(arrayLiteral.elements, node); + var prop = getPropertyOfType(type, "" + index); + if (prop) { + return getTypeOfSymbol(prop); } return getIndexTypeOfType(type, IndexKind.Number); } @@ -3837,21 +3835,24 @@ module ts { function checkArrayLiteral(node: ArrayLiteral, contextualMapper?: TypeMapper): Type { var contextualType = getContextualType(node); - var isTupleLiteral = contextualType && (contextualType.flags & TypeFlags.Tuple) !== 0; + var elements = node.elements; var elementTypes: Type[] = []; - forEach(node.elements, element => { - var type = element.kind !== SyntaxKind.OmittedExpression ? checkExpression(element, contextualMapper) : undefinedType; - if (isTupleLiteral || !contains(elementTypes, type)) { - elementTypes.push(type); + var isTupleLiteral: boolean = false; + for (var i = 0; i < elements.length; i++) { + if (contextualType && getPropertyOfType(contextualType, "" + i)) { + isTupleLiteral = true; } - }); + var element = elements[i]; + var type = element.kind !== SyntaxKind.OmittedExpression ? checkExpression(element, contextualMapper) : undefinedType; + elementTypes.push(type); + } if (isTupleLiteral) { return createTupleType(elementTypes); } var contextualElementType = contextualType && !isInferentialContext(contextualMapper) ? getIndexTypeOfType(contextualType, IndexKind.Number) : undefined; - var elementType = getBestCommonType(elementTypes, contextualElementType, true); + var elementType = getBestCommonType(uniqueElements(elementTypes), contextualElementType, true); if (!elementType) { - elementType = elementTypes.length ? emptyObjectType : undefinedType; + elementType = elements.length ? emptyObjectType : undefinedType; } return createArrayType(elementType); } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 8f6ac1ddfc8..221b6016ce5 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -19,8 +19,7 @@ module ts { export function contains(array: T[], value: T): boolean { if (array) { - var len = array.length; - for (var i = 0; i < len; i++) { + for (var i = 0, len = array.length; i < len; i++) { if (array[i] === value) { return true; } @@ -31,8 +30,7 @@ module ts { export function indexOf(array: T[], value: T): number { if (array) { - var len = array.length; - for (var i = 0; i < len; i++) { + for (var i = 0, len = array.length; i < len; i++) { if (array[i] === value) { return i; } @@ -42,9 +40,8 @@ module ts { } export function filter(array: T[], f: (x: T) => boolean): T[] { - var result: T[]; if (array) { - result = []; + var result: T[] = []; for (var i = 0, len = array.length; i < len; i++) { var item = array[i]; if (f(item)) { @@ -56,11 +53,9 @@ module ts { } export function map(array: T[], f: (x: T) => U): U[] { - var result: U[]; if (array) { - result = []; - var len = array.length; - for (var i = 0; i < len; i++) { + var result: U[] = []; + for (var i = 0, len = array.length; i < len; i++) { result.push(f(array[i])); } } @@ -73,6 +68,17 @@ module ts { return array1.concat(array2); } + export function uniqueElements(array: T[]): T[] { + if (array) { + var result: T[] = []; + for (var i = 0, len = array.length; i < len; i++) { + var item = array[i]; + if (!contains(result, item)) result.push(item); + } + } + return result; + } + export function sum(array: any[], prop: string): number { var result = 0; for (var i = 0; i < array.length; i++) { diff --git a/tests/baselines/reference/tupleTypes.errors.txt b/tests/baselines/reference/tupleTypes.errors.txt index 44cb22daf0f..1e1ebd07292 100644 --- a/tests/baselines/reference/tupleTypes.errors.txt +++ b/tests/baselines/reference/tupleTypes.errors.txt @@ -16,8 +16,8 @@ t = []; // Error ~ -!!! Type '[]' is not assignable to type '[number, string]': -!!! Property '0' is missing in type '[]'. +!!! Type '{}[]' is not assignable to type '[number, string]': +!!! Property '0' is missing in type '{}[]'. t = [1]; // Error ~ !!! Type '[number]' is not assignable to type '[number, string]': @@ -53,7 +53,7 @@ tt = [undefined, undefined]; tt = []; // Error ~~ -!!! Type '[]' is not assignable to type '[number, string]'. +!!! Type '{}[]' is not assignable to type '[number, string]'. var a: number[]; var a1: [number, string]; From 24f6e41de10d3488985b5e00d1ebb206a3a510b4 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 28 Aug 2014 17:14:57 -0700 Subject: [PATCH 08/46] Added getOccs support for if/else keywords, and some tests. --- src/services/services.ts | 85 ++++++++++++++++--- tests/cases/fourslash/getOccurrencesIfElse.ts | 36 ++++++++ .../cases/fourslash/getOccurrencesIfElse2.ts | 32 +++++++ .../cases/fourslash/getOccurrencesIfElse3.ts | 32 +++++++ .../cases/fourslash/getOccurrencesIfElse4.ts | 30 +++++++ .../getOccurrencesIfElseNegatives.ts | 29 +++++++ 6 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 tests/cases/fourslash/getOccurrencesIfElse.ts create mode 100644 tests/cases/fourslash/getOccurrencesIfElse2.ts create mode 100644 tests/cases/fourslash/getOccurrencesIfElse3.ts create mode 100644 tests/cases/fourslash/getOccurrencesIfElse4.ts create mode 100644 tests/cases/fourslash/getOccurrencesIfElseNegatives.ts diff --git a/src/services/services.ts b/src/services/services.ts index 862ca387e86..ccc4924a9cc 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2168,6 +2168,11 @@ module ts { } switch (node.kind) { + case SyntaxKind.IfKeyword: + case SyntaxKind.ElseKeyword: + if (hasKind(node.parent, SyntaxKind.IfStatement)) { + return getIfElseOccurrences(node.parent); + } case SyntaxKind.TryKeyword: case SyntaxKind.CatchKeyword: case SyntaxKind.FinallyKeyword: @@ -2195,6 +2200,65 @@ module ts { return undefined; + function getIfElseOccurrences(ifStatement: IfStatement): ReferenceEntry[] { + var keywords: Node[] = []; + + // Traverse upwards through all parent if-statements linked by their else-branches. + while (hasKind(ifStatement.parent, SyntaxKind.IfStatement) && (ifStatement.parent).elseStatement === ifStatement) { + ifStatement = ifStatement.parent; + } + + // Now traverse back down through the else branches, aggregating if/else keywords of if-statements. + while (ifStatement) { + pushIfAndElseKeywords(); + + if (!hasKind(ifStatement.elseStatement, SyntaxKind.IfStatement)) { + break + } + + ifStatement = ifStatement.elseStatement; + } + + var result: ReferenceEntry[] = []; + + // Here we do a little extra. + // We'd like to make else/ifs on the same line to be highlighted together + for (var i = 0; i < keywords.length; i++) { + if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) { + var elseKeyword = keywords[i]; + var ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword. + + // Ensure that the keywords are only separated by trivia. + if (elseKeyword.end === ifKeyword.getFullStart()) { + var elseLine = sourceFile.getLineAndCharacterFromPosition(elseKeyword.end); + var ifLine = sourceFile.getLineAndCharacterFromPosition(ifKeyword.getStart()); + + if (elseLine.line === ifLine.line) { + result.push(new ReferenceEntry(filename, TypeScript.TextSpan.fromBounds(elseKeyword.getStart(), ifKeyword.end), /* isWriteAccess */ false)); + i++; // skip the next keyword + continue; + } + } + } + + result.push(keywordToReferenceEntry(keywords[i])); + } + + return result; + + function pushIfAndElseKeywords() { + var children = ifStatement.getChildren(); + pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword); + + // Generally the 'else' keyword is second-to-last, so we traverse backwards. + for (var i = children.length - 1; i >= 0; i--) { + if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) { + break; + } + } + } + } + function getTryCatchFinallyOccurrences(tryStatement: TryStatement): ReferenceEntry[] { var keywords: Node[] = []; @@ -2208,7 +2272,7 @@ module ts { pushKeywordIf(keywords, tryStatement.finallyBlock.getFirstToken(), SyntaxKind.FinallyKeyword); } - return keywordsToReferenceEntries(keywords); + return map(keywords, keywordToReferenceEntry); } function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement) { @@ -2244,7 +2308,7 @@ module ts { }); }); - return keywordsToReferenceEntries(keywords); + return map(keywords, keywordToReferenceEntry); } function getBreakStatementOccurences(breakStatement: BreakOrContinueStatement): ReferenceEntry[]{ @@ -2285,20 +2349,17 @@ module ts { return node && node.parent; } - function pushKeywordIf(keywordList: Node[], token: Node, ...expected: SyntaxKind[]): void { - if (!token) { - return; + function pushKeywordIf(keywordList: Node[], token: Node, ...expected: SyntaxKind[]): boolean { + if (token && contains(expected, token.kind)) { + keywordList.push(token); + return true; } - if (contains(expected, token.kind)) { - keywordList.push(token); - } + return false; } - function keywordsToReferenceEntries(keywords: Node[]): ReferenceEntry[]{ - return map(keywords, keyword => - new ReferenceEntry(filename, TypeScript.TextSpan.fromBounds(keyword.getStart(), keyword.end), /* isWriteAccess */ false) - ); + function keywordToReferenceEntry(keyword: Node): ReferenceEntry { + return new ReferenceEntry(filename, TypeScript.TextSpan.fromBounds(keyword.getStart(), keyword.end), /* isWriteAccess */ false); } } diff --git a/tests/cases/fourslash/getOccurrencesIfElse.ts b/tests/cases/fourslash/getOccurrencesIfElse.ts new file mode 100644 index 00000000000..ec9533bf5be --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElse.ts @@ -0,0 +1,36 @@ +/// + +////[|if|] (true) { +//// if (false) { +//// } +//// else { +//// } +//// if (true) { +//// } +//// else { +//// if (false) +//// if (true) +//// var x = undefined; +//// } +////} +////[|else i/**/f|] (null) { +////} +////[|else /* whar garbl */ if|] (undefined) { +////} +////[|else|] +////[|if|] (false) { +////} +////[|else|] { } + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesIfElse2.ts b/tests/cases/fourslash/getOccurrencesIfElse2.ts new file mode 100644 index 00000000000..76df97179c4 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElse2.ts @@ -0,0 +1,32 @@ +/// + +////if (true) { +//// [|if|] (false) { +//// } +//// [|else|]{ +//// } +//// if (true) { +//// } +//// else { +//// if (false) +//// if (true) +//// var x = undefined; +//// } +////} +////else if (null) { +////} +////else /* whar garbl */ if (undefined) { +////} +////else +////if (false) { +////} +////else { } + + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesIfElse3.ts b/tests/cases/fourslash/getOccurrencesIfElse3.ts new file mode 100644 index 00000000000..92a31800e92 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElse3.ts @@ -0,0 +1,32 @@ +/// + +////if (true) { +//// if (false) { +//// } +//// else { +//// } +//// [|if|] (true) { +//// } +//// [|else|] { +//// if (false) +//// if (true) +//// var x = undefined; +//// } +////} +////else if (null) { +////} +////else /* whar garbl */ if (undefined) { +////} +////else +////if (false) { +////} +////else { } + + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesIfElse4.ts b/tests/cases/fourslash/getOccurrencesIfElse4.ts new file mode 100644 index 00000000000..c110521b93a --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElse4.ts @@ -0,0 +1,30 @@ +/// + +////if (true) { +//// if (false) { +//// } +//// else { +//// } +//// if (true) { +//// } +//// else { +//// /*1*/if (false) +//// /*2*/i/*3*/f (true) +//// var x = undefined; +//// } +////} +////else if (null) { +////} +////else /* whar garbl */ if (undefined) { +////} +////else +////if (false) { +////} +////else { } + + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + + verify.occurrencesAtPositionCount(1); +} \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesIfElseNegatives.ts b/tests/cases/fourslash/getOccurrencesIfElseNegatives.ts new file mode 100644 index 00000000000..68cbf5f1875 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElseNegatives.ts @@ -0,0 +1,29 @@ +/// + +////if/*1*/ (true) { +//// if/*2*/ (false) { +//// } +//// else/*3*/ { +//// } +//// if/*4*/ (true) { +//// } +//// else/*5*/ { +//// if/*6*/ (false) +//// if/*7*/ (true) +//// var x = undefined; +//// } +////} +////else/*8*/ if (null) { +////} +////else/*9*/ /* whar garbl */ if/*10*/ (undefined) { +////} +////else/*11*/ +////if/*12*/ (false) { +////} +////else/*13*/ { } + + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + verify.occurrencesAtPositionCount(0); +} From 0632d0c38c286e44d0324f3117f92a102ca73401 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 29 Aug 2014 13:08:48 -0700 Subject: [PATCH 09/46] Addressed CR feedback, no longer highlighting elseifs with comments between. --- src/services/services.ts | 50 ++++++++++--------- tests/cases/fourslash/getOccurrencesIfElse.ts | 4 +- .../cases/fourslash/getOccurrencesIfElse2.ts | 2 +- .../cases/fourslash/getOccurrencesIfElse3.ts | 2 +- .../cases/fourslash/getOccurrencesIfElse4.ts | 2 +- .../getOccurrencesIfElseNegatives.ts | 8 +-- 6 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index ccc4924a9cc..ca59140ffb8 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2210,7 +2210,15 @@ module ts { // Now traverse back down through the else branches, aggregating if/else keywords of if-statements. while (ifStatement) { - pushIfAndElseKeywords(); + var children = ifStatement.getChildren(); + pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword); + + // Generally the 'else' keyword is second-to-last, so we traverse backwards. + for (var i = children.length - 1; i >= 0; i--) { + if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) { + break; + } + } if (!hasKind(ifStatement.elseStatement, SyntaxKind.IfStatement)) { break @@ -2221,42 +2229,36 @@ module ts { var result: ReferenceEntry[] = []; - // Here we do a little extra. - // We'd like to make else/ifs on the same line to be highlighted together + // We'd like to highlight else/ifs together if they are only separated by spaces/tabs + // (i.e. the keywords are separated by no comments, no newlines). for (var i = 0; i < keywords.length; i++) { if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) { var elseKeyword = keywords[i]; var ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword. - // Ensure that the keywords are only separated by trivia. - if (elseKeyword.end === ifKeyword.getFullStart()) { - var elseLine = sourceFile.getLineAndCharacterFromPosition(elseKeyword.end); - var ifLine = sourceFile.getLineAndCharacterFromPosition(ifKeyword.getStart()); + var shouldHighlightNextKeyword = true; - if (elseLine.line === ifLine.line) { - result.push(new ReferenceEntry(filename, TypeScript.TextSpan.fromBounds(elseKeyword.getStart(), ifKeyword.end), /* isWriteAccess */ false)); - i++; // skip the next keyword - continue; + // Avoid recalculating getStart() by iterating backwards. + for (var j = ifKeyword.getStart() - 1; j >= elseKeyword.end; j--) { + var c = sourceFile.text.charCodeAt(j); + if (c !== CharacterCodes.space && c !== CharacterCodes.tab) { + shouldHighlightNextKeyword = false; + break; } } + + if (shouldHighlightNextKeyword) { + result.push(new ReferenceEntry(filename, TypeScript.TextSpan.fromBounds(elseKeyword.getStart(), ifKeyword.end), /* isWriteAccess */ false)); + i++; // skip the next keyword + continue; + } } + // Ordinary case: just highlight the keyword. result.push(keywordToReferenceEntry(keywords[i])); } return result; - - function pushIfAndElseKeywords() { - var children = ifStatement.getChildren(); - pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword); - - // Generally the 'else' keyword is second-to-last, so we traverse backwards. - for (var i = children.length - 1; i >= 0; i--) { - if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) { - break; - } - } - } } function getTryCatchFinallyOccurrences(tryStatement: TryStatement): ReferenceEntry[] { @@ -2350,7 +2352,7 @@ module ts { } function pushKeywordIf(keywordList: Node[], token: Node, ...expected: SyntaxKind[]): boolean { - if (token && contains(expected, token.kind)) { + if (token && contains(expected, token.kind)) { keywordList.push(token); return true; } diff --git a/tests/cases/fourslash/getOccurrencesIfElse.ts b/tests/cases/fourslash/getOccurrencesIfElse.ts index ec9533bf5be..96c70d404ab 100644 --- a/tests/cases/fourslash/getOccurrencesIfElse.ts +++ b/tests/cases/fourslash/getOccurrencesIfElse.ts @@ -13,9 +13,9 @@ //// var x = undefined; //// } ////} -////[|else i/**/f|] (null) { +////[|else i/**/f|] (null) { ////} -////[|else /* whar garbl */ if|] (undefined) { +////[|else|] /* whar garbl */ [|if|] (undefined) { ////} ////[|else|] ////[|if|] (false) { diff --git a/tests/cases/fourslash/getOccurrencesIfElse2.ts b/tests/cases/fourslash/getOccurrencesIfElse2.ts index 76df97179c4..012e7a96efd 100644 --- a/tests/cases/fourslash/getOccurrencesIfElse2.ts +++ b/tests/cases/fourslash/getOccurrencesIfElse2.ts @@ -13,7 +13,7 @@ //// var x = undefined; //// } ////} -////else if (null) { +////else if (null) { ////} ////else /* whar garbl */ if (undefined) { ////} diff --git a/tests/cases/fourslash/getOccurrencesIfElse3.ts b/tests/cases/fourslash/getOccurrencesIfElse3.ts index 92a31800e92..b0ca3615648 100644 --- a/tests/cases/fourslash/getOccurrencesIfElse3.ts +++ b/tests/cases/fourslash/getOccurrencesIfElse3.ts @@ -13,7 +13,7 @@ //// var x = undefined; //// } ////} -////else if (null) { +////else if (null) { ////} ////else /* whar garbl */ if (undefined) { ////} diff --git a/tests/cases/fourslash/getOccurrencesIfElse4.ts b/tests/cases/fourslash/getOccurrencesIfElse4.ts index c110521b93a..66746252741 100644 --- a/tests/cases/fourslash/getOccurrencesIfElse4.ts +++ b/tests/cases/fourslash/getOccurrencesIfElse4.ts @@ -13,7 +13,7 @@ //// var x = undefined; //// } ////} -////else if (null) { +////else if (null) { ////} ////else /* whar garbl */ if (undefined) { ////} diff --git a/tests/cases/fourslash/getOccurrencesIfElseNegatives.ts b/tests/cases/fourslash/getOccurrencesIfElseNegatives.ts index 68cbf5f1875..601eba63c64 100644 --- a/tests/cases/fourslash/getOccurrencesIfElseNegatives.ts +++ b/tests/cases/fourslash/getOccurrencesIfElseNegatives.ts @@ -13,7 +13,7 @@ //// var x = undefined; //// } ////} -////else/*8*/ if (null) { +////else/*8*/ if (null) { ////} ////else/*9*/ /* whar garbl */ if/*10*/ (undefined) { ////} @@ -23,7 +23,7 @@ ////else/*13*/ { } -for (var i = 1; i <= test.markers().length; i++) { - goTo.marker("" + i); +test.markers().forEach(m => { + goTo.position(m.position, m.fileName) verify.occurrencesAtPositionCount(0); -} +}); From 0a5c12c7ab774a99dfd7521ec01437c92bf2760d Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 29 Aug 2014 13:44:54 -0700 Subject: [PATCH 10/46] Added test case for broken if-elses. --- .../fourslash/getOccurrencesIfElseBroken.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/cases/fourslash/getOccurrencesIfElseBroken.ts diff --git a/tests/cases/fourslash/getOccurrencesIfElseBroken.ts b/tests/cases/fourslash/getOccurrencesIfElseBroken.ts new file mode 100644 index 00000000000..a9e1e94f42d --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesIfElseBroken.ts @@ -0,0 +1,24 @@ +/// + + +////[|if|] (true) { +//// var x = 1; +////} +////[|else if|] () +////[|else if|] +////[|else|] /* whar garbl */ [|if|] (i/**/f (true) { } else { }) +////else + +// It would be nice if in the future, +// We could include that last 'else'. + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +verify.occurrencesAtPositionCount(2); \ No newline at end of file From 38d7ba612f1fa3fe3f4c7364e4578589b9d7a681 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 29 Aug 2014 13:48:00 -0700 Subject: [PATCH 11/46] Added missing break statement. --- src/services/services.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/services.ts b/src/services/services.ts index ca59140ffb8..9d653ab2ca6 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2173,6 +2173,7 @@ module ts { if (hasKind(node.parent, SyntaxKind.IfStatement)) { return getIfElseOccurrences(node.parent); } + break; case SyntaxKind.TryKeyword: case SyntaxKind.CatchKeyword: case SyntaxKind.FinallyKeyword: From 3f3dd29461c506e333044fbe274a1f6d14fd83c9 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 29 Aug 2014 14:48:20 -0700 Subject: [PATCH 12/46] Use isWhitespace in getIfElseOccurrences. --- src/services/services.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 9d653ab2ca6..e5bdd0bf751 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2230,7 +2230,7 @@ module ts { var result: ReferenceEntry[] = []; - // We'd like to highlight else/ifs together if they are only separated by spaces/tabs + // We'd like to highlight else/ifs together if they are only separated by whitespace // (i.e. the keywords are separated by no comments, no newlines). for (var i = 0; i < keywords.length; i++) { if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) { @@ -2241,8 +2241,7 @@ module ts { // Avoid recalculating getStart() by iterating backwards. for (var j = ifKeyword.getStart() - 1; j >= elseKeyword.end; j--) { - var c = sourceFile.text.charCodeAt(j); - if (c !== CharacterCodes.space && c !== CharacterCodes.tab) { + if (!isWhiteSpace(sourceFile.text.charCodeAt(j))) { shouldHighlightNextKeyword = false; break; } From fbb10cd6b375da2120e3f538c814bdb58595db87 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 29 Aug 2014 14:18:13 -0700 Subject: [PATCH 13/46] Added getOccs support for return keywords. --- src/compiler/checker.ts | 62 ++++++++++++++++++++-------------------- src/compiler/types.ts | 2 +- src/services/services.ts | 24 ++++++++++++++++ 3 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e1b2adde64f..575a9978f2f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4394,37 +4394,6 @@ module ts { return voidType; } - // WARNING: This has the same semantics as the forEach family of functions, - // in that traversal terminates in the event that 'visitor' supplies a truthy value. - function forEachReturnStatement(body: Block, visitor: (stmt: ReturnStatement) => T): T { - - return traverse(body); - - function traverse(node: Node): T { - switch (node.kind) { - case SyntaxKind.ReturnStatement: - return visitor(node); - case SyntaxKind.Block: - case SyntaxKind.FunctionBlock: - case SyntaxKind.IfStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.SwitchStatement: - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - case SyntaxKind.LabelledStatement: - case SyntaxKind.TryStatement: - case SyntaxKind.TryBlock: - case SyntaxKind.CatchBlock: - case SyntaxKind.FinallyBlock: - return forEachChild(node, traverse); - } - } - } - /// Returns a set of types relating to every return expression relating to a function block. function checkAndAggregateReturnExpressionTypes(body: Block, contextualMapper?: TypeMapper): Type[] { var aggregatedTypes: Type[] = []; @@ -7155,4 +7124,35 @@ module ts { return checker; } + + // WARNING: This has the same semantics as the forEach family of functions, + // in that traversal terminates in the event that 'visitor' supplies a truthy value. + export function forEachReturnStatement(body: Block, visitor: (stmt: ReturnStatement) => T): T { + + return traverse(body); + + function traverse(node: Node): T { + switch (node.kind) { + case SyntaxKind.ReturnStatement: + return visitor(node); + case SyntaxKind.Block: + case SyntaxKind.FunctionBlock: + case SyntaxKind.IfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + case SyntaxKind.LabelledStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.TryBlock: + case SyntaxKind.CatchBlock: + case SyntaxKind.FinallyBlock: + return forEachChild(node, traverse); + } + } + } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index feccd3a298c..198182906f2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -220,7 +220,7 @@ module ts { LastFutureReservedWord = YieldKeyword, FirstTypeNode = TypeReference, LastTypeNode = ArrayType, - FirstPunctuation= OpenBraceToken, + FirstPunctuation = OpenBraceToken, LastPunctuation = CaretEqualsToken } diff --git a/src/services/services.ts b/src/services/services.ts index e5bdd0bf751..0d75dbb2b36 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2174,6 +2174,11 @@ module ts { return getIfElseOccurrences(node.parent); } break; + case SyntaxKind.ReturnKeyword: + if (hasKind(node.parent, SyntaxKind.ReturnStatement)) { + return getReturnOccurrences(node.parent); + } + break; case SyntaxKind.TryKeyword: case SyntaxKind.CatchKeyword: case SyntaxKind.FinallyKeyword: @@ -2261,6 +2266,25 @@ module ts { return result; } + function getReturnOccurrences(returnStatement: ReturnStatement): ReferenceEntry[]{ + var node: Node = returnStatement; + while (!isAnyFunction(node) && node.parent) { + node = node.parent; + } + + // If we didn't find a containing function with a block body, bail out. + if (!(isAnyFunction(node) && hasKind((node).body, SyntaxKind.FunctionBlock))) { + return undefined; + } + + var keywords: Node[] = [] + forEachReturnStatement((node).body, returnStmt => { + pushKeywordIf(keywords, returnStmt.getFirstToken(), SyntaxKind.ReturnKeyword); + }); + + return map(keywords, keywordToReferenceEntry); + } + function getTryCatchFinallyOccurrences(tryStatement: TryStatement): ReferenceEntry[] { var keywords: Node[] = []; From 7e5802192ec172aed7a28e03bfe5efb66ade3158 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 29 Aug 2014 16:11:02 -0700 Subject: [PATCH 14/46] Added tests for getOccs on return keywords. --- tests/cases/fourslash/getOccurrencesReturn.ts | 33 ++++++++++++ .../cases/fourslash/getOccurrencesReturn2.ts | 33 ++++++++++++ .../cases/fourslash/getOccurrencesReturn3.ts | 28 ++++++++++ .../fourslash/getOccurrencesReturnBroken.ts | 54 +++++++++++++++++++ .../getOccurrencesReturnNegatives.ts | 25 +++++++++ 5 files changed, 173 insertions(+) create mode 100644 tests/cases/fourslash/getOccurrencesReturn.ts create mode 100644 tests/cases/fourslash/getOccurrencesReturn2.ts create mode 100644 tests/cases/fourslash/getOccurrencesReturn3.ts create mode 100644 tests/cases/fourslash/getOccurrencesReturnBroken.ts create mode 100644 tests/cases/fourslash/getOccurrencesReturnNegatives.ts diff --git a/tests/cases/fourslash/getOccurrencesReturn.ts b/tests/cases/fourslash/getOccurrencesReturn.ts new file mode 100644 index 00000000000..613e1645fb5 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesReturn.ts @@ -0,0 +1,33 @@ +/// + +////function f(a: number) { +//// if (a > 0) { +//// [|ret/**/urn|] (function () { +//// return; +//// return; +//// return; +//// +//// if (false) { +//// return true; +//// } +//// })() || true; +//// } +//// +//// var unusued = [1, 2, 3, 4].map(x => { return 4 }) +//// +//// [|return|]; +//// [|return|] true; +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesReturn2.ts b/tests/cases/fourslash/getOccurrencesReturn2.ts new file mode 100644 index 00000000000..15a062433fe --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesReturn2.ts @@ -0,0 +1,33 @@ +/// + +////function f(a: number) { +//// if (a > 0) { +//// return (function () { +//// [|return|]; +//// [|ret/**/urn|]; +//// [|return|]; +//// +//// while (false) { +//// [|return|] true; +//// } +//// })() || true; +//// } +//// +//// var unusued = [1, 2, 3, 4].map(x => { return 4 }) +//// +//// return; +//// return true; +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesReturn3.ts b/tests/cases/fourslash/getOccurrencesReturn3.ts new file mode 100644 index 00000000000..030d700ef6e --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesReturn3.ts @@ -0,0 +1,28 @@ +/// + +////function f(a: number) { +//// if (a > 0) { +//// return (function () { +//// return; +//// return; +//// return; +//// +//// if (false) { +//// return true; +//// } +//// })() || true; +//// } +//// +//// var unusued = [1, 2, 3, 4].map(x => { [|return|] 4 }) +//// +//// return; +//// return true; +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesReturnBroken.ts b/tests/cases/fourslash/getOccurrencesReturnBroken.ts new file mode 100644 index 00000000000..e740e7f2bb3 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesReturnBroken.ts @@ -0,0 +1,54 @@ +/// + +////ret/*1*/urn; +////retu/*2*/rn; +////function f(a: number) { +//// if (a > 0) { +//// return (function () { +//// () => [|return|]; +//// [|return|]; +//// [|return|]; +//// +//// if (false) { +//// [|return|] true; +//// } +//// })() || true; +//// } +//// +//// var unusued = [1, 2, 3, 4].map(x => { return 4 }) +//// +//// return; +//// return true; +////} +//// +////class A { +//// ret/*3*/urn; +//// r/*4*/eturn 8675309; +////} + +// Note: For this test, these 'return's get highlighted as a result of a parse recovery +// where if an arrow function starts with a statement, we try to parse a body +// as if it was missing curly braces. If the behavior changes in the future, +// a change to this test is very much welcome. +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +for (var i = 1; i <= test.markers().length; i++) { + goTo.marker("" + i); + + switch (i) { + case 0: + case 1: + case 4: + verify.occurrencesAtPositionCount(0); + break; + case 3: + verify.occurrencesAtPositionCount(1); // 'return' is an instance member + break; + } +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesReturnNegatives.ts b/tests/cases/fourslash/getOccurrencesReturnNegatives.ts new file mode 100644 index 00000000000..79cb3c659c6 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesReturnNegatives.ts @@ -0,0 +1,25 @@ +/// + +////function f(a: number) { +//// if (a > 0) { +//// return (function () { +//// return/*1*/; +//// return/*2*/; +//// return/*3*/; +//// +//// if (false) { +//// return/*4*/ true; +//// } +//// })() || true; +//// } +//// +//// var unusued = [1, 2, 3, 4].map(x => { return/*5*/ 4 }) +//// +//// return/*6*/; +//// return/*7*/ true; +////} + +test.markers().forEach(m => { + goTo.position(m.position, m.fileName) + verify.occurrencesAtPositionCount(0); +}); \ No newline at end of file From ba396ed28fc6060d3891b604fa228e9ed0518470 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 29 Aug 2014 17:13:14 -0700 Subject: [PATCH 15/46] Utilize getContainingFunction in services. --- src/compiler/checker.ts | 22 +++++++++++----------- src/services/services.ts | 13 +++++++------ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 575a9978f2f..0fc14f157d7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5774,17 +5774,6 @@ module ts { // TODO: Check that target label is valid } - function getContainingFunction(node: Node): SignatureDeclaration { - while (true) { - node = node.parent; - if (!node || node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression || - node.kind === SyntaxKind.ArrowFunction || node.kind === SyntaxKind.Method || node.kind === SyntaxKind.Constructor || - node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) { - return node; - } - } - } - function checkReturnStatement(node: ReturnStatement) { if (node.expression && !(getNodeLinks(node.expression).flags & NodeCheckFlags.TypeChecked)) { var func = getContainingFunction(node); @@ -7124,6 +7113,17 @@ module ts { return checker; } + + export function getContainingFunction(node: Node): SignatureDeclaration { + while (true) { + node = node.parent; + if (!node || node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression || + node.kind === SyntaxKind.ArrowFunction || node.kind === SyntaxKind.Method || node.kind === SyntaxKind.Constructor || + node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) { + return node; + } + } + } // WARNING: This has the same semantics as the forEach family of functions, // in that traversal terminates in the event that 'visitor' supplies a truthy value. diff --git a/src/services/services.ts b/src/services/services.ts index 0d75dbb2b36..2ad05fdb726 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1313,6 +1313,10 @@ module ts { } function isAnyFunction(node: Node): boolean { + if (!node) { + return false; + } + switch (node.kind) { case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionDeclaration: @@ -2267,18 +2271,15 @@ module ts { } function getReturnOccurrences(returnStatement: ReturnStatement): ReferenceEntry[]{ - var node: Node = returnStatement; - while (!isAnyFunction(node) && node.parent) { - node = node.parent; - } + var func = getContainingFunction(returnStatement); // If we didn't find a containing function with a block body, bail out. - if (!(isAnyFunction(node) && hasKind((node).body, SyntaxKind.FunctionBlock))) { + if (!(isAnyFunction(func) && hasKind(func.body, SyntaxKind.FunctionBlock))) { return undefined; } var keywords: Node[] = [] - forEachReturnStatement((node).body, returnStmt => { + forEachReturnStatement((func).body, returnStmt => { pushKeywordIf(keywords, returnStmt.getFirstToken(), SyntaxKind.ReturnKeyword); }); From 837dddaec37630b70416d42d7e95e0e747b40f84 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 4 Sep 2014 11:54:16 -0700 Subject: [PATCH 16/46] Addressed CR feedback. --- src/compiler/checker.ts | 31 ------------------------------- src/compiler/parser.ts | 31 +++++++++++++++++++++++++++++++ src/services/services.ts | 4 ++-- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0fc14f157d7..d9b2b556b21 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7124,35 +7124,4 @@ module ts { } } } - - // WARNING: This has the same semantics as the forEach family of functions, - // in that traversal terminates in the event that 'visitor' supplies a truthy value. - export function forEachReturnStatement(body: Block, visitor: (stmt: ReturnStatement) => T): T { - - return traverse(body); - - function traverse(node: Node): T { - switch (node.kind) { - case SyntaxKind.ReturnStatement: - return visitor(node); - case SyntaxKind.Block: - case SyntaxKind.FunctionBlock: - case SyntaxKind.IfStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.SwitchStatement: - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - case SyntaxKind.LabelledStatement: - case SyntaxKind.TryStatement: - case SyntaxKind.TryBlock: - case SyntaxKind.CatchBlock: - case SyntaxKind.FinallyBlock: - return forEachChild(node, traverse); - } - } - } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1e957cd2abe..7e03366bc1a 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -349,6 +349,37 @@ module ts { } } + // Warning: This has the same semantics as the forEach family of functions, + // in that traversal terminates in the event that 'visitor' supplies a truthy value. + export function forEachReturnStatement(body: Block, visitor: (stmt: ReturnStatement) => T): T { + + return traverse(body); + + function traverse(node: Node): T { + switch (node.kind) { + case SyntaxKind.ReturnStatement: + return visitor(node); + case SyntaxKind.Block: + case SyntaxKind.FunctionBlock: + case SyntaxKind.IfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + case SyntaxKind.LabelledStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.TryBlock: + case SyntaxKind.CatchBlock: + case SyntaxKind.FinallyBlock: + return forEachChild(node, traverse); + } + } + } + export function hasRestParameters(s: SignatureDeclaration): boolean { return s.parameters.length > 0 && (s.parameters[s.parameters.length - 1].flags & NodeFlags.Rest) !== 0; } diff --git a/src/services/services.ts b/src/services/services.ts index 2ad05fdb726..5860dee8e8d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2279,8 +2279,8 @@ module ts { } var keywords: Node[] = [] - forEachReturnStatement((func).body, returnStmt => { - pushKeywordIf(keywords, returnStmt.getFirstToken(), SyntaxKind.ReturnKeyword); + forEachReturnStatement((func).body, returnStatement => { + pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword); }); return map(keywords, keywordToReferenceEntry); From 7b5440bb8dda9135bb24c844448f3463f8ea7502 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 4 Sep 2014 12:17:35 -0700 Subject: [PATCH 17/46] Addressed more CR feedback. --- src/compiler/checker.ts | 11 ----------- src/compiler/parser.ts | 26 ++++++++++++++++++++++++++ src/services/services.ts | 20 +------------------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d9b2b556b21..e7c0b5bc32a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7113,15 +7113,4 @@ module ts { return checker; } - - export function getContainingFunction(node: Node): SignatureDeclaration { - while (true) { - node = node.parent; - if (!node || node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression || - node.kind === SyntaxKind.ArrowFunction || node.kind === SyntaxKind.Method || node.kind === SyntaxKind.Constructor || - node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) { - return node; - } - } - } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 7e03366bc1a..eea647999ac 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -380,6 +380,32 @@ module ts { } } + export function isAnyFunction(node: Node): boolean { + if (node) { + switch (node.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + case SyntaxKind.Method: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.Constructor: + return true; + } + } + + return false; + } + + export function getContainingFunction(node: Node): SignatureDeclaration { + while (true) { + node = node.parent; + if (!node || isAnyFunction(node)) { + return node; + } + } + } + export function hasRestParameters(s: SignatureDeclaration): boolean { return s.parameters.length > 0 && (s.parameters[s.parameters.length - 1].flags & NodeFlags.Rest) !== 0; } diff --git a/src/services/services.ts b/src/services/services.ts index 5860dee8e8d..2ed73a52cb7 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1312,24 +1312,6 @@ module ts { return node.parent.kind === SyntaxKind.NewExpression && (node.parent).func === node; } - function isAnyFunction(node: Node): boolean { - if (!node) { - return false; - } - - switch (node.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - case SyntaxKind.Method: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.Constructor: - return true; - } - return false; - } - function isNameOfFunctionDeclaration(node: Node): boolean { return node.kind === SyntaxKind.Identifier && isAnyFunction(node.parent) && (node.parent).name === node; @@ -2274,7 +2256,7 @@ module ts { var func = getContainingFunction(returnStatement); // If we didn't find a containing function with a block body, bail out. - if (!(isAnyFunction(func) && hasKind(func.body, SyntaxKind.FunctionBlock))) { + if (!(func && hasKind(func.body, SyntaxKind.FunctionBlock))) { return undefined; } From e6e9979482b842034f02a95dff2bd1dce4675211 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 2 Sep 2014 16:53:52 -0700 Subject: [PATCH 18/46] getRefs/getOccs support for 'this' keyword. --- src/compiler/checker.ts | 27 +----- src/compiler/parser.ts | 23 +++++ src/services/services.ts | 200 ++++++++++++++++++++++++++++----------- 3 files changed, 171 insertions(+), 79 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 780e530f497..46624fabbf1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3449,29 +3449,6 @@ module ts { return getTypeOfSymbol(getExportSymbolOfValueSymbolIfExported(symbol)); } - function getThisContainer(node: Node): Node { - while (true) { - node = node.parent; - if (!node) { - return node; - } - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.Property: - case SyntaxKind.Method: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.SourceFile: - case SyntaxKind.ArrowFunction: - return node; - } - } - } - function captureLexicalThis(node: Node, container: Node): void { var classNode = container.parent && container.parent.kind === SyntaxKind.ClassDeclaration ? container.parent : undefined; getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis; @@ -3484,11 +3461,11 @@ module ts { } function checkThisExpression(node: Node): Type { - var container = getThisContainer(node); + var container = getThisContainerOrArrowFunction(node); var needToCaptureLexicalThis = false; // skip arrow functions while (container.kind === SyntaxKind.ArrowFunction) { - container = getThisContainer(container); + container = getThisContainerOrArrowFunction(container); needToCaptureLexicalThis = true; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 05d1dddfccf..256d611d4ed 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -407,6 +407,29 @@ module ts { } } + export function getThisContainerOrArrowFunction(node: Node): Node { + while (true) { + node = node.parent; + if (!node) { + return node; + } + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.Property: + case SyntaxKind.Method: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.SourceFile: + case SyntaxKind.ArrowFunction: + return node; + } + } + } + export function hasRestParameters(s: SignatureDeclaration): boolean { return s.parameters.length > 0 && (s.parameters[s.parameters.length - 1].flags & NodeFlags.Rest) !== 0; } diff --git a/src/services/services.ts b/src/services/services.ts index 04451086ff8..fb978aa46aa 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2146,7 +2146,8 @@ module ts { return undefined; } - if (node.kind === SyntaxKind.Identifier || isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isNameOfExternalModuleImportOrDeclaration(node)) { + if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || + isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isNameOfExternalModuleImportOrDeclaration(node)) { return getReferencesForNode(node, [sourceFile]); } @@ -2243,7 +2244,7 @@ module ts { } // Ordinary case: just highlight the keyword. - result.push(keywordToReferenceEntry(keywords[i])); + result.push(getReferenceEntryFromNode(keywords[i])); } return result; @@ -2262,7 +2263,7 @@ module ts { pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword); }); - return map(keywords, keywordToReferenceEntry); + return map(keywords, getReferenceEntryFromNode); } function getTryCatchFinallyOccurrences(tryStatement: TryStatement): ReferenceEntry[] { @@ -2278,7 +2279,7 @@ module ts { pushKeywordIf(keywords, tryStatement.finallyBlock.getFirstToken(), SyntaxKind.FinallyKeyword); } - return map(keywords, keywordToReferenceEntry); + return map(keywords, getReferenceEntryFromNode); } function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement) { @@ -2314,7 +2315,7 @@ module ts { }); }); - return map(keywords, keywordToReferenceEntry); + return map(keywords, getReferenceEntryFromNode); } function getBreakStatementOccurences(breakStatement: BreakOrContinueStatement): ReferenceEntry[]{ @@ -2363,10 +2364,6 @@ module ts { return false; } - - function keywordToReferenceEntry(keyword: Node): ReferenceEntry { - return new ReferenceEntry(filename, TypeScript.TextSpan.fromBounds(keyword.getStart(), keyword.end), /* isWriteAccess */ false); - } } function getReferencesAtPosition(filename: string, position: number): ReferenceEntry[] { @@ -2381,6 +2378,7 @@ module ts { } if (node.kind !== SyntaxKind.Identifier && + node.kind !== SyntaxKind.ThisKeyword && !isLiteralNameOfPropertyDeclarationOrIndexAccess(node) && !isNameOfExternalModuleImportOrDeclaration(node)) { return undefined; @@ -2396,7 +2394,7 @@ module ts { var labelDefinition = getTargetLabel((node.parent), (node).text); // if we have a label definition, look within its statement for references, if not, then // the label is undefined, just return a set of one for the current node. - return labelDefinition ? getLabelReferencesInNode(labelDefinition.parent, labelDefinition) : [getReferenceEntry(node)]; + return labelDefinition ? getLabelReferencesInNode(labelDefinition.parent, labelDefinition) : [getReferenceEntryFromNode(node)]; } else { // it is a label definition and not a target, search within the parent labeledStatement @@ -2404,13 +2402,17 @@ module ts { } } + if (node.kind === SyntaxKind.ThisKeyword) { + return getReferencesForThisKeyword(node, sourceFiles); + } + var symbol = typeInfoResolver.getSymbolInfo(node); // Could not find a symbol e.g. unknown identifier if (!symbol) { // Even if we did not find a symbol, we have an identifer, so there is at least - // one reference that we know of. return than instead of undefined. - return [getReferenceEntry(node)]; + // one reference that we know of. return that instead of undefined. + return [getReferenceEntryFromNode(node)]; } // the symbol was an internal symbol and does not have a declaration e.g.undefined symbol @@ -2554,7 +2556,7 @@ module ts { // Only pick labels that are either the target label, or have a target that is the target label if (node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel)) { - result.push(getReferenceEntry(node)); + result.push(getReferenceEntryFromNode(node)); } }); return result; @@ -2619,7 +2621,97 @@ module ts { } if (isRelatableToSearchSet(searchSymbols, referenceSymbol, referenceLocation)) { - result.push(getReferenceEntry(referenceLocation)); + result.push(getReferenceEntryFromNode(referenceLocation)); + } + }); + } + } + + function getReferencesForThisKeyword(thisKeyword: Node, sourceFiles: SourceFile[]) { + // Get the owner" of the 'this' keyword. + var thisContainer = thisKeyword; + do { + thisContainer = getThisContainerOrArrowFunction(thisContainer); + } while (thisContainer.kind === SyntaxKind.ArrowFunction); + + var searchSpaceNode: Node; + + // Whether 'this' occurs in a static context within a class; + var staticFlag = NodeFlags.Static; + + switch (thisContainer.kind) { + case SyntaxKind.Property: + case SyntaxKind.Method: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + searchSpaceNode = thisContainer.parent; // should be the owning class + staticFlag &= thisContainer.flags + break; + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.SourceFile: + searchSpaceNode = thisContainer; + break; + default: + return undefined; + } + + var result: ReferenceEntry[] = []; + + if (searchSpaceNode.kind === SyntaxKind.SourceFile) { + forEach(sourceFiles, sourceFile => { + var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", sourceFile.getStart(), sourceFile.getEnd()); + getThisReferencesInFile(sourceFile, sourceFile, possiblePositions, result); + }); + } + else { + var sourceFile = searchSpaceNode.getSourceFile(); + var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", searchSpaceNode.getStart(), searchSpaceNode.getEnd()); + getThisReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, result); + } + + return result; + + function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: ReferenceEntry[]): void { + forEach(possiblePositions, position => { + cancellationToken.throwIfCancellationRequested(); + + var node = getNodeAtPosition(sourceFile, position); + if (!node || node.kind !== SyntaxKind.ThisKeyword) { + return; + } + + // Get the owner" of the 'this' keyword. + var container = node; + do { + container = getThisContainerOrArrowFunction(container); + } while (container.kind === SyntaxKind.ArrowFunction); + + switch (container.kind) { + case SyntaxKind.Property: + case SyntaxKind.Method: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // Make sure the container belongs to the same class + // and has the appropriate static modifier from the original container. + if (searchSpaceNode.symbol === container.parent.symbol && (container.flags & NodeFlags.Static) === staticFlag) { + result.push(getReferenceEntryFromNode(node)); + } + break; + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + if (searchSpaceNode.symbol === container.symbol) { + result.push(getReferenceEntryFromNode(node)); + } + break; + case SyntaxKind.SourceFile: + // Add all 'this' keywords that belong to the top-level scope. + if (searchSpaceNode.kind === SyntaxKind.SourceFile) { + result.push(getReferenceEntryFromNode(node)); + } + break; } }); } @@ -2721,18 +2813,6 @@ module ts { return undefined; } - function getReferenceEntry(node: Node): ReferenceEntry { - var start = node.getStart(); - var end = node.getEnd(); - - if (node.kind === SyntaxKind.StringLiteral) { - start += 1; - end -= 1; - } - - return new ReferenceEntry(node.getSourceFile().filename, TypeScript.TextSpan.fromBounds(start, end), isWriteAccess(node)); - } - function getMeaningFromDeclaration(node: Declaration): SearchMeaning { switch (node.kind) { case SyntaxKind.Parameter: @@ -2869,40 +2949,52 @@ module ts { } return meaning; } + } - /// A node is considedered a writeAccess iff it is a name of a declaration or a target of an assignment - function isWriteAccess(node: Node): boolean { - if (node.kind === SyntaxKind.Identifier && isDeclarationOrFunctionExpressionOrCatchVariableName(node)) { + function getReferenceEntryFromNode(node: Node): ReferenceEntry { + var start = node.getStart(); + var end = node.getEnd(); + + if (node.kind === SyntaxKind.StringLiteral) { + start += 1; + end -= 1; + } + + return new ReferenceEntry(node.getSourceFile().filename, TypeScript.TextSpan.fromBounds(start, end), isWriteAccess(node)); + } + + /// A node is considedered a writeAccess iff it is a name of a declaration or a target of an assignment + function isWriteAccess(node: Node): boolean { + if (node.kind === SyntaxKind.Identifier && isDeclarationOrFunctionExpressionOrCatchVariableName(node)) { + return true; + } + + var parent = node.parent; + if (parent) { + if (parent.kind === SyntaxKind.PostfixOperator || parent.kind === SyntaxKind.PrefixOperator) { return true; } - - var parent = node.parent; - if (parent) { - if (parent.kind === SyntaxKind.PostfixOperator || parent.kind === SyntaxKind.PrefixOperator) { - return true; + else if (parent.kind === SyntaxKind.BinaryExpression && (parent).left === node) { + var operator = (parent).operator; + switch (operator) { + case SyntaxKind.AsteriskEqualsToken: + case SyntaxKind.SlashEqualsToken: + case SyntaxKind.PercentEqualsToken: + case SyntaxKind.MinusEqualsToken: + case SyntaxKind.LessThanLessThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + case SyntaxKind.BarEqualsToken: + case SyntaxKind.CaretEqualsToken: + case SyntaxKind.AmpersandEqualsToken: + case SyntaxKind.PlusEqualsToken: + case SyntaxKind.EqualsToken: + return true; } - else if (parent.kind === SyntaxKind.BinaryExpression && (parent).left === node) { - var operator = (parent).operator; - switch (operator) { - case SyntaxKind.AsteriskEqualsToken: - case SyntaxKind.SlashEqualsToken: - case SyntaxKind.PercentEqualsToken: - case SyntaxKind.MinusEqualsToken: - case SyntaxKind.LessThanLessThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - case SyntaxKind.BarEqualsToken: - case SyntaxKind.CaretEqualsToken: - case SyntaxKind.AmpersandEqualsToken: - case SyntaxKind.PlusEqualsToken: - case SyntaxKind.EqualsToken: - return true; - } - } - - return false; } } + + return false; } /// Syntactic features From a6a6d77d4a28763c73aec44a7e2feb35142548b1 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 2 Sep 2014 17:44:53 -0700 Subject: [PATCH 19/46] Added fourslash tests for 'this' keyword findOccs/findRefs. --- .../findAllRefsThisKeywordMultipleFiles.ts | 15 ++ tests/cases/fourslash/getOccurrencesThis.ts | 154 ++++++++++++++++++ tests/cases/fourslash/getOccurrencesThis2.ts | 154 ++++++++++++++++++ tests/cases/fourslash/getOccurrencesThis3.ts | 154 ++++++++++++++++++ tests/cases/fourslash/getOccurrencesThis4.ts | 154 ++++++++++++++++++ tests/cases/fourslash/getOccurrencesThis5.ts | 154 ++++++++++++++++++ .../fourslash/getOccurrencesThisNegatives.ts | 149 +++++++++++++++++ .../fourslash/getOccurrencesThisNegatives2.ts | 147 +++++++++++++++++ 8 files changed, 1081 insertions(+) create mode 100644 tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts create mode 100644 tests/cases/fourslash/getOccurrencesThis.ts create mode 100644 tests/cases/fourslash/getOccurrencesThis2.ts create mode 100644 tests/cases/fourslash/getOccurrencesThis3.ts create mode 100644 tests/cases/fourslash/getOccurrencesThis4.ts create mode 100644 tests/cases/fourslash/getOccurrencesThis5.ts create mode 100644 tests/cases/fourslash/getOccurrencesThisNegatives.ts create mode 100644 tests/cases/fourslash/getOccurrencesThisNegatives2.ts diff --git a/tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts b/tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts new file mode 100644 index 00000000000..bc31ffd1579 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts @@ -0,0 +1,15 @@ +/// + +// @Filename: file1.ts +////this; this; + +// @Filename: file2.ts +////this; +////this; + +// @Filename: file3.ts +//// ((x = this, y) => t/**/his)(this, this); + +goTo.file("file1.ts"); +goTo.marker(); +verify.referencesCountIs(8); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesThis.ts b/tests/cases/fourslash/getOccurrencesThis.ts new file mode 100644 index 00000000000..e4b059b9cf9 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThis.ts @@ -0,0 +1,154 @@ +/// + +////[|this|]; +////[|th/**/is|]; +//// +////function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +////} +//// +////module m { +//// function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////class A { +//// public b = this.method1; +//// +//// public method1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private method2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// public static staticB = this.staticMethod1; +//// +//// public static staticMethod1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private static staticMethod2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////var x = { +//// f() { +//// this; +//// }, +//// g() { +//// this; +//// } +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesThis2.ts b/tests/cases/fourslash/getOccurrencesThis2.ts new file mode 100644 index 00000000000..3df30369e3e --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThis2.ts @@ -0,0 +1,154 @@ +/// + +////this; +////this; +//// +////function f() { +//// [|this|]; +//// [|this|]; +//// () => [|this|]; +//// () => { +//// if ([|this|]) { +//// [|this|]; +//// } +//// else { +//// [|t/**/his|].this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +////} +//// +////module m { +//// function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////class A { +//// public b = this.method1; +//// +//// public method1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private method2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// public static staticB = this.staticMethod1; +//// +//// public static staticMethod1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private static staticMethod2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////var x = { +//// f() { +//// this; +//// }, +//// g() { +//// this; +//// } +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesThis3.ts b/tests/cases/fourslash/getOccurrencesThis3.ts new file mode 100644 index 00000000000..7ee18a31286 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThis3.ts @@ -0,0 +1,154 @@ +/// + +////this; +////this; +//// +////function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// [|t/**/his|]; +//// (function (_) { +//// this; +//// })([|this|]); +//// } +////} +//// +////module m { +//// function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////class A { +//// public b = this.method1; +//// +//// public method1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private method2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// public static staticB = this.staticMethod1; +//// +//// public static staticMethod1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private static staticMethod2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////var x = { +//// f() { +//// this; +//// }, +//// g() { +//// this; +//// } +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesThis4.ts b/tests/cases/fourslash/getOccurrencesThis4.ts new file mode 100644 index 00000000000..75a9383f88e --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThis4.ts @@ -0,0 +1,154 @@ +/// + +////this; +////this; +//// +////function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +////} +//// +////module m { +//// function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////class A { +//// public b = [|this|].method1; +//// +//// public method1() { +//// [|this|]; +//// [|this|]; +//// () => [|this|]; +//// () => { +//// if ([|this|]) { +//// [|this|]; +//// } +//// else { +//// [|this|].this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private method2() { +//// [|this|]; +//// [|this|]; +//// () => [|t/**/his|]; +//// () => { +//// if ([|this|]) { +//// [|this|]; +//// } +//// else { +//// [|this|].this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// public static staticB = this.staticMethod1; +//// +//// public static staticMethod1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private static staticMethod2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////var x = { +//// f() { +//// this; +//// }, +//// g() { +//// this; +//// } +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesThis5.ts b/tests/cases/fourslash/getOccurrencesThis5.ts new file mode 100644 index 00000000000..86ce16b16be --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThis5.ts @@ -0,0 +1,154 @@ +/// + +////this; +////this; +//// +////function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +////} +//// +////module m { +//// function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////class A { +//// public b = this.method1; +//// +//// public method1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private method2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// public static staticB = [|this|].staticMethod1; +//// +//// public static staticMethod1() { +//// [|this|]; +//// [|this|]; +//// () => [|this|]; +//// () => { +//// if ([|this|]) { +//// [|this|]; +//// } +//// else { +//// [|this|].this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private static staticMethod2() { +//// [|this|]; +//// [|this|]; +//// () => [|this|]; +//// () => { +//// if ([|this|]) { +//// [|this|]; +//// } +//// else { +//// [|t/**/his|].this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////var x = { +//// f() { +//// this; +//// }, +//// g() { +//// this; +//// } +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesThisNegatives.ts b/tests/cases/fourslash/getOccurrencesThisNegatives.ts new file mode 100644 index 00000000000..95c676e0add --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThisNegatives.ts @@ -0,0 +1,149 @@ +/// + +////this/*1*/; +////this; +//// +////function f() { +//// this/*2*/; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +////} +//// +////module m { +//// var x = th/*6*/is; +//// function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////class A { +//// public b = this.method1; +//// +//// public method1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this/*3*/; +//// })(this); +//// } +//// } +//// +//// private method2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// public static staticB = this.staticMethod1; +//// +//// public static staticMethod1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private static staticMethod2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////var x = { +//// f() { +//// this/*4*/; +//// }, +//// g() { +//// this/*5*/; +//// } +////} + + +test.markers().forEach(m => { + goTo.position(m.position, m.fileName) + + verify.occurrencesAtPositionCount(0); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesThisNegatives2.ts b/tests/cases/fourslash/getOccurrencesThisNegatives2.ts new file mode 100644 index 00000000000..65fb37d09b4 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesThisNegatives2.ts @@ -0,0 +1,147 @@ +/// + +////this; +////this; +//// +////function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.t/*1*/his; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +////} +//// +////module m { +//// function f() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this./*2*/this; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////class A { +//// public b = this.method1; +//// +//// public method1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.thi/*3*/s; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private method2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.t/*4*/his; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// public static staticB = this.staticMethod1; +//// +//// public static staticMethod1() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.th/*5*/is; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +//// +//// private static staticMethod2() { +//// this; +//// this; +//// () => this; +//// () => { +//// if (this) { +//// this; +//// } +//// else { +//// this.th/*6*/is; +//// } +//// } +//// function inside() { +//// this; +//// (function (_) { +//// this; +//// })(this); +//// } +//// } +////} +//// +////var x = { +//// f() { +//// this; +//// }, +//// g() { +//// this; +//// } +////} + + +test.markers().forEach(m => { + goTo.position(m.position, m.fileName) + verify.occurrencesAtPositionCount(1); +}); From 84e385ddfa318f5f81eb100ca9ed68f65ef56c17 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 5 Sep 2014 12:14:50 -0700 Subject: [PATCH 20/46] Made a getThisContainer function. --- src/compiler/parser.ts | 8 ++++++++ src/services/services.ts | 10 ++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 256d611d4ed..288781db78f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -430,6 +430,14 @@ module ts { } } + export function getThisContainer(node: Node): Node { + do { + node = getThisContainerOrArrowFunction(node); + } while (node.kind === SyntaxKind.ArrowFunction); + + return node; + } + export function hasRestParameters(s: SignatureDeclaration): boolean { return s.parameters.length > 0 && (s.parameters[s.parameters.length - 1].flags & NodeFlags.Rest) !== 0; } diff --git a/src/services/services.ts b/src/services/services.ts index fb978aa46aa..ef9c8cf40c2 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2629,10 +2629,7 @@ module ts { function getReferencesForThisKeyword(thisKeyword: Node, sourceFiles: SourceFile[]) { // Get the owner" of the 'this' keyword. - var thisContainer = thisKeyword; - do { - thisContainer = getThisContainerOrArrowFunction(thisContainer); - } while (thisContainer.kind === SyntaxKind.ArrowFunction); + var thisContainer = getThisContainer(thisKeyword); var searchSpaceNode: Node; @@ -2683,10 +2680,7 @@ module ts { } // Get the owner" of the 'this' keyword. - var container = node; - do { - container = getThisContainerOrArrowFunction(container); - } while (container.kind === SyntaxKind.ArrowFunction); + var container = getThisContainer(node); switch (container.kind) { case SyntaxKind.Property: From 024ca6d6ac9bb4ddbef628a7d4e0d22d5c84c7f5 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 5 Sep 2014 15:58:22 -0700 Subject: [PATCH 21/46] Addressed CR feedback. --- src/compiler/checker.ts | 11 +++++++---- src/compiler/parser.ts | 16 ++++++---------- src/services/services.ts | 31 +++++++++++-------------------- 3 files changed, 24 insertions(+), 34 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 46624fabbf1..80778789e7b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3461,11 +3461,14 @@ module ts { } function checkThisExpression(node: Node): Type { - var container = getThisContainerOrArrowFunction(node); + // Stop at the first arrow function so that we can + // tell whether 'this' needs to be captured. + var container = getThisContainer(node, /* includeArrowFunctions */ true); var needToCaptureLexicalThis = false; - // skip arrow functions - while (container.kind === SyntaxKind.ArrowFunction) { - container = getThisContainerOrArrowFunction(container); + + // Now skip arrow functions to get the "real" owner of 'this'. + if (container.kind === SyntaxKind.ArrowFunction) { + container = getThisContainer(container, /* includeArrowFunctions */ false); needToCaptureLexicalThis = true; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 288781db78f..e2d2bb3886b 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -407,13 +407,18 @@ module ts { } } - export function getThisContainerOrArrowFunction(node: Node): Node { + export function getThisContainer(node: Node, includeArrowFunctions: boolean): Node { while (true) { node = node.parent; if (!node) { return node; } switch (node.kind) { + case SyntaxKind.ArrowFunction: + if (!includeArrowFunctions) { + continue; + } + // Fall through case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ModuleDeclaration: @@ -424,20 +429,11 @@ module ts { case SyntaxKind.SetAccessor: case SyntaxKind.EnumDeclaration: case SyntaxKind.SourceFile: - case SyntaxKind.ArrowFunction: return node; } } } - export function getThisContainer(node: Node): Node { - do { - node = getThisContainerOrArrowFunction(node); - } while (node.kind === SyntaxKind.ArrowFunction); - - return node; - } - export function hasRestParameters(s: SignatureDeclaration): boolean { return s.parameters.length > 0 && (s.parameters[s.parameters.length - 1].flags & NodeFlags.Rest) !== 0; } diff --git a/src/services/services.ts b/src/services/services.ts index ef9c8cf40c2..12f7e0ce24e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2629,7 +2629,7 @@ module ts { function getReferencesForThisKeyword(thisKeyword: Node, sourceFiles: SourceFile[]) { // Get the owner" of the 'this' keyword. - var thisContainer = getThisContainer(thisKeyword); + var thisContainer = getThisContainer(thisKeyword, /* includeArrowFunctions */ false); var searchSpaceNode: Node; @@ -2645,9 +2645,13 @@ module ts { searchSpaceNode = thisContainer.parent; // should be the owning class staticFlag &= thisContainer.flags break; + case SyntaxKind.SourceFile: + if (isExternalModule(thisContainer)) { + return undefined; + } + // Fall through case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: - case SyntaxKind.SourceFile: searchSpaceNode = thisContainer; break; default: @@ -2679,8 +2683,9 @@ module ts { return; } - // Get the owner" of the 'this' keyword. - var container = getThisContainer(node); + // Get the owner of the 'this' keyword. + // This *should* be a node that occurs somewhere within searchSpaceNode. + var container = getThisContainer(node, /* includeArrowFunctions */ false); switch (container.kind) { case SyntaxKind.Property: @@ -2702,7 +2707,7 @@ module ts { break; case SyntaxKind.SourceFile: // Add all 'this' keywords that belong to the top-level scope. - if (searchSpaceNode.kind === SyntaxKind.SourceFile) { + if (searchSpaceNode.kind === SyntaxKind.SourceFile && !isExternalModule(searchSpaceNode)) { result.push(getReferenceEntryFromNode(node)); } break; @@ -2970,21 +2975,7 @@ module ts { } else if (parent.kind === SyntaxKind.BinaryExpression && (parent).left === node) { var operator = (parent).operator; - switch (operator) { - case SyntaxKind.AsteriskEqualsToken: - case SyntaxKind.SlashEqualsToken: - case SyntaxKind.PercentEqualsToken: - case SyntaxKind.MinusEqualsToken: - case SyntaxKind.LessThanLessThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - case SyntaxKind.BarEqualsToken: - case SyntaxKind.CaretEqualsToken: - case SyntaxKind.AmpersandEqualsToken: - case SyntaxKind.PlusEqualsToken: - case SyntaxKind.EqualsToken: - return true; - } + return SyntaxKind.FirstAssignment <= operator && operator <= SyntaxKind.LastAssignment; } } From 1121e11c458ff3fdf39acf090fc90d8a5c676338 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 5 Sep 2014 18:06:36 -0700 Subject: [PATCH 22/46] Basic implementation without tests for findAllRefs/getOccs for 'super' keywords. --- src/compiler/parser.ts | 17 ++++++ src/services/services.ts | 114 +++++++++++++++++++++++---------------- 2 files changed, 84 insertions(+), 47 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index e2d2bb3886b..798c13125d5 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -434,6 +434,23 @@ module ts { } } + export function getSuperContainer(node: Node): Node { + while (true) { + node = node.parent; + if (!node) { + return node; + } + switch (node.kind) { + case SyntaxKind.Property: + case SyntaxKind.Method: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return node; + } + } + } + export function hasRestParameters(s: SignatureDeclaration): boolean { return s.parameters.length > 0 && (s.parameters[s.parameters.length - 1].flags & NodeFlags.Rest) !== 0; } diff --git a/src/services/services.ts b/src/services/services.ts index 12f7e0ce24e..a414e70cc54 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2146,7 +2146,7 @@ module ts { return undefined; } - if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || + if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.SuperKeyword || isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isNameOfExternalModuleImportOrDeclaration(node)) { return getReferencesForNode(node, [sourceFile]); } @@ -2379,6 +2379,7 @@ module ts { if (node.kind !== SyntaxKind.Identifier && node.kind !== SyntaxKind.ThisKeyword && + node.kind !== SyntaxKind.SuperKeyword && !isLiteralNameOfPropertyDeclarationOrIndexAccess(node) && !isNameOfExternalModuleImportOrDeclaration(node)) { return undefined; @@ -2402,8 +2403,8 @@ module ts { } } - if (node.kind === SyntaxKind.ThisKeyword) { - return getReferencesForThisKeyword(node, sourceFiles); + if (node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.SuperKeyword) { + return getReferencesForThisOrSuperKeyword(node, sourceFiles); } var symbol = typeInfoResolver.getSymbolInfo(node); @@ -2627,32 +2628,46 @@ module ts { } } - function getReferencesForThisKeyword(thisKeyword: Node, sourceFiles: SourceFile[]) { - // Get the owner" of the 'this' keyword. - var thisContainer = getThisContainer(thisKeyword, /* includeArrowFunctions */ false); - + function getReferencesForThisOrSuperKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[]): ReferenceEntry[] { + var keywordName: string; var searchSpaceNode: Node; - // Whether 'this' occurs in a static context within a class; + if (thisOrSuperKeyword.kind === SyntaxKind.ThisKeyword) { + keywordName = "this" + searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); + } + else { + keywordName = "super"; + searchSpaceNode = getSuperContainer(thisOrSuperKeyword); + + if (!searchSpaceNode) { + return undefined; + } + } + + // Whether 'this'/'super' occurs in a static context within a class. var staticFlag = NodeFlags.Static; - switch (thisContainer.kind) { + switch (searchSpaceNode.kind) { case SyntaxKind.Property: case SyntaxKind.Method: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: - searchSpaceNode = thisContainer.parent; // should be the owning class - staticFlag &= thisContainer.flags + staticFlag &= searchSpaceNode.flags + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class break; case SyntaxKind.SourceFile: - if (isExternalModule(thisContainer)) { + if (isExternalModule(searchSpaceNode)) { return undefined; } - // Fall through + break; case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: - searchSpaceNode = thisContainer; + // 'super' can only occur within a class. + if (thisOrSuperKeyword.kind === SyntaxKind.SuperKeyword) { + return undefined; + } break; default: return undefined; @@ -2662,55 +2677,60 @@ module ts { if (searchSpaceNode.kind === SyntaxKind.SourceFile) { forEach(sourceFiles, sourceFile => { - var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", sourceFile.getStart(), sourceFile.getEnd()); - getThisReferencesInFile(sourceFile, sourceFile, possiblePositions, result); + var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, keywordName, sourceFile.getStart(), sourceFile.getEnd()); + getThisOrSuperReferencesInFile(sourceFile, sourceFile, possiblePositions, result); }); } else { var sourceFile = searchSpaceNode.getSourceFile(); - var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", searchSpaceNode.getStart(), searchSpaceNode.getEnd()); - getThisReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, result); + var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, keywordName, searchSpaceNode.getStart(), searchSpaceNode.getEnd()); + getThisOrSuperReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, result); } return result; - function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: ReferenceEntry[]): void { + function getThisOrSuperReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: ReferenceEntry[]): void { forEach(possiblePositions, position => { cancellationToken.throwIfCancellationRequested(); var node = getNodeAtPosition(sourceFile, position); - if (!node || node.kind !== SyntaxKind.ThisKeyword) { + if (!node) { return; } - // Get the owner of the 'this' keyword. - // This *should* be a node that occurs somewhere within searchSpaceNode. - var container = getThisContainer(node, /* includeArrowFunctions */ false); + var container = getNodeAtPosition(sourceFile, position); + if (!container) { + return; + } - switch (container.kind) { - case SyntaxKind.Property: - case SyntaxKind.Method: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // Make sure the container belongs to the same class - // and has the appropriate static modifier from the original container. - if (searchSpaceNode.symbol === container.parent.symbol && (container.flags & NodeFlags.Static) === staticFlag) { - result.push(getReferenceEntryFromNode(node)); - } - break; - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - if (searchSpaceNode.symbol === container.symbol) { - result.push(getReferenceEntryFromNode(node)); - } - break; - case SyntaxKind.SourceFile: - // Add all 'this' keywords that belong to the top-level scope. - if (searchSpaceNode.kind === SyntaxKind.SourceFile && !isExternalModule(searchSpaceNode)) { - result.push(getReferenceEntryFromNode(node)); - } - break; + if (node.kind === SyntaxKind.SuperKeyword) { + container = getSuperContainer(node); + } + else if (node.kind === SyntaxKind.ThisKeyword) { + container = getThisContainer(node, /* includeArrowFunctions */ false); + } + + if (container) { + switch (searchSpaceNode.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + if (searchSpaceNode.symbol === container.symbol) { + result.push(getReferenceEntryFromNode(node)); + } + break; + case SyntaxKind.ClassDeclaration: + // Make sure the container belongs to the same class + // and has the appropriate static modifier from the original container. + if (container.parent && searchSpaceNode.symbol === container.parent.symbol && (container.flags & NodeFlags.Static) === staticFlag) { + result.push(getReferenceEntryFromNode(node)); + } + break; + case SyntaxKind.SourceFile: + if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { + result.push(getReferenceEntryFromNode(node)); + } + break; + } } }); } From 1cd0b306ed309e294d5f89221a4b9205d5c26592 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 8 Sep 2014 14:40:44 -0700 Subject: [PATCH 23/46] Added tests for getOccurrences on super. --- tests/cases/fourslash/getOccurrencesSuper.ts | 64 +++++++++++++++++++ tests/cases/fourslash/getOccurrencesSuper2.ts | 64 +++++++++++++++++++ .../fourslash/getOccurrencesSuperNegatives.ts | 27 ++++++++ 3 files changed, 155 insertions(+) create mode 100644 tests/cases/fourslash/getOccurrencesSuper.ts create mode 100644 tests/cases/fourslash/getOccurrencesSuper2.ts create mode 100644 tests/cases/fourslash/getOccurrencesSuperNegatives.ts diff --git a/tests/cases/fourslash/getOccurrencesSuper.ts b/tests/cases/fourslash/getOccurrencesSuper.ts new file mode 100644 index 00000000000..f53d9aca62e --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSuper.ts @@ -0,0 +1,64 @@ +/// + +////class SuperType { +//// superMethod() { +//// } +//// +//// static superStaticMethod() { +//// return 10; +//// } +////} +//// +////class SubType extends SuperType { +//// public prop1 = [|s/**/uper|].superMethod; +//// private prop2 = [|super|].superMethod; +//// +//// constructor() { +//// [|super|](); +//// } +//// +//// public method1() { +//// return [|super|].superMethod(); +//// } +//// +//// private method2() { +//// return [|super|].superMethod(); +//// } +//// +//// public method3() { +//// var x = () => [|super|].superMethod(); +//// +//// // Bad but still gets highlighted +//// function f() { +//// [|super|].superMethod(); +//// } +//// } +//// +//// // Bad but still gets highlighted. +//// public static statProp1 = super.superStaticMethod; +//// +//// public static staticMethod1() { +//// return super.superStaticMethod(); +//// } +//// +//// private static staticMethod2() { +//// return super.superStaticMethod(); +//// } +//// +//// // Are not actually 'super' keywords. +//// super = 10; +//// static super = 20; +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesSuper2.ts b/tests/cases/fourslash/getOccurrencesSuper2.ts new file mode 100644 index 00000000000..1392170ae18 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSuper2.ts @@ -0,0 +1,64 @@ +/// + +////class SuperType { +//// superMethod() { +//// } +//// +//// static superStaticMethod() { +//// return 10; +//// } +////} +//// +////class SubType extends SuperType { +//// public prop1 = super.superMethod; +//// private prop2 = super.superMethod; +//// +//// constructor() { +//// super(); +//// } +//// +//// public method1() { +//// return super.superMethod(); +//// } +//// +//// private method2() { +//// return super.superMethod(); +//// } +//// +//// public method3() { +//// var x = () => super.superMethod(); +//// +//// // Bad but still gets highlighted +//// function f() { +//// super.superMethod(); +//// } +//// } +//// +//// // Bad but still gets highlighted. +//// public static statProp1 = [|super|].superStaticMethod; +//// +//// public static staticMethod1() { +//// return [|super|].superStaticMethod(); +//// } +//// +//// private static staticMethod2() { +//// return [|supe/**/r|].superStaticMethod(); +//// } +//// +//// // Are not actually 'super' keywords. +//// super = 10; +//// static super = 20; +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesSuperNegatives.ts b/tests/cases/fourslash/getOccurrencesSuperNegatives.ts new file mode 100644 index 00000000000..8c76da33c12 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSuperNegatives.ts @@ -0,0 +1,27 @@ +/// + +////function f(x = [|super|]) { +//// [|super|]; +////} +//// +////module M { +//// [|super|]; +//// function f(x = [|super|]) { +//// [|super|]; +//// } +//// +//// class A { +//// } +//// +//// class B extends A { +//// constructor() { +//// super(); +//// } +//// } +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + verify.occurrencesAtPositionCount(0); +}); From 131ac2f188cb2ca5a5b9f16dba5a112cb5341eb5 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 8 Sep 2014 17:44:15 -0700 Subject: [PATCH 24/46] Disabled findAllRefs for 'this'/'super'. --- src/services/services.ts | 5 +++-- tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index a414e70cc54..fa491176269 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2378,8 +2378,9 @@ module ts { } if (node.kind !== SyntaxKind.Identifier && - node.kind !== SyntaxKind.ThisKeyword && - node.kind !== SyntaxKind.SuperKeyword && + // TODO (drosen): This should be enabled in a later release - currently breaks rename. + //node.kind !== SyntaxKind.ThisKeyword && + //node.kind !== SyntaxKind.SuperKeyword && !isLiteralNameOfPropertyDeclarationOrIndexAccess(node) && !isNameOfExternalModuleImportOrDeclaration(node)) { return undefined; diff --git a/tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts b/tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts index bc31ffd1579..4b9f7a450ec 100644 --- a/tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts +++ b/tests/cases/fourslash/findAllRefsThisKeywordMultipleFiles.ts @@ -12,4 +12,7 @@ goTo.file("file1.ts"); goTo.marker(); -verify.referencesCountIs(8); \ No newline at end of file + +// TODO (drosen): The CURRENT behavior is that findAllRefs doesn't work on 'this' or 'super' keywords. +// This should change down the line. +verify.referencesCountIs(0); \ No newline at end of file From 0e93d283e3135414b731b64094fdb4ece9cf0ef1 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 9 Sep 2014 12:41:57 -0700 Subject: [PATCH 25/46] Separated 'super'/'this' keyword searching to simplify logic. --- src/services/services.ts | 138 ++++++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 59 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index fa491176269..c8e73f657a4 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2134,7 +2134,6 @@ module ts { return result; } - /// Find references function getOccurrencesAtPosition(filename: string, position: number): ReferenceEntry[] { synchronizeHostData(); @@ -2404,8 +2403,12 @@ module ts { } } - if (node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.SuperKeyword) { - return getReferencesForThisOrSuperKeyword(node, sourceFiles); + if (node.kind === SyntaxKind.ThisKeyword) { + return getReferencesForThisKeyword(node, sourceFiles); + } + + if (node.kind === SyntaxKind.SuperKeyword) { + return getReferencesForSuperKeyword(node); } var symbol = typeInfoResolver.getSymbolInfo(node); @@ -2629,24 +2632,57 @@ module ts { } } - function getReferencesForThisOrSuperKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[]): ReferenceEntry[] { - var keywordName: string; - var searchSpaceNode: Node; - - if (thisOrSuperKeyword.kind === SyntaxKind.ThisKeyword) { - keywordName = "this" - searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); + function getReferencesForSuperKeyword(superKeyword: Node): ReferenceEntry[]{ + var searchSpaceNode = getSuperContainer(superKeyword); + if (!searchSpaceNode) { + return undefined; } - else { - keywordName = "super"; - searchSpaceNode = getSuperContainer(thisOrSuperKeyword); + // Whether 'super' occurs in a static context within a class. + var staticFlag = NodeFlags.Static; - if (!searchSpaceNode) { + switch (searchSpaceNode.kind) { + case SyntaxKind.Property: + case SyntaxKind.Method: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + staticFlag &= searchSpaceNode.flags; + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class + break; + default: return undefined; - } } - // Whether 'this'/'super' occurs in a static context within a class. + var result: ReferenceEntry[] = []; + + var sourceFile = searchSpaceNode.getSourceFile(); + var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode.getStart(), searchSpaceNode.getEnd()); + forEach(possiblePositions, position => { + cancellationToken.throwIfCancellationRequested(); + + var node = getNodeAtPosition(sourceFile, position); + + if (!node || node.kind !== SyntaxKind.SuperKeyword) { + return; + } + + var container = getSuperContainer(node); + + // If we have a 'super' container, we must have an enclosing class. + // Now make sure the owning class is the same as the search-space + // and has the same static qualifier as the original 'super's owner. + if (container && (NodeFlags.Static & container.flags) === staticFlag && container.parent.symbol === searchSpaceNode.symbol) { + result.push(getReferenceEntryFromNode(node)); + } + }); + + return result; + } + + function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[]): ReferenceEntry[] { + var searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); + + // Whether 'this' occurs in a static context within a class. var staticFlag = NodeFlags.Static; switch (searchSpaceNode.kind) { @@ -2662,13 +2698,9 @@ module ts { if (isExternalModule(searchSpaceNode)) { return undefined; } - break; + // Fall through case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: - // 'super' can only occur within a class. - if (thisOrSuperKeyword.kind === SyntaxKind.SuperKeyword) { - return undefined; - } break; default: return undefined; @@ -2678,60 +2710,48 @@ module ts { if (searchSpaceNode.kind === SyntaxKind.SourceFile) { forEach(sourceFiles, sourceFile => { - var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, keywordName, sourceFile.getStart(), sourceFile.getEnd()); - getThisOrSuperReferencesInFile(sourceFile, sourceFile, possiblePositions, result); + var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", sourceFile.getStart(), sourceFile.getEnd()); + getThisReferencesInFile(sourceFile, sourceFile, possiblePositions, result); }); } else { var sourceFile = searchSpaceNode.getSourceFile(); - var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, keywordName, searchSpaceNode.getStart(), searchSpaceNode.getEnd()); - getThisOrSuperReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, result); + var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", searchSpaceNode.getStart(), searchSpaceNode.getEnd()); + getThisReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, result); } return result; - function getThisOrSuperReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: ReferenceEntry[]): void { + function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: ReferenceEntry[]): void { forEach(possiblePositions, position => { cancellationToken.throwIfCancellationRequested(); var node = getNodeAtPosition(sourceFile, position); - if (!node) { + if (!node || node.kind !== SyntaxKind.ThisKeyword) { return; } - var container = getNodeAtPosition(sourceFile, position); - if (!container) { - return; - } + var container = getThisContainer(node, /* includeArrowFunctions */ false); - if (node.kind === SyntaxKind.SuperKeyword) { - container = getSuperContainer(node); - } - else if (node.kind === SyntaxKind.ThisKeyword) { - container = getThisContainer(node, /* includeArrowFunctions */ false); - } - - if (container) { - switch (searchSpaceNode.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - if (searchSpaceNode.symbol === container.symbol) { - result.push(getReferenceEntryFromNode(node)); - } - break; - case SyntaxKind.ClassDeclaration: - // Make sure the container belongs to the same class - // and has the appropriate static modifier from the original container. - if (container.parent && searchSpaceNode.symbol === container.parent.symbol && (container.flags & NodeFlags.Static) === staticFlag) { - result.push(getReferenceEntryFromNode(node)); - } - break; - case SyntaxKind.SourceFile: - if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { - result.push(getReferenceEntryFromNode(node)); - } - break; - } + switch (searchSpaceNode.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + if (searchSpaceNode.symbol === container.symbol) { + result.push(getReferenceEntryFromNode(node)); + } + break; + case SyntaxKind.ClassDeclaration: + // Make sure the container belongs to the same class + // and has the appropriate static modifier from the original container. + if (container.parent && searchSpaceNode.symbol === container.parent.symbol && (container.flags & NodeFlags.Static) === staticFlag) { + result.push(getReferenceEntryFromNode(node)); + } + break; + case SyntaxKind.SourceFile: + if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { + result.push(getReferenceEntryFromNode(node)); + } + break; } }); } From 83c35ad059a440c0a49e31365967bcab1c13b10e Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 9 Sep 2014 12:50:14 -0700 Subject: [PATCH 26/46] Addressed CR feedback. --- src/compiler/parser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 798c13125d5..a89152e043c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -411,7 +411,7 @@ module ts { while (true) { node = node.parent; if (!node) { - return node; + return undefined; } switch (node.kind) { case SyntaxKind.ArrowFunction: @@ -438,7 +438,7 @@ module ts { while (true) { node = node.parent; if (!node) { - return node; + return undefined; } switch (node.kind) { case SyntaxKind.Property: From 69803d4d3c6e7751959e75d2430131542d39af16 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 4 Sep 2014 13:36:32 -0700 Subject: [PATCH 27/46] Implemented getOccurrences for for/for-in/while/do-while loops and their breaks/continues. This includes labelled break/continue. --- src/services/services.ts | 112 ++++++++++++++++++++++++++++++++++----- 1 file changed, 98 insertions(+), 14 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index c8e73f657a4..b0b4c21f438 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1296,6 +1296,16 @@ module ts { (node.parent).label === node; } + function isLabelledBy(node: Node, labelName: string) { + for (var owner = node.parent; owner && owner.kind === SyntaxKind.LabelledStatement; owner = owner.parent) { + if ((owner).label.text === labelName) { + return true; + } + } + + return false; + } + function isLabelName(node: Node): boolean { return isLabelOfLabeledStatement(node) || isJumpStatementTarget(node); } @@ -2181,8 +2191,20 @@ module ts { } break; case SyntaxKind.BreakKeyword: - if (hasKind(node.parent, SyntaxKind.BreakStatement)) { - return getBreakStatementOccurences(node.parent); + case SyntaxKind.ContinueKeyword: + if (hasKind(node.parent, SyntaxKind.BreakStatement) || hasKind(node.parent, SyntaxKind.ContinueStatement)) { + return getBreakOrContinueStatementOccurences(node.parent); + } + break; + case SyntaxKind.ForKeyword: + if (hasKind(node.parent, SyntaxKind.ForStatement) || hasKind(node.parent, SyntaxKind.ForInStatement)) { + return getLoopBreakContinueOccurrences(node.parent); + } + break; + case SyntaxKind.WhileKeyword: + case SyntaxKind.DoKeyword: + if (hasKind(node.parent, SyntaxKind.WhileStatement) || hasKind(node.parent, SyntaxKind.DoStatement)) { + return getLoopBreakContinueOccurrences(node.parent); } break; } @@ -2281,6 +2303,66 @@ module ts { return map(keywords, getReferenceEntryFromNode); } + function getLoopBreakContinueOccurrences(loopNode: IterationStatement): ReferenceEntry[] { + var keywords: Node[] = []; + + if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) { + // If we succeeded and got a do-while loop, then start looking for a 'while' keyword. + if (loopNode.kind === SyntaxKind.DoStatement) { + var loopTokens = loopNode.getChildren(); + + for (var i = loopTokens.length - 1; i >= 0; i--) { + if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) { + break; + } + } + } + } + + // This switch tracks whether or not we're traversing into a construct that takes + // ownership over unlabelled 'break'/'continue' statements. + var onlyCheckLabelled = false; + + forEachChild(loopNode.statement, function aggregateBreakContinues(node: Node) { + // This tracks the status of the flag before diving into the next node. + var lastOnlyCheckLabelled = onlyCheckLabelled; + + switch (node.kind) { + case SyntaxKind.BreakStatement: + case SyntaxKind.ContinueStatement: + // If the 'break'/'continue' statement has a label, it must be one of our tracked labels. + if ((node).label) { + var labelName = (node).label.text; + if (isLabelledBy(loopNode, labelName)) { + pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); + } + } + // If not, we are free to add it if we haven't lost ownership of unlabeled break/continue statements. + else if (!onlyCheckLabelled) { + pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); + } + break; + + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.SwitchStatement: + onlyCheckLabelled = true; + // Fall through + default: + // Do not cross function boundaries. + if (!isAnyFunction(node)) { + forEachChild(node, aggregateBreakContinues); + } + } + // Restore the last state. + onlyCheckLabelled = lastOnlyCheckLabelled; + }); + + return map(keywords, keywordToReferenceEntry); + } + function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement) { var keywords: Node[] = []; @@ -2317,28 +2399,30 @@ module ts { return map(keywords, getReferenceEntryFromNode); } - function getBreakStatementOccurences(breakStatement: BreakOrContinueStatement): ReferenceEntry[]{ - // TODO (drosen): Deal with labeled statements. - if (breakStatement.label) { - return undefined; - } - + function getBreakOrContinueStatementOccurences(breakOrContinueStatement: BreakOrContinueStatement): ReferenceEntry[]{ for (var owner = node.parent; owner; owner = owner.parent) { switch (owner.kind) { case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: - // TODO (drosen): Handle loops! - return undefined; - + // The iteration statement is the owner if the break/continue statement is either unlabeled, + // or if the break/continue statement's label corresponds to one of the loop's labels. + if (!breakOrContinueStatement.label || isLabelledBy(owner, breakOrContinueStatement.label.text)) { + return getLoopBreakContinueOccurrences(owner) + } + break; case SyntaxKind.SwitchStatement: - return getSwitchCaseDefaultOccurrences(owner); - + // A switch statement can only be the owner of an unlabeled break statement. + if (breakOrContinueStatement.kind === SyntaxKind.BreakStatement && !breakOrContinueStatement.label) { + return getSwitchCaseDefaultOccurrences(owner); + } + break; default: if (isAnyFunction(owner)) { return undefined; } + break; } } @@ -2347,7 +2431,7 @@ module ts { // returns true if 'node' is defined and has a matching 'kind'. function hasKind(node: Node, kind: SyntaxKind) { - return !!(node && node.kind === kind); + return node !== undefined && node.kind === kind; } // Null-propagating 'parent' function. From 90dd3276359eb8cd747a1978f02cf4599a2e3ed3 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 5 Sep 2014 10:59:20 -0700 Subject: [PATCH 28/46] Changed logic for break/continue search in switch statements and loops. Now if a labeled break in a switch refers to its original switch statement, we also highlight the 'switch' keyword. Also added tests for loop/break/continue. --- src/services/services.ts | 105 ++++++++++++------ .../getOccurrencesLoopBreakContinue.ts | 77 +++++++++++++ .../getOccurrencesLoopBreakContinue2.ts | 77 +++++++++++++ .../getOccurrencesLoopBreakContinue3.ts | 77 +++++++++++++ .../getOccurrencesLoopBreakContinue4.ts | 77 +++++++++++++ .../getOccurrencesLoopBreakContinue5.ts | 77 +++++++++++++ ...etOccurrencesLoopBreakContinueNegatives.ts | 70 ++++++++++++ .../getOccurrencesSwitchCaseDefault2.ts | 13 ++- .../getOccurrencesSwitchCaseDefault3.ts | 25 +++++ 9 files changed, 561 insertions(+), 37 deletions(-) create mode 100644 tests/cases/fourslash/getOccurrencesLoopBreakContinue.ts create mode 100644 tests/cases/fourslash/getOccurrencesLoopBreakContinue2.ts create mode 100644 tests/cases/fourslash/getOccurrencesLoopBreakContinue3.ts create mode 100644 tests/cases/fourslash/getOccurrencesLoopBreakContinue4.ts create mode 100644 tests/cases/fourslash/getOccurrencesLoopBreakContinue5.ts create mode 100644 tests/cases/fourslash/getOccurrencesLoopBreakContinueNegatives.ts create mode 100644 tests/cases/fourslash/getOccurrencesSwitchCaseDefault3.ts diff --git a/src/services/services.ts b/src/services/services.ts index b0b4c21f438..3ef524c82ef 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1359,11 +1359,19 @@ module ts { } enum SearchMeaning { + None = 0x0, Value = 0x1, Type = 0x2, Namespace = 0x4 } + enum BreakContinueSearchType { + None = 0x0, + Unlabeled = 0x1, + Labeled = 0x2, + All = Unlabeled | Labeled + } + // A cache of completion entries for keywords, these do not change between sessions var keywordCompletions:CompletionEntry[] = []; for (var i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { @@ -2318,49 +2326,45 @@ module ts { } } } + + // These track whether we can own unlabeled break/continues. + var breakSearchType = BreakContinueSearchType.All; + var continueSearchType = BreakContinueSearchType.All; - // This switch tracks whether or not we're traversing into a construct that takes - // ownership over unlabelled 'break'/'continue' statements. - var onlyCheckLabelled = false; - - forEachChild(loopNode.statement, function aggregateBreakContinues(node: Node) { - // This tracks the status of the flag before diving into the next node. - var lastOnlyCheckLabelled = onlyCheckLabelled; + (function aggregateBreakContinues(node: Node) { + // Remember the statuses of the flags before diving into the next node. + var prevBreakSearchType = breakSearchType; + var prevContinueSearchType = continueSearchType; switch (node.kind) { case SyntaxKind.BreakStatement: case SyntaxKind.ContinueStatement: - // If the 'break'/'continue' statement has a label, it must be one of our tracked labels. - if ((node).label) { - var labelName = (node).label.text; - if (isLabelledBy(loopNode, labelName)) { - pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); - } - } - // If not, we are free to add it if we haven't lost ownership of unlabeled break/continue statements. - else if (!onlyCheckLabelled) { + if (ownsBreakOrContinue(loopNode, node, breakSearchType, continueSearchType)) { pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); } break; - + case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: - case SyntaxKind.SwitchStatement: - onlyCheckLabelled = true; + continueSearchType = BreakContinueSearchType.Labeled; // Fall through - default: - // Do not cross function boundaries. - if (!isAnyFunction(node)) { - forEachChild(node, aggregateBreakContinues); - } + case SyntaxKind.SwitchStatement: + breakSearchType = BreakContinueSearchType.Labeled; } - // Restore the last state. - onlyCheckLabelled = lastOnlyCheckLabelled; - }); - return map(keywords, keywordToReferenceEntry); + // Do not cross function boundaries. + if (!isAnyFunction(node)) { + forEachChild(node, aggregateBreakContinues); + } + + // Restore the last state. + breakSearchType = prevBreakSearchType; + continueSearchType = prevContinueSearchType; + })(loopNode.statement); + + return map(keywords, getReferenceEntryFromNode); } function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement) { @@ -2368,38 +2372,50 @@ module ts { pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword); - // Go through each clause in the switch statement, collecting the clause keywords. + // Types of break statements we can grab on to. + var breakSearchType = BreakContinueSearchType.All; + + // Go through each clause in the switch statement, collecting the case/default keywords. forEach(switchStatement.clauses, clause => { pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword); // For each clause, also recursively traverse the statements where we can find analogous breaks. forEachChild(clause, function aggregateBreakKeywords(node: Node): void { + // Back the old search value up. + var oldBreakSearchType = breakSearchType; + switch (node.kind) { case SyntaxKind.BreakStatement: // If the break statement has a label, it cannot be part of a switch block. - if (!(node).label) { + if (ownsBreakOrContinue(switchStatement, + node, + breakSearchType, + /*continuesSearchType*/ BreakContinueSearchType.None)) { pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.BreakKeyword); } - // Fall through + break; case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: case SyntaxKind.SwitchStatement: - return; + breakSearchType = BreakContinueSearchType.Labeled; } // Do not cross function boundaries. if (!isAnyFunction(node)) { forEachChild(node, aggregateBreakKeywords); } + + // Restore the last state. + breakSearchType = oldBreakSearchType; }); }); return map(keywords, getReferenceEntryFromNode); } - function getBreakOrContinueStatementOccurences(breakOrContinueStatement: BreakOrContinueStatement): ReferenceEntry[]{ + function getBreakOrContinueStatementOccurences(breakOrContinueStatement: BreakOrContinueStatement): ReferenceEntry[] { for (var owner = node.parent; owner; owner = owner.parent) { switch (owner.kind) { case SyntaxKind.ForStatement: @@ -2413,8 +2429,8 @@ module ts { } break; case SyntaxKind.SwitchStatement: - // A switch statement can only be the owner of an unlabeled break statement. - if (breakOrContinueStatement.kind === SyntaxKind.BreakStatement && !breakOrContinueStatement.label) { + // A switch statement can only be the owner of an break statement. + if (breakOrContinueStatement.kind === SyntaxKind.BreakStatement && (!breakOrContinueStatement.label || isLabelledBy(owner, breakOrContinueStatement.label.text))) { return getSwitchCaseDefaultOccurrences(owner); } break; @@ -2429,6 +2445,25 @@ module ts { return undefined; } + // Note: 'statement' must be a descendant of 'root'. + // Reasonable logic for restricting traversal prior to arriving at the + // 'statement' node is beyond the scope of this function. + function ownsBreakOrContinue(root: Node, + statement: BreakOrContinueStatement, + breakSearchType: BreakContinueSearchType, + continueSearchType: BreakContinueSearchType): boolean { + var searchType = statement.kind === SyntaxKind.BreakStatement ? + breakSearchType : + continueSearchType; + + if (statement.label) { + return isLabelledBy(root, statement.label.text); + } + else { + return !!(searchType & BreakContinueSearchType.Unlabeled); + } + } + // returns true if 'node' is defined and has a matching 'kind'. function hasKind(node: Node, kind: SyntaxKind) { return node !== undefined && node.kind === kind; diff --git a/tests/cases/fourslash/getOccurrencesLoopBreakContinue.ts b/tests/cases/fourslash/getOccurrencesLoopBreakContinue.ts new file mode 100644 index 00000000000..aad909f9737 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesLoopBreakContinue.ts @@ -0,0 +1,77 @@ +/// + +////var arr = [1, 2, 3, 4]; +////label1: [|for|] (var n in arr) { +//// [|break|]; +//// [|continue|]; +//// [|br/**/eak|] label1; +//// [|continue|] label1; +//// +//// label2: for (var i = 0; i < arr[n]; i++) { +//// [|break|] label1; +//// [|continue|] label1; +//// +//// break; +//// continue; +//// break label2; +//// continue label2; +//// +//// function foo() { +//// label3: while (true) { +//// break; +//// continue; +//// break label3; +//// continue label3; +//// +//// // these cross function boundaries +//// break label1; +//// continue label1; +//// break label2; +//// continue label2; +//// +//// label4: do { +//// break; +//// continue; +//// break label4; +//// continue label4; +//// +//// break label3; +//// continue label3; +//// +//// switch (10) { +//// case 1: +//// case 2: +//// break; +//// break label4; +//// default: +//// continue; +//// } +//// +//// // these cross function boundaries +//// break label1; +//// continue label1; +//// break label2; +//// continue label2; +//// () => { break; } +//// } while (true) +//// } +//// } +//// } +////} +//// +////label5: while (true) break label5; +//// +////label7: while (true) continue label5; + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesLoopBreakContinue2.ts b/tests/cases/fourslash/getOccurrencesLoopBreakContinue2.ts new file mode 100644 index 00000000000..cae9845e06f --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesLoopBreakContinue2.ts @@ -0,0 +1,77 @@ +/// + +////var arr = [1, 2, 3, 4]; +////label1: for (var n in arr) { +//// break; +//// continue; +//// break label1; +//// continue label1; +//// +//// label2: [|f/**/or|] (var i = 0; i < arr[n]; i++) { +//// break label1; +//// continue label1; +//// +//// [|break|]; +//// [|continue|]; +//// [|break|] label2; +//// [|continue|] label2; +//// +//// function foo() { +//// label3: while (true) { +//// break; +//// continue; +//// break label3; +//// continue label3; +//// +//// // these cross function boundaries +//// break label1; +//// continue label1; +//// break label2; +//// continue label2; +//// +//// label4: do { +//// break; +//// continue; +//// break label4; +//// continue label4; +//// +//// break label3; +//// continue label3; +//// +//// switch (10) { +//// case 1: +//// case 2: +//// break; +//// break label4; +//// default: +//// continue; +//// } +//// +//// // these cross function boundaries +//// break label1; +//// continue label1; +//// break label2; +//// continue label2; +//// () => { break; +//// } while (true) +//// } +//// } +//// } +////} +//// +////label5: while (true) break label5; +//// +////label7: while (true) continue label5; + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesLoopBreakContinue3.ts b/tests/cases/fourslash/getOccurrencesLoopBreakContinue3.ts new file mode 100644 index 00000000000..571114ea153 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesLoopBreakContinue3.ts @@ -0,0 +1,77 @@ +/// + +////var arr = [1, 2, 3, 4]; +////label1: for (var n in arr) { +//// break; +//// continue; +//// break label1; +//// continue label1; +//// +//// label2: for (var i = 0; i < arr[n]; i++) { +//// break label1; +//// continue label1; +//// +//// break; +//// continue; +//// break label2; +//// continue label2; +//// +//// function foo() { +//// label3: [|w/**/hile|] (true) { +//// [|break|]; +//// [|continue|]; +//// [|break|] label3; +//// [|continue|] label3; +//// +//// // these cross function boundaries +//// break label1; +//// continue label1; +//// break label2; +//// continue label2; +//// +//// label4: do { +//// break; +//// continue; +//// break label4; +//// continue label4; +//// +//// [|break|] label3; +//// [|continue|] label3; +//// +//// switch (10) { +//// case 1: +//// case 2: +//// break; +//// break label4; +//// default: +//// continue; +//// } +//// +//// // these cross function boundaries +//// break label1; +//// continue label1; +//// break label2; +//// continue label2; +//// () => { break; } +//// } while (true) +//// } +//// } +//// } +////} +//// +////label5: while (true) break label5; +//// +////label7: while (true) continue label5; + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesLoopBreakContinue4.ts b/tests/cases/fourslash/getOccurrencesLoopBreakContinue4.ts new file mode 100644 index 00000000000..587fb1a0938 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesLoopBreakContinue4.ts @@ -0,0 +1,77 @@ +/// + +////var arr = [1, 2, 3, 4]; +////label1: for (var n in arr) { +//// break; +//// continue; +//// break label1; +//// continue label1; +//// +//// label2: for (var i = 0; i < arr[n]; i++) { +//// break label1; +//// continue label1; +//// +//// break; +//// continue; +//// break label2; +//// continue label2; +//// +//// function foo() { +//// label3: while (true) { +//// break; +//// continue; +//// break label3; +//// continue label3; +//// +//// // these cross function boundaries +//// break label1; +//// continue label1; +//// break label2; +//// continue label2; +//// +//// label4: [|do|] { +//// [|break|]; +//// [|continue|]; +//// [|break|] label4; +//// [|continue|] label4; +//// +//// break label3; +//// continue label3; +//// +//// switch (10) { +//// case 1: +//// case 2: +//// break; +//// [|break|] label4; +//// default: +//// [|continue|]; +//// } +//// +//// // these cross function boundaries +//// break label1; +//// continue label1; +//// break label2; +//// continue label2; +//// () => { break; } +//// } [|wh/**/ile|] (true) +//// } +//// } +//// } +////} +//// +////label5: while (true) break label5; +//// +////label7: while (true) continue label5; + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesLoopBreakContinue5.ts b/tests/cases/fourslash/getOccurrencesLoopBreakContinue5.ts new file mode 100644 index 00000000000..d558e6f3854 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesLoopBreakContinue5.ts @@ -0,0 +1,77 @@ +/// + +////var arr = [1, 2, 3, 4]; +////label1: for (var n in arr) { +//// break; +//// continue; +//// break label1; +//// continue label1; +//// +//// label2: for (var i = 0; i < arr[n]; i++) { +//// break label1; +//// continue label1; +//// +//// break; +//// continue; +//// break label2; +//// continue label2; +//// +//// function foo() { +//// label3: while (true) { +//// break; +//// continue; +//// break label3; +//// continue label3; +//// +//// // these cross function boundaries +//// break label1; +//// continue label1; +//// break label2; +//// continue label2; +//// +//// label4: do { +//// break; +//// continue; +//// break label4; +//// continue label4; +//// +//// break label3; +//// continue label3; +//// +//// switch (10) { +//// case 1: +//// case 2: +//// break; +//// break label4; +//// default: +//// continue; +//// } +//// +//// // these cross function boundaries +//// break label1; +//// continue label1; +//// break label2; +//// continue label2; +//// () => { break; } +//// } while (true) +//// } +//// } +//// } +////} +//// +////label5: [|while|] (true) [|br/**/eak|] label5; +//// +////label7: while (true) continue label5; + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesLoopBreakContinueNegatives.ts b/tests/cases/fourslash/getOccurrencesLoopBreakContinueNegatives.ts new file mode 100644 index 00000000000..0127245dd50 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesLoopBreakContinueNegatives.ts @@ -0,0 +1,70 @@ +/// + +////var arr = [1, 2, 3, 4]; +////label1: for (var n in arr) { +//// break; +//// continue; +//// break label1; +//// continue label1; +//// +//// label2: for (var i = 0; i < arr[n]; i++) { +//// break label1; +//// continue label1; +//// +//// break; +//// continue; +//// break label2; +//// continue label2; +//// +//// function foo() { +//// label3: while (true) { +//// break; +//// continue; +//// break label3; +//// continue label3; +//// +//// // these cross function boundaries +//// br/*1*/eak label1; +//// cont/*2*/inue label1; +//// bre/*3*/ak label2; +//// c/*4*/ontinue label2; +//// +//// label4: do { +//// break; +//// continue; +//// break label4; +//// continue label4; +//// +//// break label3; +//// continue label3; +//// +//// switch (10) { +//// case 1: +//// case 2: +//// break; +//// break label4; +//// default: +//// continue; +//// } +//// +//// // these cross function boundaries +//// br/*5*/eak label1; +//// co/*6*/ntinue label1; +//// br/*7*/eak label2; +//// con/*8*/tinue label2; +//// () => { b/*9*/reak; } +//// } while (true) +//// } +//// } +//// } +////} +//// +////label5: while (true) break label5; +//// +////label7: while (true) co/*10*/ntinue label5; + +test.markers().forEach(m => { + goTo.position(m.position); + + verify.occurrencesAtPositionCount(0); +}); diff --git a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault2.ts b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault2.ts index a8c91f530ca..dc0db1f4b35 100644 --- a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault2.ts +++ b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault2.ts @@ -10,7 +10,7 @@ //// [|cas/*3*/e|] 2: //// [|b/*4*/reak|]; //// [|defaul/*5*/t|]: -//// break foo; +//// [|break|] foo; //// } //// case 0xBEEF: //// default: @@ -19,9 +19,18 @@ ////} +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + + for (var i = 1; i <= test.markers().length; i++) { goTo.marker("" + i); - verify.occurrencesAtPositionCount(5); + verify.occurrencesAtPositionCount(6); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); diff --git a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault3.ts b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault3.ts new file mode 100644 index 00000000000..959ce9d9939 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault3.ts @@ -0,0 +1,25 @@ +/// + +////foo: [|switch|] (1) { +//// [|case|] 1: +//// [|case|] 2: +//// [|break|]; +//// [|case|] 3: +//// switch (2) { +//// case 1: +//// [|break|] foo; +//// continue; // invalid +//// default: +//// break; +//// } +//// [|default|]: +//// [|break|]; +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); From 6cc0305a5d3210c406d088f8e73d647fe8844e08 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 9 Sep 2014 14:54:19 -0700 Subject: [PATCH 29/46] Implemented getOccurrences for 'constructor' keywords. --- src/services/services.ts | 23 +++++++++++-- .../fourslash/getOccurrencesConstructor.ts | 32 +++++++++++++++++++ .../fourslash/getOccurrencesConstructor2.ts | 32 +++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 tests/cases/fourslash/getOccurrencesConstructor.ts create mode 100644 tests/cases/fourslash/getOccurrencesConstructor2.ts diff --git a/src/services/services.ts b/src/services/services.ts index c8e73f657a4..d51eeee97c0 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2185,6 +2185,11 @@ module ts { return getBreakStatementOccurences(node.parent); } break; + case SyntaxKind.ConstructorKeyword: + if (hasKind(node.parent, SyntaxKind.Constructor)) { + return getConstructorOccurrences(node.parent); + } + break; } return undefined; @@ -2249,7 +2254,7 @@ module ts { return result; } - function getReturnOccurrences(returnStatement: ReturnStatement): ReferenceEntry[]{ + function getReturnOccurrences(returnStatement: ReturnStatement): ReferenceEntry[] { var func = getContainingFunction(returnStatement); // If we didn't find a containing function with a block body, bail out. @@ -2317,7 +2322,7 @@ module ts { return map(keywords, getReferenceEntryFromNode); } - function getBreakStatementOccurences(breakStatement: BreakOrContinueStatement): ReferenceEntry[]{ + function getBreakStatementOccurences(breakStatement: BreakOrContinueStatement): ReferenceEntry[] { // TODO (drosen): Deal with labeled statements. if (breakStatement.label) { return undefined; @@ -2345,6 +2350,20 @@ module ts { return undefined; } + function getConstructorOccurrences(constructorDeclaration: ConstructorDeclaration): ReferenceEntry[] { + var declarations = constructorDeclaration.symbol.getDeclarations() + + var keywords: Node[] = []; + + forEach(declarations, declaration => { + forEach(declaration.getChildren(), token => { + return pushKeywordIf(keywords, token, SyntaxKind.ConstructorKeyword); + }); + }); + + return map(keywords, getReferenceEntryFromNode); + } + // returns true if 'node' is defined and has a matching 'kind'. function hasKind(node: Node, kind: SyntaxKind) { return !!(node && node.kind === kind); diff --git a/tests/cases/fourslash/getOccurrencesConstructor.ts b/tests/cases/fourslash/getOccurrencesConstructor.ts new file mode 100644 index 00000000000..0a6b84a5770 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesConstructor.ts @@ -0,0 +1,32 @@ +/// + +////class C { +//// [|const/**/ructor|](); +//// [|constructor|](x: number); +//// [|constructor|](y: string, x: number); +//// [|constructor|](a?: any, ...r: any[]) { +//// if (a === undefined && r.length === 0) { +//// return; +//// } +//// +//// return; +//// } +////} +//// +////class D { +//// constructor(public x: number, public y: number) { +//// } +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesConstructor2.ts b/tests/cases/fourslash/getOccurrencesConstructor2.ts new file mode 100644 index 00000000000..049c25b7f52 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesConstructor2.ts @@ -0,0 +1,32 @@ +/// + +////class C { +//// constructor(); +//// constructor(x: number); +//// constructor(y: string, x: number); +//// constructor(a?: any, ...r: any[]) { +//// if (a === undefined && r.length === 0) { +//// return; +//// } +//// +//// return; +//// } +////} +//// +////class D { +//// [|con/**/structor|](public x: number, public y: number) { +//// } +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +goTo.marker(); +test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); +}); \ No newline at end of file From 2e2d0c3bf18a0cdb48b6fe4061d9b0646cf366b1 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 9 Sep 2014 17:43:46 -0700 Subject: [PATCH 30/46] Extracted 'break'/'continue' aggregation into common helper function. Also addressed other CR feedback. Still need tests. --- src/services/services.ts | 138 ++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 73 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 3ef524c82ef..00be1a75237 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1296,8 +1296,12 @@ module ts { (node.parent).label === node; } + /** + * Whether or not a 'node' is preceded by a label of the given string. + * Note: 'node' cannot be a SourceFile. + */ function isLabelledBy(node: Node, labelName: string) { - for (var owner = node.parent; owner && owner.kind === SyntaxKind.LabelledStatement; owner = owner.parent) { + for (var owner = node.parent; owner.kind === SyntaxKind.LabelledStatement; owner = owner.parent) { if ((owner).label.text === labelName) { return true; } @@ -2326,43 +2330,12 @@ module ts { } } } - - // These track whether we can own unlabeled break/continues. - var breakSearchType = BreakContinueSearchType.All; - var continueSearchType = BreakContinueSearchType.All; - (function aggregateBreakContinues(node: Node) { - // Remember the statuses of the flags before diving into the next node. - var prevBreakSearchType = breakSearchType; - var prevContinueSearchType = continueSearchType; - - switch (node.kind) { - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - if (ownsBreakOrContinue(loopNode, node, breakSearchType, continueSearchType)) { - pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); - } - break; - - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - continueSearchType = BreakContinueSearchType.Labeled; - // Fall through - case SyntaxKind.SwitchStatement: - breakSearchType = BreakContinueSearchType.Labeled; - } - - // Do not cross function boundaries. - if (!isAnyFunction(node)) { - forEachChild(node, aggregateBreakContinues); - } - - // Restore the last state. - breakSearchType = prevBreakSearchType; - continueSearchType = prevContinueSearchType; - })(loopNode.statement); + aggregateBreakAndContinueKeywords(/* owner */ loopNode, + /* startPoint */ loopNode.statement, + /* breakSearchType */ BreakContinueSearchType.All, + /* continueSearchType */ BreakContinueSearchType.All, + /* keywordAccumulator */ keywords); return map(keywords, getReferenceEntryFromNode); } @@ -2375,41 +2348,16 @@ module ts { // Types of break statements we can grab on to. var breakSearchType = BreakContinueSearchType.All; - // Go through each clause in the switch statement, collecting the case/default keywords. + // Go through each clause in the switch statement, collecting the 'case'/'default' keywords. forEach(switchStatement.clauses, clause => { pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword); - // For each clause, also recursively traverse the statements where we can find analogous breaks. - forEachChild(clause, function aggregateBreakKeywords(node: Node): void { - // Back the old search value up. - var oldBreakSearchType = breakSearchType; - - switch (node.kind) { - case SyntaxKind.BreakStatement: - // If the break statement has a label, it cannot be part of a switch block. - if (ownsBreakOrContinue(switchStatement, - node, - breakSearchType, - /*continuesSearchType*/ BreakContinueSearchType.None)) { - pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.BreakKeyword); - } - break; - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.SwitchStatement: - breakSearchType = BreakContinueSearchType.Labeled; - } - - // Do not cross function boundaries. - if (!isAnyFunction(node)) { - forEachChild(node, aggregateBreakKeywords); - } - - // Restore the last state. - breakSearchType = oldBreakSearchType; - }); + // For each clause, aggregate each of the analogous 'break' statements. + aggregateBreakAndContinueKeywords(/* owner */ switchStatement, + /* startPoint */ clause, + /* breakSearchType */ BreakContinueSearchType.All, + /* continueSearchType */ BreakContinueSearchType.None, + /* keywordAccumulator */ keywords); }); return map(keywords, getReferenceEntryFromNode); @@ -2445,10 +2393,54 @@ module ts { return undefined; } + function aggregateBreakAndContinueKeywords(owner: Node, + startPoint: Node, + breakSearchType: BreakContinueSearchType, + continueSearchType: BreakContinueSearchType, + keywordAccumulator: Node[]): void { + (function aggregate(node: Node) { + // Remember the statuses of the flags before diving into the next node. + var prevBreakSearchType = breakSearchType; + var prevContinueSearchType = continueSearchType; + + switch (node.kind) { + case SyntaxKind.BreakStatement: + case SyntaxKind.ContinueStatement: + if (ownsBreakOrContinue(owner, node, breakSearchType, continueSearchType)) { + pushKeywordIf(keywordAccumulator, node.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); + } + break; + + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + // Inner loops take ownership of unlabeled 'continue' statements. + continueSearchType &= ~BreakContinueSearchType.Unlabeled; + // Fall through + case SyntaxKind.SwitchStatement: + // Inner loops & 'switch' statements take ownership of unlabeled 'break' statements. + breakSearchType &= ~BreakContinueSearchType.Unlabeled; + break; + } + + // Do not cross function boundaries. + if (!isAnyFunction(node)) { + forEachChild(node, aggregate); + } + + // Restore the last state. + breakSearchType = prevBreakSearchType; + continueSearchType = prevContinueSearchType; + })(startPoint); + + return; + } + // Note: 'statement' must be a descendant of 'root'. // Reasonable logic for restricting traversal prior to arriving at the // 'statement' node is beyond the scope of this function. - function ownsBreakOrContinue(root: Node, + function ownsBreakOrContinue(owner: Node, statement: BreakOrContinueStatement, breakSearchType: BreakContinueSearchType, continueSearchType: BreakContinueSearchType): boolean { @@ -2456,8 +2448,8 @@ module ts { breakSearchType : continueSearchType; - if (statement.label) { - return isLabelledBy(root, statement.label.text); + if (statement.label && (searchType & BreakContinueSearchType.Labeled)) { + return isLabelledBy(owner, statement.label.text); } else { return !!(searchType & BreakContinueSearchType.Unlabeled); @@ -2817,7 +2809,7 @@ module ts { if (isExternalModule(searchSpaceNode)) { return undefined; } - // Fall through + // Fall through case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: break; From d98a11e6f7858c26ed91188d3ab7e2813af87b30 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 10 Sep 2014 11:54:10 -0700 Subject: [PATCH 31/46] Modified tests and added a test for labeled continues in a switch block. --- .../getOccurrencesLoopBreakContinue.ts | 2 ++ .../getOccurrencesLoopBreakContinue2.ts | 2 ++ .../getOccurrencesLoopBreakContinue3.ts | 1 + .../getOccurrencesLoopBreakContinue4.ts | 2 ++ .../getOccurrencesLoopBreakContinue5.ts | 2 ++ .../getOccurrencesSwitchCaseDefault.ts | 26 +++++++++---------- .../getOccurrencesSwitchCaseDefault2.ts | 21 +++++---------- .../getOccurrencesSwitchCaseDefault3.ts | 1 + .../getOccurrencesSwitchCaseDefault4.ts | 25 ++++++++++++++++++ 9 files changed, 54 insertions(+), 28 deletions(-) create mode 100644 tests/cases/fourslash/getOccurrencesSwitchCaseDefault4.ts diff --git a/tests/cases/fourslash/getOccurrencesLoopBreakContinue.ts b/tests/cases/fourslash/getOccurrencesLoopBreakContinue.ts index aad909f9737..81aaaed5c58 100644 --- a/tests/cases/fourslash/getOccurrencesLoopBreakContinue.ts +++ b/tests/cases/fourslash/getOccurrencesLoopBreakContinue.ts @@ -65,6 +65,7 @@ test.ranges().forEach(r => { goTo.position(r.start); + verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); @@ -72,6 +73,7 @@ test.ranges().forEach(r => { }); goTo.marker(); +verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); }); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesLoopBreakContinue2.ts b/tests/cases/fourslash/getOccurrencesLoopBreakContinue2.ts index cae9845e06f..51d4d89b657 100644 --- a/tests/cases/fourslash/getOccurrencesLoopBreakContinue2.ts +++ b/tests/cases/fourslash/getOccurrencesLoopBreakContinue2.ts @@ -65,6 +65,7 @@ test.ranges().forEach(r => { goTo.position(r.start); + verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); @@ -72,6 +73,7 @@ test.ranges().forEach(r => { }); goTo.marker(); +verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); }); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesLoopBreakContinue3.ts b/tests/cases/fourslash/getOccurrencesLoopBreakContinue3.ts index 571114ea153..8777c912afd 100644 --- a/tests/cases/fourslash/getOccurrencesLoopBreakContinue3.ts +++ b/tests/cases/fourslash/getOccurrencesLoopBreakContinue3.ts @@ -65,6 +65,7 @@ test.ranges().forEach(r => { goTo.position(r.start); + verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); diff --git a/tests/cases/fourslash/getOccurrencesLoopBreakContinue4.ts b/tests/cases/fourslash/getOccurrencesLoopBreakContinue4.ts index 587fb1a0938..1bca62ba013 100644 --- a/tests/cases/fourslash/getOccurrencesLoopBreakContinue4.ts +++ b/tests/cases/fourslash/getOccurrencesLoopBreakContinue4.ts @@ -65,6 +65,7 @@ test.ranges().forEach(r => { goTo.position(r.start); + verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); @@ -72,6 +73,7 @@ test.ranges().forEach(r => { }); goTo.marker(); +verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); }); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesLoopBreakContinue5.ts b/tests/cases/fourslash/getOccurrencesLoopBreakContinue5.ts index d558e6f3854..f4e62c554e4 100644 --- a/tests/cases/fourslash/getOccurrencesLoopBreakContinue5.ts +++ b/tests/cases/fourslash/getOccurrencesLoopBreakContinue5.ts @@ -65,6 +65,7 @@ test.ranges().forEach(r => { goTo.position(r.start); + verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); @@ -72,6 +73,7 @@ test.ranges().forEach(r => { }); goTo.marker(); +verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); }); \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault.ts b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault.ts index a3d51e642d1..f32ff0e8f82 100644 --- a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault.ts +++ b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault.ts @@ -1,10 +1,10 @@ /// -////[|sw/*1*/itch|] (10) { -//// [|/*2*/case|] 1: -//// [|cas/*3*/e|] 2: -//// [|c/*4*/ase|] 4: -//// [|c/*5*/ase|] 8: +////[|switch|] (10) { +//// [|case|] 1: +//// [|case|] 2: +//// [|case|] 4: +//// [|case|] 8: //// foo: switch (20) { //// case 1: //// case 2: @@ -12,18 +12,18 @@ //// default: //// break foo; //// } -//// [|cas/*6*/e|] 0xBEEF: -//// [|defa/*7*/ult|]: -//// [|bre/*9*/ak|]; -//// [|/*8*/case|] 16: +//// [|case|] 0xBEEF: +//// [|default|]: +//// [|break|]; +//// [|case|] 16: ////} -for (var i = 1; i <= test.markers().length; i++) { - goTo.marker("" + i); - verify.occurrencesAtPositionCount(9); +test.ranges().forEach(r => { + goTo.position(r.start); + verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); }); -} +}); diff --git a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault2.ts b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault2.ts index dc0db1f4b35..dd4577faa1f 100644 --- a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault2.ts +++ b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault2.ts @@ -5,11 +5,11 @@ //// case 2: //// case 4: //// case 8: -//// foo: [|swi/*1*/tch|] (20) { -//// [|/*2*/case|] 1: -//// [|cas/*3*/e|] 2: -//// [|b/*4*/reak|]; -//// [|defaul/*5*/t|]: +//// foo: [|switch|] (20) { +//// [|case|] 1: +//// [|case|] 2: +//// [|break|]; +//// [|default|]: //// [|break|] foo; //// } //// case 0xBEEF: @@ -21,18 +21,9 @@ test.ranges().forEach(r => { goTo.position(r.start); + verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); }); }); - - -for (var i = 1; i <= test.markers().length; i++) { - goTo.marker("" + i); - verify.occurrencesAtPositionCount(6); - - test.ranges().forEach(range => { - verify.occurrencesAtPositionContains(range, false); - }); -} diff --git a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault3.ts b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault3.ts index 959ce9d9939..24330ca1912 100644 --- a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault3.ts +++ b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault3.ts @@ -18,6 +18,7 @@ test.ranges().forEach(r => { goTo.position(r.start); + verify.occurrencesAtPositionCount(test.ranges().length); test.ranges().forEach(range => { verify.occurrencesAtPositionContains(range, false); diff --git a/tests/cases/fourslash/getOccurrencesSwitchCaseDefault4.ts b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault4.ts new file mode 100644 index 00000000000..039009746dd --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesSwitchCaseDefault4.ts @@ -0,0 +1,25 @@ +/// + +////foo: [|switch|] (10) { +//// [|case|] 1: +//// [|case|] 2: +//// [|case|] 3: +//// [|break|]; +//// [|break|] foo; +//// co/*1*/ntinue; +//// contin/*2*/ue foo; +////} + +test.ranges().forEach(r => { + goTo.position(r.start); + verify.occurrencesAtPositionCount(test.ranges().length); + + test.ranges().forEach(range => { + verify.occurrencesAtPositionContains(range, false); + }); +}); + +test.markers().forEach(m => { + goTo.position(m.position); + verify.occurrencesAtPositionCount(0); +}); \ No newline at end of file From 3d92adec7b252e4a7aa5f6df4a6c3969f44b3674 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Sep 2014 12:58:30 -0700 Subject: [PATCH 32/46] Fix widening in object literal property assignments --- src/compiler/checker.ts | 11 +++++++---- tests/baselines/reference/arrayBestCommonTypes.types | 2 +- .../reference/declFileRegressionTests.types | 2 +- tests/baselines/reference/declInput3.types | 4 ++-- .../decrementOperatorWithAnyOtherType.types | 2 +- ...doNotWidenAtObjectLiteralPropertyAssignment.types | 2 +- .../reference/forStatementsMultipleValidDecl.types | 4 ++-- .../reference/functionImplementations.types | 4 ++-- .../incrementOperatorWithAnyOtherType.types | 2 +- .../reference/interfaceWithPropertyOfEveryType.types | 2 +- tests/baselines/reference/null.types | 2 +- tests/baselines/reference/objectLiteralWidened.types | 12 ++++++------ .../overloadResolutionOverNonCTObjectLit.types | 2 +- .../propertyNameWithoutTypeAnnotation.types | 2 +- tests/baselines/reference/typeArgInference2.types | 2 +- .../reference/typeArgInferenceWithNull.types | 4 ++-- .../reference/undefinedArgumentInference.types | 4 ++-- .../validMultipleVariableDeclarations.types | 4 ++-- tests/baselines/reference/widenedTypes1.types | 6 +++--- 19 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c96b2edc5b1..f794f33e046 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1354,10 +1354,13 @@ module ts { } // Use the type of the initializer expression if one is present if (declaration.initializer) { - var unwidenedType = checkAndMarkExpression(declaration.initializer); - var type = getWidenedType(unwidenedType); - if (type !== unwidenedType) { - checkImplicitAny(type); + var type = checkAndMarkExpression(declaration.initializer); + if (declaration.kind !== SyntaxKind.PropertyAssignment) { + var unwidenedType = type; + type = getWidenedType(type); + if (type !== unwidenedType) { + checkImplicitAny(type); + } } return type; } diff --git a/tests/baselines/reference/arrayBestCommonTypes.types b/tests/baselines/reference/arrayBestCommonTypes.types index 40f74350fe5..a0f407358c3 100644 --- a/tests/baselines/reference/arrayBestCommonTypes.types +++ b/tests/baselines/reference/arrayBestCommonTypes.types @@ -222,7 +222,7 @@ class f { >base : base >[ { x: undefined, y: new base() }, { x: '', y: new derived() } ] : { x: string; y: base; }[] >{ x: undefined, y: new base() } : { x: undefined; y: base; } ->x : any +>x : undefined >undefined : undefined >y : base >new base() : base diff --git a/tests/baselines/reference/declFileRegressionTests.types b/tests/baselines/reference/declFileRegressionTests.types index f230d93f0dd..8d058769c0a 100644 --- a/tests/baselines/reference/declFileRegressionTests.types +++ b/tests/baselines/reference/declFileRegressionTests.types @@ -4,7 +4,7 @@ var n = { w: null, x: '', y: () => { }, z: 32 }; >n : { w: any; x: string; y: () => void; z: number; } >{ w: null, x: '', y: () => { }, z: 32 } : { w: null; x: string; y: () => void; z: number; } ->w : any +>w : null >x : string >y : () => void >() => { } : () => void diff --git a/tests/baselines/reference/declInput3.types b/tests/baselines/reference/declInput3.types index 4147396b26d..df69422f55b 100644 --- a/tests/baselines/reference/declInput3.types +++ b/tests/baselines/reference/declInput3.types @@ -16,9 +16,9 @@ class bar { >a : bar >null : bar >bar : bar ->b : any +>b : undefined >undefined : undefined ->c : any +>c : undefined >void 4 : undefined public h(x = 4, y = null, z = '') { x++; } diff --git a/tests/baselines/reference/decrementOperatorWithAnyOtherType.types b/tests/baselines/reference/decrementOperatorWithAnyOtherType.types index 1a4aea0d507..4e8387f9066 100644 --- a/tests/baselines/reference/decrementOperatorWithAnyOtherType.types +++ b/tests/baselines/reference/decrementOperatorWithAnyOtherType.types @@ -15,7 +15,7 @@ var obj = {x:1,y:null}; >obj : { x: number; y: any; } >{x:1,y:null} : { x: number; y: null; } >x : number ->y : any +>y : null class A { >A : A diff --git a/tests/baselines/reference/doNotWidenAtObjectLiteralPropertyAssignment.types b/tests/baselines/reference/doNotWidenAtObjectLiteralPropertyAssignment.types index 24eafce8cde..225bca8de63 100644 --- a/tests/baselines/reference/doNotWidenAtObjectLiteralPropertyAssignment.types +++ b/tests/baselines/reference/doNotWidenAtObjectLiteralPropertyAssignment.types @@ -26,5 +26,5 @@ var test: IIntervalTreeNode[] = [{ interval: { begin: 0 }, children: null }]; // >interval : { begin: number; } >{ begin: 0 } : { begin: number; } >begin : number ->children : any +>children : null diff --git a/tests/baselines/reference/forStatementsMultipleValidDecl.types b/tests/baselines/reference/forStatementsMultipleValidDecl.types index f894eef1404..2f3bfd70cea 100644 --- a/tests/baselines/reference/forStatementsMultipleValidDecl.types +++ b/tests/baselines/reference/forStatementsMultipleValidDecl.types @@ -39,7 +39,7 @@ for (var p: Point = { x: 0, y: undefined }; ;) { } >Point : Point >{ x: 0, y: undefined } : { x: number; y: undefined; } >x : number ->y : any +>y : undefined >undefined : undefined for (var p = { x: 1, y: undefined }; ;) { } @@ -65,7 +65,7 @@ for (var p = <{ x: number; y: number; }>{ x: 0, y: undefined }; ;) { } >y : number >{ x: 0, y: undefined } : { x: number; y: undefined; } >x : number ->y : any +>y : undefined >undefined : undefined for (var p: typeof p; ;) { } diff --git a/tests/baselines/reference/functionImplementations.types b/tests/baselines/reference/functionImplementations.types index fe03fa58d98..29fe7c0e39c 100644 --- a/tests/baselines/reference/functionImplementations.types +++ b/tests/baselines/reference/functionImplementations.types @@ -253,8 +253,8 @@ function opt2(n = { x: null, y: undefined }) { >opt2 : (n?: { x: any; y: any; }) => void >n : { x: any; y: any; } >{ x: null, y: undefined } : { x: null; y: undefined; } ->x : any ->y : any +>x : null +>y : undefined >undefined : undefined var m = n; diff --git a/tests/baselines/reference/incrementOperatorWithAnyOtherType.types b/tests/baselines/reference/incrementOperatorWithAnyOtherType.types index 3d43764943a..165c166941c 100644 --- a/tests/baselines/reference/incrementOperatorWithAnyOtherType.types +++ b/tests/baselines/reference/incrementOperatorWithAnyOtherType.types @@ -15,7 +15,7 @@ var obj = {x:1,y:null}; >obj : { x: number; y: any; } >{x:1,y:null} : { x: number; y: null; } >x : number ->y : any +>y : null class A { >A : A diff --git a/tests/baselines/reference/interfaceWithPropertyOfEveryType.types b/tests/baselines/reference/interfaceWithPropertyOfEveryType.types index 69ad29374a2..21d4359b6fb 100644 --- a/tests/baselines/reference/interfaceWithPropertyOfEveryType.types +++ b/tests/baselines/reference/interfaceWithPropertyOfEveryType.types @@ -95,7 +95,7 @@ var a: Foo = { >{} : {} e: null , ->e : any +>e : null f: [1], >f : number[] diff --git a/tests/baselines/reference/null.types b/tests/baselines/reference/null.types index 0614b5efec4..7a5232efb0b 100644 --- a/tests/baselines/reference/null.types +++ b/tests/baselines/reference/null.types @@ -41,7 +41,7 @@ var w:I={x:null,y:3}; >w : I >I : I >{x:null,y:3} : { x: null; y: number; } ->x : any +>x : null >y : number diff --git a/tests/baselines/reference/objectLiteralWidened.types b/tests/baselines/reference/objectLiteralWidened.types index e8dc707a489..3f9163e77ef 100644 --- a/tests/baselines/reference/objectLiteralWidened.types +++ b/tests/baselines/reference/objectLiteralWidened.types @@ -6,10 +6,10 @@ var x = { >{ foo: null, bar: undefined} : { foo: null; bar: undefined; } foo: null, ->foo : any +>foo : null bar: undefined ->bar : any +>bar : undefined >undefined : undefined } @@ -18,17 +18,17 @@ var y = { >{ foo: null, bar: { baz: null, boo: undefined }} : { foo: null; bar: { baz: null; boo: undefined; }; } foo: null, ->foo : any +>foo : null bar: { ->bar : { baz: any; boo: any; } +>bar : { baz: null; boo: undefined; } >{ baz: null, boo: undefined } : { baz: null; boo: undefined; } baz: null, ->baz : any +>baz : null boo: undefined ->boo : any +>boo : undefined >undefined : undefined } } diff --git a/tests/baselines/reference/overloadResolutionOverNonCTObjectLit.types b/tests/baselines/reference/overloadResolutionOverNonCTObjectLit.types index 6bc491fd157..846190010b4 100644 --- a/tests/baselines/reference/overloadResolutionOverNonCTObjectLit.types +++ b/tests/baselines/reference/overloadResolutionOverNonCTObjectLit.types @@ -61,7 +61,7 @@ module Bugs { >startIndex : number >type : string >bracket : number ->state : any +>state : null >length : number } } diff --git a/tests/baselines/reference/propertyNameWithoutTypeAnnotation.types b/tests/baselines/reference/propertyNameWithoutTypeAnnotation.types index bdc8452863d..520567d33a1 100644 --- a/tests/baselines/reference/propertyNameWithoutTypeAnnotation.types +++ b/tests/baselines/reference/propertyNameWithoutTypeAnnotation.types @@ -25,7 +25,7 @@ var b = { >{ foo: null} : { foo: null; } foo: null ->foo : any +>foo : null } // These should all be of type 'any' diff --git a/tests/baselines/reference/typeArgInference2.types b/tests/baselines/reference/typeArgInference2.types index b6568afee24..1b94a0638bd 100644 --- a/tests/baselines/reference/typeArgInference2.types +++ b/tests/baselines/reference/typeArgInference2.types @@ -31,7 +31,7 @@ var z3 = foo({ name: null }); // { name: any } >foo({ name: null }) : { name: any; } >foo : (x?: T, y?: T) => T >{ name: null } : { name: null; } ->name : any +>name : null var z4 = foo({ name: "abc" }); // { name: string } >z4 : { name: string; } diff --git a/tests/baselines/reference/typeArgInferenceWithNull.types b/tests/baselines/reference/typeArgInferenceWithNull.types index 9fd3ea33e24..f480e7c169c 100644 --- a/tests/baselines/reference/typeArgInferenceWithNull.types +++ b/tests/baselines/reference/typeArgInferenceWithNull.types @@ -22,7 +22,7 @@ fn5({ x: null }); >fn5({ x: null }) : void >fn5 : (n: T) => void >{ x: null } : { x: null; } ->x : any +>x : null function fn6(n: T, fun: (x: T) => void, n2: T) { } >fn6 : (n: T, fun: (x: T) => void, n2: T) => void @@ -40,7 +40,7 @@ fn6({ x: null }, y => { }, { x: "" }); // y has type { x: any }, but ideally wou >fn6({ x: null }, y => { }, { x: "" }) : void >fn6 : (n: T, fun: (x: T) => void, n2: T) => void >{ x: null } : { x: null; } ->x : any +>x : null >y => { } : (y: { x: string; }) => void >y : { x: string; } >{ x: "" } : { x: string; } diff --git a/tests/baselines/reference/undefinedArgumentInference.types b/tests/baselines/reference/undefinedArgumentInference.types index e6c50461aeb..998a717b70a 100644 --- a/tests/baselines/reference/undefinedArgumentInference.types +++ b/tests/baselines/reference/undefinedArgumentInference.types @@ -19,8 +19,8 @@ var z1 = foo1({ x: undefined, y: undefined }); >foo1({ x: undefined, y: undefined }) : any >foo1 : (f1: { x: T; y: T; }) => T >{ x: undefined, y: undefined } : { x: undefined; y: undefined; } ->x : any +>x : undefined >undefined : undefined ->y : any +>y : undefined >undefined : undefined diff --git a/tests/baselines/reference/validMultipleVariableDeclarations.types b/tests/baselines/reference/validMultipleVariableDeclarations.types index 0e25906aac4..89d3702a702 100644 --- a/tests/baselines/reference/validMultipleVariableDeclarations.types +++ b/tests/baselines/reference/validMultipleVariableDeclarations.types @@ -47,7 +47,7 @@ var p: Point = { x: 0, y: undefined }; >Point : Point >{ x: 0, y: undefined } : { x: number; y: undefined; } >x : number ->y : any +>y : undefined >undefined : undefined var p = { x: 1, y: undefined }; @@ -73,7 +73,7 @@ var p = <{ x: number; y: number; }>{ x: 0, y: undefined }; >y : number >{ x: 0, y: undefined } : { x: number; y: undefined; } >x : number ->y : any +>y : undefined >undefined : undefined var p: typeof p; diff --git a/tests/baselines/reference/widenedTypes1.types b/tests/baselines/reference/widenedTypes1.types index 75cc5e3ede4..7d334054d18 100644 --- a/tests/baselines/reference/widenedTypes1.types +++ b/tests/baselines/reference/widenedTypes1.types @@ -9,13 +9,13 @@ var b = undefined; var c = {x: null}; >c : { x: any; } >{x: null} : { x: null; } ->x : any +>x : null var d = [{x: null}]; >d : { x: any; }[] >[{x: null}] : { x: null; }[] >{x: null} : { x: null; } ->x : any +>x : null var f = [null, null]; >f : any[] @@ -31,6 +31,6 @@ var h = [{x: undefined}]; >h : { x: any; }[] >[{x: undefined}] : { x: undefined; }[] >{x: undefined} : { x: undefined; } ->x : any +>x : undefined >undefined : undefined From b9ae6cec17f03761cd9cff477cdb66e67185533c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Sep 2014 13:14:30 -0700 Subject: [PATCH 33/46] Adding a comment --- src/compiler/checker.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f794f33e046..5dfe4a8e7a4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1355,6 +1355,7 @@ module ts { // Use the type of the initializer expression if one is present if (declaration.initializer) { var type = checkAndMarkExpression(declaration.initializer); + // Widening of property assignments is handled by checkObjectLiteral, exclude them here if (declaration.kind !== SyntaxKind.PropertyAssignment) { var unwidenedType = type; type = getWidenedType(type); From dae34875b44895157c8a86b5d075a5f1bd08856f Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 10 Sep 2014 17:38:02 -0700 Subject: [PATCH 34/46] Minor CR feedback addressed. --- src/compiler/checker.ts | 6 +++--- src/compiler/emitter.ts | 10 +++++----- src/compiler/parser.ts | 12 ++++++------ src/compiler/types.ts | 4 ++-- src/harness/typeWriter.ts | 4 ++-- src/services/services.ts | 29 +++++++++++++++-------------- 6 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 80778789e7b..04ab6d4bc6b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5815,7 +5815,7 @@ module ts { }); } - function checkLabelledStatement(node: LabelledStatement) { + function checkLabelledStatement(node: LabeledStatement) { checkSourceElement(node.statement); } @@ -6378,8 +6378,8 @@ module ts { return checkWithStatement(node); case SyntaxKind.SwitchStatement: return checkSwitchStatement(node); - case SyntaxKind.LabelledStatement: - return checkLabelledStatement(node); + case SyntaxKind.LabeledStatement: + return checkLabelledStatement(node); case SyntaxKind.ThrowStatement: return checkThrowStatement(node); case SyntaxKind.TryStatement: diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 7634e6a778c..7494b8f2b06 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -781,8 +781,8 @@ module ts { case SyntaxKind.ContinueStatement: case SyntaxKind.ExportAssignment: return false; - case SyntaxKind.LabelledStatement: - return (node.parent).label === node; + case SyntaxKind.LabeledStatement: + return (node.parent).label === node; case SyntaxKind.CatchBlock: return (node.parent).variable === node; } @@ -1200,7 +1200,7 @@ module ts { write(";"); } - function emitLabelledStatement(node: LabelledStatement) { + function emitLabelledStatement(node: LabeledStatement) { emit(node.label); write(": "); emit(node.statement); @@ -2080,8 +2080,8 @@ module ts { case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: return emitCaseOrDefaultClause(node); - case SyntaxKind.LabelledStatement: - return emitLabelledStatement(node); + case SyntaxKind.LabeledStatement: + return emitLabelledStatement(node); case SyntaxKind.ThrowStatement: return emitThrowStatement(node); case SyntaxKind.TryStatement: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index a89152e043c..d50da53b22b 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -305,9 +305,9 @@ module ts { case SyntaxKind.DefaultClause: return child((node).expression) || children((node).statements); - case SyntaxKind.LabelledStatement: - return child((node).label) || - child((node).statement); + case SyntaxKind.LabeledStatement: + return child((node).label) || + child((node).statement); case SyntaxKind.ThrowStatement: return child((node).expression); case SyntaxKind.TryStatement: @@ -371,7 +371,7 @@ module ts { case SyntaxKind.SwitchStatement: case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: - case SyntaxKind.LabelledStatement: + case SyntaxKind.LabeledStatement: case SyntaxKind.TryStatement: case SyntaxKind.TryBlock: case SyntaxKind.CatchBlock: @@ -2799,8 +2799,8 @@ module ts { return isIdentifier() && lookAhead(() => nextToken() === SyntaxKind.ColonToken); } - function parseLabelledStatement(): LabelledStatement { - var node = createNode(SyntaxKind.LabelledStatement); + function parseLabelledStatement(): LabeledStatement { + var node = createNode(SyntaxKind.LabeledStatement); node.label = parseIdentifier(); parseExpected(SyntaxKind.ColonToken); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index fbd5d8b8662..aa4b149a41a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -183,7 +183,7 @@ module ts { SwitchStatement, CaseClause, DefaultClause, - LabelledStatement, + LabeledStatement, ThrowStatement, TryStatement, TryBlock, @@ -459,7 +459,7 @@ module ts { statements: NodeArray; } - export interface LabelledStatement extends Statement { + export interface LabeledStatement extends Statement { label: Identifier; statement: Statement; } diff --git a/src/harness/typeWriter.ts b/src/harness/typeWriter.ts index a3b23d10f14..0f88d9f0b1e 100644 --- a/src/harness/typeWriter.ts +++ b/src/harness/typeWriter.ts @@ -67,8 +67,8 @@ class TypeWriterWalker { case ts.SyntaxKind.ContinueStatement: case ts.SyntaxKind.BreakStatement: return (parent).label === identifier; - case ts.SyntaxKind.LabelledStatement: - return (parent).label === identifier; + case ts.SyntaxKind.LabeledStatement: + return (parent).label === identifier; } return false; } diff --git a/src/services/services.ts b/src/services/services.ts index 00be1a75237..92da2d095b9 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1276,8 +1276,8 @@ module ts { /// Helpers function getTargetLabel(referenceNode: Node, labelName: string): Identifier { while (referenceNode) { - if (referenceNode.kind === SyntaxKind.LabelledStatement && (referenceNode).label.text === labelName) { - return (referenceNode).label; + if (referenceNode.kind === SyntaxKind.LabeledStatement && (referenceNode).label.text === labelName) { + return (referenceNode).label; } referenceNode = referenceNode.parent; } @@ -1292,17 +1292,17 @@ module ts { function isLabelOfLabeledStatement(node: Node): boolean { return node.kind === SyntaxKind.Identifier && - node.parent.kind === SyntaxKind.LabelledStatement && - (node.parent).label === node; + node.parent.kind === SyntaxKind.LabeledStatement && + (node.parent).label === node; } /** * Whether or not a 'node' is preceded by a label of the given string. * Note: 'node' cannot be a SourceFile. */ - function isLabelledBy(node: Node, labelName: string) { - for (var owner = node.parent; owner.kind === SyntaxKind.LabelledStatement; owner = owner.parent) { - if ((owner).label.text === labelName) { + function isLabeledBy(node: Node, labelName: string) { + for (var owner = node.parent; owner.kind === SyntaxKind.LabeledStatement; owner = owner.parent) { + if ((owner).label.text === labelName) { return true; } } @@ -2372,13 +2372,13 @@ module ts { case SyntaxKind.WhileStatement: // The iteration statement is the owner if the break/continue statement is either unlabeled, // or if the break/continue statement's label corresponds to one of the loop's labels. - if (!breakOrContinueStatement.label || isLabelledBy(owner, breakOrContinueStatement.label.text)) { + if (!breakOrContinueStatement.label || isLabeledBy(owner, breakOrContinueStatement.label.text)) { return getLoopBreakContinueOccurrences(owner) } break; case SyntaxKind.SwitchStatement: // A switch statement can only be the owner of an break statement. - if (breakOrContinueStatement.kind === SyntaxKind.BreakStatement && (!breakOrContinueStatement.label || isLabelledBy(owner, breakOrContinueStatement.label.text))) { + if (breakOrContinueStatement.kind === SyntaxKind.BreakStatement && (!breakOrContinueStatement.label || isLabeledBy(owner, breakOrContinueStatement.label.text))) { return getSwitchCaseDefaultOccurrences(owner); } break; @@ -2398,7 +2398,10 @@ module ts { breakSearchType: BreakContinueSearchType, continueSearchType: BreakContinueSearchType, keywordAccumulator: Node[]): void { - (function aggregate(node: Node) { + + return aggregate(startPoint); + + function aggregate(node: Node): void { // Remember the statuses of the flags before diving into the next node. var prevBreakSearchType = breakSearchType; var prevContinueSearchType = continueSearchType; @@ -2432,9 +2435,7 @@ module ts { // Restore the last state. breakSearchType = prevBreakSearchType; continueSearchType = prevContinueSearchType; - })(startPoint); - - return; + }; } // Note: 'statement' must be a descendant of 'root'. @@ -2449,7 +2450,7 @@ module ts { continueSearchType; if (statement.label && (searchType & BreakContinueSearchType.Labeled)) { - return isLabelledBy(owner, statement.label.text); + return isLabeledBy(owner, statement.label.text); } else { return !!(searchType & BreakContinueSearchType.Unlabeled); From efed1f9acd37a18da96ed7ec6155c5011720d71d Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 10 Sep 2014 19:02:50 -0700 Subject: [PATCH 35/46] Simplified ownership code for continue/break statements. --- src/services/services.ts | 152 +++++++++++++++++---------------------- 1 file changed, 65 insertions(+), 87 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 92da2d095b9..bd2a67a38a4 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2331,11 +2331,13 @@ module ts { } } - aggregateBreakAndContinueKeywords(/* owner */ loopNode, - /* startPoint */ loopNode.statement, - /* breakSearchType */ BreakContinueSearchType.All, - /* continueSearchType */ BreakContinueSearchType.All, - /* keywordAccumulator */ keywords); + var breaksAndContinues = aggregateAllBreakAndContinueStatements(loopNode.statement); + + forEach(breaksAndContinues, statement => { + if (ownsBreakOrContinueStatement(loopNode, statement)) { + pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); + } + }); return map(keywords, getReferenceEntryFromNode); } @@ -2352,38 +2354,78 @@ module ts { forEach(switchStatement.clauses, clause => { pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword); - // For each clause, aggregate each of the analogous 'break' statements. - aggregateBreakAndContinueKeywords(/* owner */ switchStatement, - /* startPoint */ clause, - /* breakSearchType */ BreakContinueSearchType.All, - /* continueSearchType */ BreakContinueSearchType.None, - /* keywordAccumulator */ keywords); + var breaksAndContinues = aggregateAllBreakAndContinueStatements(clause); + + forEach(breaksAndContinues, statement => { + if (ownsBreakOrContinueStatement(switchStatement, statement)) { + pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword); + } + }); }); return map(keywords, getReferenceEntryFromNode); } - function getBreakOrContinueStatementOccurences(breakOrContinueStatement: BreakOrContinueStatement): ReferenceEntry[] { - for (var owner = node.parent; owner; owner = owner.parent) { + function getBreakOrContinueStatementOccurences(breakOrContinueStatement: BreakOrContinueStatement): ReferenceEntry[]{ + var owner = getBreakOrContinueOwner(breakOrContinueStatement); + + if (owner) { switch (owner.kind) { case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: - // The iteration statement is the owner if the break/continue statement is either unlabeled, - // or if the break/continue statement's label corresponds to one of the loop's labels. - if (!breakOrContinueStatement.label || isLabeledBy(owner, breakOrContinueStatement.label.text)) { - return getLoopBreakContinueOccurrences(owner) - } - break; + return getLoopBreakContinueOccurrences(owner) case SyntaxKind.SwitchStatement: - // A switch statement can only be the owner of an break statement. - if (breakOrContinueStatement.kind === SyntaxKind.BreakStatement && (!breakOrContinueStatement.label || isLabeledBy(owner, breakOrContinueStatement.label.text))) { - return getSwitchCaseDefaultOccurrences(owner); + return getSwitchCaseDefaultOccurrences(owner); + + } + } + + return undefined; + } + + function aggregateAllBreakAndContinueStatements(node: Node): BreakOrContinueStatement[] { + var statementAccumulator: BreakOrContinueStatement[] = [] + aggregate(node); + return statementAccumulator; + + function aggregate(node: Node): void { + if (node.kind === SyntaxKind.BreakStatement || node.kind === SyntaxKind.ContinueStatement) { + statementAccumulator.push(node); + } + // Do not cross function boundaries. + else if (!isAnyFunction(node)) { + forEachChild(node, aggregate); + } + }; + } + + function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean { + var actualOwner = getBreakOrContinueOwner(statement); + + return actualOwner && actualOwner === owner; + } + + function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node { + for (var node = statement.parent; node; node = node.parent) { + switch (node.kind) { + case SyntaxKind.SwitchStatement: + if (statement.kind === SyntaxKind.ContinueStatement) { + continue; + } + // Fall through. + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.DoStatement: + if (!statement.label || isLabeledBy(node, statement.label.text)) { + return node; } break; default: - if (isAnyFunction(owner)) { + // Don't cross function boundaries. + if (isAnyFunction(node)) { return undefined; } break; @@ -2393,70 +2435,6 @@ module ts { return undefined; } - function aggregateBreakAndContinueKeywords(owner: Node, - startPoint: Node, - breakSearchType: BreakContinueSearchType, - continueSearchType: BreakContinueSearchType, - keywordAccumulator: Node[]): void { - - return aggregate(startPoint); - - function aggregate(node: Node): void { - // Remember the statuses of the flags before diving into the next node. - var prevBreakSearchType = breakSearchType; - var prevContinueSearchType = continueSearchType; - - switch (node.kind) { - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - if (ownsBreakOrContinue(owner, node, breakSearchType, continueSearchType)) { - pushKeywordIf(keywordAccumulator, node.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); - } - break; - - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - // Inner loops take ownership of unlabeled 'continue' statements. - continueSearchType &= ~BreakContinueSearchType.Unlabeled; - // Fall through - case SyntaxKind.SwitchStatement: - // Inner loops & 'switch' statements take ownership of unlabeled 'break' statements. - breakSearchType &= ~BreakContinueSearchType.Unlabeled; - break; - } - - // Do not cross function boundaries. - if (!isAnyFunction(node)) { - forEachChild(node, aggregate); - } - - // Restore the last state. - breakSearchType = prevBreakSearchType; - continueSearchType = prevContinueSearchType; - }; - } - - // Note: 'statement' must be a descendant of 'root'. - // Reasonable logic for restricting traversal prior to arriving at the - // 'statement' node is beyond the scope of this function. - function ownsBreakOrContinue(owner: Node, - statement: BreakOrContinueStatement, - breakSearchType: BreakContinueSearchType, - continueSearchType: BreakContinueSearchType): boolean { - var searchType = statement.kind === SyntaxKind.BreakStatement ? - breakSearchType : - continueSearchType; - - if (statement.label && (searchType & BreakContinueSearchType.Labeled)) { - return isLabeledBy(owner, statement.label.text); - } - else { - return !!(searchType & BreakContinueSearchType.Unlabeled); - } - } - // returns true if 'node' is defined and has a matching 'kind'. function hasKind(node: Node, kind: SyntaxKind) { return node !== undefined && node.kind === kind; From 1f2a2d2ae354b6e5ed59681537aa214f7a155fe0 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 11 Sep 2014 09:22:27 -0700 Subject: [PATCH 36/46] Simple fixes for the tests --- scripts/importDefinitelyTypedTests.ts | 79 ++++++++++++++------------- tests/webTestServer.ts | 8 ++- 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/scripts/importDefinitelyTypedTests.ts b/scripts/importDefinitelyTypedTests.ts index caa6be0063a..f33ba1dd525 100644 --- a/scripts/importDefinitelyTypedTests.ts +++ b/scripts/importDefinitelyTypedTests.ts @@ -33,44 +33,44 @@ function importDefinitelyTypedTest(testCaseName: string, testFiles: string[], re fs.mkdirSync(testDirectoryPath); child_process.exec(cmd, { - maxBuffer: 1 * 1024 * 1024, - cwd: testDirectoryPath - }, (error, stdout, stderr) => { - //console.log("importing " + testCaseName + " ..."); - //console.log(cmd); - - if (error) { - console.log("importing " + testCaseName + " ..."); - console.log(cmd); - console.log("==> error " + JSON.stringify(error)); - console.log("==> stdout " + String(stdout)); - console.log("==> stderr " + String(stderr)); - console.log("\r\n"); - return; - } - - // copy generated file to output location - var outputFilePath = path.join(testDirectoryPath, "iocapture0.json"); - var testCasePath = path.join(rwcTestPath, "DefinitelyTyped_" + testCaseName + ".json"); - copyFileSync(outputFilePath, testCasePath); - - //console.log("output generated at: " + outputFilePath); - - if (!fs.existsSync(testCasePath)) { - throw new Error("could not find test case at: " + testCasePath); - } - else { - fs.unlinkSync(outputFilePath); - fs.rmdirSync(testDirectoryPath); - //console.log("testcase generated at: " + testCasePath); - //console.log("Done."); - } - //console.log("\r\n"); - - }) - .on('error', function (error) { - console.log("==> error " + JSON.stringify(error)); - console.log("\r\n"); + maxBuffer: 1 * 1024 * 1024, + cwd: testDirectoryPath + }, (error, stdout, stderr) => { + console.log("importing " + testCaseName + " ..."); + console.log(cmd); + + if (error) { + console.log("importing " + testCaseName + " ..."); + console.log(cmd); + console.log("==> error " + JSON.stringify(error)); + console.log("==> stdout " + String(stdout)); + console.log("==> stderr " + String(stderr)); + console.log("\r\n"); + return; + } + + // copy generated file to output location + var outputFilePath = path.join(testDirectoryPath, "iocapture0.json"); + var testCasePath = path.join(rwcTestPath, "DefinitelyTyped_" + testCaseName + ".json"); + copyFileSync(outputFilePath, testCasePath); + + //console.log("output generated at: " + outputFilePath); + + if (!fs.existsSync(testCasePath)) { + throw new Error("could not find test case at: " + testCasePath); + } + else { + fs.unlinkSync(outputFilePath); + fs.rmdirSync(testDirectoryPath); + //console.log("testcase generated at: " + testCasePath); + //console.log("Done."); + } + //console.log("\r\n"); + + }) + .on('error', function (error) { + console.log("==> error " + JSON.stringify(error)); + console.log("\r\n"); }); } @@ -79,7 +79,8 @@ function importDefinitelyTypedTests(definitelyTypedRoot: string): void { if (err) throw err; subDirectorys - .filter(d => ["_infrastructure", "node_modules", ".git"].indexOf(d) >= 0) + .filter(d => ["_infrastructure", "node_modules", ".git"].indexOf(d) < 0) + .filter(i => i.indexOf("sipml") >=0 ) .filter(i => fs.statSync(path.join(definitelyTypedRoot, i)).isDirectory()) .forEach(d => { var directoryPath = path.join(definitelyTypedRoot, d); diff --git a/tests/webTestServer.ts b/tests/webTestServer.ts index 05cf7a10f20..40e347fed50 100644 --- a/tests/webTestServer.ts +++ b/tests/webTestServer.ts @@ -227,11 +227,15 @@ function handleRequestOperation(req: http.ServerRequest, res: http.ServerRespons send('success', res, null); break; case RequestType.DeleteFile: - fs.unlinkSync(reqPath); + if (fs.existsSync(reqPath)) { + fs.unlinkSync(reqPath); + } send('success', res, null); break; case RequestType.DeleteDir: - fs.rmdirSync(reqPath); + if (fs.existsSync(reqPath)) { + fs.rmdirSync(reqPath); + } send('success', res, null); break; case RequestType.AppendFile: From 9093bac9deee5227ebbd41526a035f443689d2f6 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 11 Sep 2014 10:29:43 -0700 Subject: [PATCH 37/46] Update LKG --- bin/tsc.js | 87 ++++++++++++++++++++++++--------------- bin/typescriptServices.js | 83 +++++++++++++++++++++++-------------- 2 files changed, 106 insertions(+), 64 deletions(-) diff --git a/bin/tsc.js b/bin/tsc.js index 98e690a82e0..af5f939a80d 100644 --- a/bin/tsc.js +++ b/bin/tsc.js @@ -1532,6 +1532,7 @@ var ts; TypeFormatFlags[TypeFormatFlags["None"] = 0x00000000] = "None"; TypeFormatFlags[TypeFormatFlags["WriteArrayAsGenericType"] = 0x00000001] = "WriteArrayAsGenericType"; TypeFormatFlags[TypeFormatFlags["UseTypeOfFunction"] = 0x00000002] = "UseTypeOfFunction"; + TypeFormatFlags[TypeFormatFlags["NoTruncation"] = 0x00000004] = "NoTruncation"; })(ts.TypeFormatFlags || (ts.TypeFormatFlags = {})); var TypeFormatFlags = ts.TypeFormatFlags; (function (SymbolAccessibility) { @@ -1959,6 +1960,8 @@ var ts; } ts.getLocaleSpecificMessage = getLocaleSpecificMessage; function createFileDiagnostic(file, start, length, message) { + Debug.assert(start >= 0, "start must be non-negative, is " + start); + Debug.assert(length >= 0, "length must be non-negative, is " + length); var text = getLocaleSpecificMessage(message.key); if (arguments.length > 4) { text = formatStringFromArgs(text, arguments, 4); @@ -2002,6 +2005,8 @@ var ts; } ts.chainDiagnosticMessages = chainDiagnosticMessages; function flattenDiagnosticChain(file, start, length, diagnosticChain, newLine) { + Debug.assert(start >= 0, "start must be non-negative, is " + start); + Debug.assert(length >= 0, "length must be non-negative, is " + length); var code = diagnosticChain.code; var category = diagnosticChain.category; var messageText = ""; @@ -2257,6 +2262,7 @@ var ts; AssertionLevel[AssertionLevel["VeryAggressive"] = 3] = "VeryAggressive"; })(ts.AssertionLevel || (ts.AssertionLevel = {})); var AssertionLevel = ts.AssertionLevel; + var Debug; (function (Debug) { var currentAssertionLevel = 0 /* None */; function shouldAssert(level) { @@ -2277,8 +2283,7 @@ var ts; Debug.assert(false, message); } Debug.fail = fail; - })(ts.Debug || (ts.Debug = {})); - var Debug = ts.Debug; + })(Debug = ts.Debug || (ts.Debug = {})); })(ts || (ts = {})); var sys = (function () { function getWScriptSystem() { @@ -2544,7 +2549,7 @@ var ts; function createDiagnosticForNode(node, message, arg0, arg1, arg2) { node = getErrorSpanForNode(node); var file = getSourceFileOfNode(node); - var start = ts.skipTrivia(file.text, node.pos); + var start = node.kind === 111 /* Missing */ ? node.pos : ts.skipTrivia(file.text, node.pos); var length = node.end - start; return ts.createFileDiagnostic(file, start, length, message, arg0, arg1, arg2); } @@ -4735,10 +4740,11 @@ var ts; parseExpected(88 /* VarKeyword */); node.declarations = parseVariableDeclarationList(flags, false); parseSemicolon(); + finishNode(node); if (!node.declarations.length && file.syntacticErrors.length === errorCountBeforeVarStatement) { grammarErrorOnNode(node, ts.Diagnostics.Variable_declaration_list_cannot_be_empty); } - return finishNode(node); + return node; } function parseFunctionDeclaration(pos, flags) { var node = createNode(167 /* FunctionDeclaration */, pos); @@ -8476,6 +8482,7 @@ var ts; var typeCount = 0; var emptyArray = []; var emptySymbols = {}; + var compilerOptions = program.getCompilerOptions(); var checker = { getProgram: function () { return program; }, getDiagnostics: getDiagnostics, @@ -9186,7 +9193,7 @@ var ts; } return symbol.name; } - if (enclosingDeclaration && !(symbol.flags & (ts.SymbolFlags.PropertyOrAccessor | ts.SymbolFlags.Signature | 4096 /* Constructor */ | 2048 /* Method */ | 262144 /* TypeParameter */))) { + if (enclosingDeclaration && !(symbol.flags & 262144 /* TypeParameter */)) { var symbolName; while (symbol) { var isFirstName = !symbolName; @@ -9215,17 +9222,25 @@ var ts; function writeSymbolToTextWriter(symbol, enclosingDeclaration, meaning, writer) { writer.write(symbolToString(symbol, enclosingDeclaration, meaning)); } - function createSingleLineTextWriter() { + function createSingleLineTextWriter(maxLength) { var result = ""; - return { - write: function (s) { + var overflow = false; + function write(s) { + if (!overflow) { result += s; - }, + if (result.length > maxLength) { + result = result.substr(0, maxLength - 3) + "..."; + overflow = true; + } + } + } + return { + write: write, writeSymbol: function (symbol, enclosingDeclaration, meaning) { writeSymbolToTextWriter(symbol, enclosingDeclaration, meaning, this); }, writeLine: function () { - result += " "; + write(" "); }, increaseIndent: function () { }, @@ -9237,7 +9252,8 @@ var ts; }; } function typeToString(type, enclosingDeclaration, flags) { - var stringWriter = createSingleLineTextWriter(); + var maxLength = compilerOptions.noErrorTruncation || flags & 4 /* NoTruncation */ ? undefined : 100; + var stringWriter = createSingleLineTextWriter(maxLength); writeTypeToTextWriter(type, enclosingDeclaration, flags, stringWriter); return stringWriter.getText(); } @@ -9567,7 +9583,7 @@ var ts; checkImplicitAny(type); return type; function checkImplicitAny(type) { - if (!fullTypeCheck || !program.getCompilerOptions().noImplicitAny) { + if (!fullTypeCheck || !compilerOptions.noImplicitAny) { return; } if (getInnermostTypeOfNestedArrayTypes(type) !== anyType) { @@ -9651,7 +9667,7 @@ var ts; type = getReturnTypeFromBody(getter); } else { - if (program.getCompilerOptions().noImplicitAny) { + if (compilerOptions.noImplicitAny) { error(setter, ts.Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_type_annotation, symbol.name); } type = anyType; @@ -11813,7 +11829,7 @@ var ts; if (stringIndexType) { return stringIndexType; } - if (program.getCompilerOptions().noImplicitAny && objectType !== anyType) { + if (compilerOptions.noImplicitAny && objectType !== anyType) { error(node, ts.Diagnostics.Index_signature_of_object_type_implicitly_has_an_any_type); } return anyType; @@ -11884,17 +11900,6 @@ var ts; }); return getSignatureInstantiation(signature, getInferredTypes(context)); } - function inferentiallyTypeExpession(expr, contextualType, contextualMapper) { - var type = checkExpressionWithContextualType(expr, contextualType, contextualMapper); - var signature = getSingleCallSignature(type); - if (signature && signature.typeParameters) { - var contextualSignature = getSingleCallSignature(contextualType); - if (contextualSignature && !contextualSignature.typeParameters) { - type = getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, contextualMapper)); - } - } - return type; - } function inferTypeArguments(signature, args, excludeArgument) { var typeParameters = signature.typeParameters; var context = createInferenceContext(typeParameters); @@ -11902,14 +11907,14 @@ var ts; for (var i = 0; i < args.length; i++) { if (!excludeArgument || excludeArgument[i] === undefined) { var parameterType = getTypeAtPosition(signature, i); - inferTypes(context, inferentiallyTypeExpession(args[i], parameterType, mapper), parameterType); + inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType); } } if (excludeArgument) { for (var i = 0; i < args.length; i++) { if (excludeArgument[i] === false) { var parameterType = getTypeAtPosition(signature, i); - inferTypes(context, inferentiallyTypeExpession(args[i], parameterType, mapper), parameterType); + inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType); } } } @@ -12067,7 +12072,7 @@ var ts; if (node.kind === 133 /* NewExpression */) { var declaration = signature.declaration; if (declaration && (declaration.kind !== 117 /* Constructor */ && declaration.kind !== 121 /* ConstructSignature */)) { - if (program.getCompilerOptions().noImplicitAny) { + if (compilerOptions.noImplicitAny) { error(node, ts.Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); } return anyType; @@ -12106,7 +12111,7 @@ var ts; if (func.body.kind !== 168 /* FunctionBlock */) { var unwidenedType = checkAndMarkExpression(func.body, contextualMapper); var widenedType = getWidenedType(unwidenedType); - if (fullTypeCheck && program.getCompilerOptions().noImplicitAny && widenedType !== unwidenedType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) { + if (fullTypeCheck && compilerOptions.noImplicitAny && widenedType !== unwidenedType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) { error(func, ts.Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeToString(widenedType)); } return widenedType; @@ -12119,7 +12124,7 @@ var ts; return unknownType; } var widenedType = getWidenedType(commonType); - if (fullTypeCheck && program.getCompilerOptions().noImplicitAny && widenedType !== commonType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) { + if (fullTypeCheck && compilerOptions.noImplicitAny && widenedType !== commonType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) { var typeName = typeToString(widenedType); if (func.name) { error(func, ts.Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type, ts.identifierToString(func.name), typeName); @@ -12455,6 +12460,22 @@ var ts; return result; } function checkExpression(node, contextualMapper) { + var type = checkExpressionNode(node, contextualMapper); + if (contextualMapper && contextualMapper !== identityMapper) { + var signature = getSingleCallSignature(type); + if (signature && signature.typeParameters) { + var contextualType = getContextualType(node); + if (contextualType) { + var contextualSignature = getSingleCallSignature(contextualType); + if (contextualSignature && !contextualSignature.typeParameters) { + type = getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, contextualMapper)); + } + } + } + } + return type; + } + function checkExpressionNode(node, contextualMapper) { switch (node.kind) { case 55 /* Identifier */: return checkIdentifier(node); @@ -12565,7 +12586,7 @@ var ts; checkCollisionWithCapturedThisVariable(node, node.name); checkCollistionWithRequireExportsInGeneratedCode(node, node.name); checkCollisionWithArgumentsInGeneratedCode(node); - if (program.getCompilerOptions().noImplicitAny && !node.type) { + if (compilerOptions.noImplicitAny && !node.type) { switch (node.kind) { case 121 /* ConstructSignature */: error(node, ts.Diagnostics.Construct_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); @@ -12976,7 +12997,7 @@ var ts; if (node.type && !isAccessor(node.kind)) { checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type)); } - if (fullTypeCheck && program.getCompilerOptions().noImplicitAny && !node.body && !node.type) { + if (fullTypeCheck && compilerOptions.noImplicitAny && !node.body && !node.type) { if (!isPrivateWithinAmbient(node)) { var typeName = typeToString(anyType); if (node.name) { @@ -14273,7 +14294,7 @@ var ts; return target !== unknownSymbol && ((target.flags & ts.SymbolFlags.Value) !== 0); } function shouldEmitDeclarations() { - return program.getCompilerOptions().declaration && !program.getDiagnostics().length && !getDiagnostics().length; + return compilerOptions.declaration && !program.getDiagnostics().length && !getDiagnostics().length; } function isReferencedImportDeclaration(node) { var symbol = getSymbolOfNode(node); diff --git a/bin/typescriptServices.js b/bin/typescriptServices.js index 0e520a2b447..1efb6d232e3 100644 --- a/bin/typescriptServices.js +++ b/bin/typescriptServices.js @@ -1532,6 +1532,7 @@ var ts; TypeFormatFlags[TypeFormatFlags["None"] = 0x00000000] = "None"; TypeFormatFlags[TypeFormatFlags["WriteArrayAsGenericType"] = 0x00000001] = "WriteArrayAsGenericType"; TypeFormatFlags[TypeFormatFlags["UseTypeOfFunction"] = 0x00000002] = "UseTypeOfFunction"; + TypeFormatFlags[TypeFormatFlags["NoTruncation"] = 0x00000004] = "NoTruncation"; })(ts.TypeFormatFlags || (ts.TypeFormatFlags = {})); var TypeFormatFlags = ts.TypeFormatFlags; (function (SymbolAccessibility) { @@ -1959,6 +1960,8 @@ var ts; } ts.getLocaleSpecificMessage = getLocaleSpecificMessage; function createFileDiagnostic(file, start, length, message) { + Debug.assert(start >= 0, "start must be non-negative, is " + start); + Debug.assert(length >= 0, "length must be non-negative, is " + length); var text = getLocaleSpecificMessage(message.key); if (arguments.length > 4) { text = formatStringFromArgs(text, arguments, 4); @@ -2002,6 +2005,8 @@ var ts; } ts.chainDiagnosticMessages = chainDiagnosticMessages; function flattenDiagnosticChain(file, start, length, diagnosticChain, newLine) { + Debug.assert(start >= 0, "start must be non-negative, is " + start); + Debug.assert(length >= 0, "length must be non-negative, is " + length); var code = diagnosticChain.code; var category = diagnosticChain.category; var messageText = ""; @@ -2349,7 +2354,7 @@ var ts; function createDiagnosticForNode(node, message, arg0, arg1, arg2) { node = getErrorSpanForNode(node); var file = getSourceFileOfNode(node); - var start = ts.skipTrivia(file.text, node.pos); + var start = node.kind === 111 /* Missing */ ? node.pos : ts.skipTrivia(file.text, node.pos); var length = node.end - start; return ts.createFileDiagnostic(file, start, length, message, arg0, arg1, arg2); } @@ -4540,10 +4545,11 @@ var ts; parseExpected(88 /* VarKeyword */); node.declarations = parseVariableDeclarationList(flags, false); parseSemicolon(); + finishNode(node); if (!node.declarations.length && file.syntacticErrors.length === errorCountBeforeVarStatement) { grammarErrorOnNode(node, ts.Diagnostics.Variable_declaration_list_cannot_be_empty); } - return finishNode(node); + return node; } function parseFunctionDeclaration(pos, flags) { var node = createNode(167 /* FunctionDeclaration */, pos); @@ -8281,6 +8287,7 @@ var ts; var typeCount = 0; var emptyArray = []; var emptySymbols = {}; + var compilerOptions = program.getCompilerOptions(); var checker = { getProgram: function () { return program; }, getDiagnostics: getDiagnostics, @@ -8991,7 +8998,7 @@ var ts; } return symbol.name; } - if (enclosingDeclaration && !(symbol.flags & (ts.SymbolFlags.PropertyOrAccessor | ts.SymbolFlags.Signature | 4096 /* Constructor */ | 2048 /* Method */ | 262144 /* TypeParameter */))) { + if (enclosingDeclaration && !(symbol.flags & 262144 /* TypeParameter */)) { var symbolName; while (symbol) { var isFirstName = !symbolName; @@ -9020,17 +9027,25 @@ var ts; function writeSymbolToTextWriter(symbol, enclosingDeclaration, meaning, writer) { writer.write(symbolToString(symbol, enclosingDeclaration, meaning)); } - function createSingleLineTextWriter() { + function createSingleLineTextWriter(maxLength) { var result = ""; - return { - write: function (s) { + var overflow = false; + function write(s) { + if (!overflow) { result += s; - }, + if (result.length > maxLength) { + result = result.substr(0, maxLength - 3) + "..."; + overflow = true; + } + } + } + return { + write: write, writeSymbol: function (symbol, enclosingDeclaration, meaning) { writeSymbolToTextWriter(symbol, enclosingDeclaration, meaning, this); }, writeLine: function () { - result += " "; + write(" "); }, increaseIndent: function () { }, @@ -9042,7 +9057,8 @@ var ts; }; } function typeToString(type, enclosingDeclaration, flags) { - var stringWriter = createSingleLineTextWriter(); + var maxLength = compilerOptions.noErrorTruncation || flags & 4 /* NoTruncation */ ? undefined : 100; + var stringWriter = createSingleLineTextWriter(maxLength); writeTypeToTextWriter(type, enclosingDeclaration, flags, stringWriter); return stringWriter.getText(); } @@ -9372,7 +9388,7 @@ var ts; checkImplicitAny(type); return type; function checkImplicitAny(type) { - if (!fullTypeCheck || !program.getCompilerOptions().noImplicitAny) { + if (!fullTypeCheck || !compilerOptions.noImplicitAny) { return; } if (getInnermostTypeOfNestedArrayTypes(type) !== anyType) { @@ -9456,7 +9472,7 @@ var ts; type = getReturnTypeFromBody(getter); } else { - if (program.getCompilerOptions().noImplicitAny) { + if (compilerOptions.noImplicitAny) { error(setter, ts.Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_type_annotation, symbol.name); } type = anyType; @@ -11618,7 +11634,7 @@ var ts; if (stringIndexType) { return stringIndexType; } - if (program.getCompilerOptions().noImplicitAny && objectType !== anyType) { + if (compilerOptions.noImplicitAny && objectType !== anyType) { error(node, ts.Diagnostics.Index_signature_of_object_type_implicitly_has_an_any_type); } return anyType; @@ -11689,17 +11705,6 @@ var ts; }); return getSignatureInstantiation(signature, getInferredTypes(context)); } - function inferentiallyTypeExpession(expr, contextualType, contextualMapper) { - var type = checkExpressionWithContextualType(expr, contextualType, contextualMapper); - var signature = getSingleCallSignature(type); - if (signature && signature.typeParameters) { - var contextualSignature = getSingleCallSignature(contextualType); - if (contextualSignature && !contextualSignature.typeParameters) { - type = getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, contextualMapper)); - } - } - return type; - } function inferTypeArguments(signature, args, excludeArgument) { var typeParameters = signature.typeParameters; var context = createInferenceContext(typeParameters); @@ -11707,14 +11712,14 @@ var ts; for (var i = 0; i < args.length; i++) { if (!excludeArgument || excludeArgument[i] === undefined) { var parameterType = getTypeAtPosition(signature, i); - inferTypes(context, inferentiallyTypeExpession(args[i], parameterType, mapper), parameterType); + inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType); } } if (excludeArgument) { for (var i = 0; i < args.length; i++) { if (excludeArgument[i] === false) { var parameterType = getTypeAtPosition(signature, i); - inferTypes(context, inferentiallyTypeExpession(args[i], parameterType, mapper), parameterType); + inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType); } } } @@ -11872,7 +11877,7 @@ var ts; if (node.kind === 133 /* NewExpression */) { var declaration = signature.declaration; if (declaration && (declaration.kind !== 117 /* Constructor */ && declaration.kind !== 121 /* ConstructSignature */)) { - if (program.getCompilerOptions().noImplicitAny) { + if (compilerOptions.noImplicitAny) { error(node, ts.Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); } return anyType; @@ -11911,7 +11916,7 @@ var ts; if (func.body.kind !== 168 /* FunctionBlock */) { var unwidenedType = checkAndMarkExpression(func.body, contextualMapper); var widenedType = getWidenedType(unwidenedType); - if (fullTypeCheck && program.getCompilerOptions().noImplicitAny && widenedType !== unwidenedType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) { + if (fullTypeCheck && compilerOptions.noImplicitAny && widenedType !== unwidenedType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) { error(func, ts.Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeToString(widenedType)); } return widenedType; @@ -11924,7 +11929,7 @@ var ts; return unknownType; } var widenedType = getWidenedType(commonType); - if (fullTypeCheck && program.getCompilerOptions().noImplicitAny && widenedType !== commonType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) { + if (fullTypeCheck && compilerOptions.noImplicitAny && widenedType !== commonType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) { var typeName = typeToString(widenedType); if (func.name) { error(func, ts.Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type, ts.identifierToString(func.name), typeName); @@ -12260,6 +12265,22 @@ var ts; return result; } function checkExpression(node, contextualMapper) { + var type = checkExpressionNode(node, contextualMapper); + if (contextualMapper && contextualMapper !== identityMapper) { + var signature = getSingleCallSignature(type); + if (signature && signature.typeParameters) { + var contextualType = getContextualType(node); + if (contextualType) { + var contextualSignature = getSingleCallSignature(contextualType); + if (contextualSignature && !contextualSignature.typeParameters) { + type = getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, contextualMapper)); + } + } + } + } + return type; + } + function checkExpressionNode(node, contextualMapper) { switch (node.kind) { case 55 /* Identifier */: return checkIdentifier(node); @@ -12370,7 +12391,7 @@ var ts; checkCollisionWithCapturedThisVariable(node, node.name); checkCollistionWithRequireExportsInGeneratedCode(node, node.name); checkCollisionWithArgumentsInGeneratedCode(node); - if (program.getCompilerOptions().noImplicitAny && !node.type) { + if (compilerOptions.noImplicitAny && !node.type) { switch (node.kind) { case 121 /* ConstructSignature */: error(node, ts.Diagnostics.Construct_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); @@ -12781,7 +12802,7 @@ var ts; if (node.type && !isAccessor(node.kind)) { checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type)); } - if (fullTypeCheck && program.getCompilerOptions().noImplicitAny && !node.body && !node.type) { + if (fullTypeCheck && compilerOptions.noImplicitAny && !node.body && !node.type) { if (!isPrivateWithinAmbient(node)) { var typeName = typeToString(anyType); if (node.name) { @@ -14078,7 +14099,7 @@ var ts; return target !== unknownSymbol && ((target.flags & ts.SymbolFlags.Value) !== 0); } function shouldEmitDeclarations() { - return program.getCompilerOptions().declaration && !program.getDiagnostics().length && !getDiagnostics().length; + return compilerOptions.declaration && !program.getDiagnostics().length && !getDiagnostics().length; } function isReferencedImportDeclaration(node) { var symbol = getSymbolOfNode(node); From 2ba3ae92255a8ce9b66c53c6fafc3233cb5d7fce Mon Sep 17 00:00:00 2001 From: Jason Freeman Date: Thu, 11 Sep 2014 10:39:57 -0700 Subject: [PATCH 38/46] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd1c6276a2b..6b1d991cca6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "typescript", "author": "Microsoft Corp.", "homepage": "http://typescriptlang.org/", - "version": "1.0.1", + "version": "1.1.0", "licenses": [ { "type": "Apache License 2.0", From 74536cc6ed7064fa06df9ea109e3180ac7508d9d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 12 Sep 2014 07:28:49 -0700 Subject: [PATCH 39/46] Report circular type inference errors with -noImplicitAny --- src/compiler/checker.ts | 24 +++- .../diagnosticInformationMap.generated.ts | 3 + src/compiler/diagnosticMessages.json | 12 ++ src/compiler/emitter.ts | 2 +- src/compiler/parser.ts | 2 +- ...mplicitAnyFromCircularInference.errors.txt | 73 +++++++++++++ .../implicitAnyFromCircularInference.js | 103 ++++++++++++++++++ .../implicitAnyFromCircularInference.ts | 51 +++++++++ 8 files changed, 264 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/implicitAnyFromCircularInference.errors.txt create mode 100644 tests/baselines/reference/implicitAnyFromCircularInference.js create mode 100644 tests/cases/compiler/implicitAnyFromCircularInference.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c4f83f75a5f..fa1ab99f036 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -413,7 +413,7 @@ module ts { } } - function getFullyQualifiedName(symbol: Symbol) { + function getFullyQualifiedName(symbol: Symbol): string { return symbol.parent ? getFullyQualifiedName(symbol.parent) + "." + symbolToString(symbol) : symbolToString(symbol); } @@ -1420,6 +1420,9 @@ module ts { } else if (links.type === resolvingType) { links.type = anyType; + if (compilerOptions.noImplicitAny) { + error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_type_inference_encountered_a_circularity, symbolToString(symbol)); + } } return links.type; } @@ -1475,7 +1478,7 @@ module ts { // Otherwise, fall back to 'any'. else { if (compilerOptions.noImplicitAny) { - error(setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_type_annotation, symbol.name); + error(setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_type_annotation, symbolToString(symbol)); } type = anyType; @@ -1489,6 +1492,10 @@ module ts { } else if (links.type === resolvingType) { links.type = anyType; + if (compilerOptions.noImplicitAny) { + var getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); + error(getter, Diagnostics._0_implicitly_has_type_any_because_type_inference_encountered_a_circularity, symbolToString(symbol)); + } } } @@ -1552,7 +1559,7 @@ module ts { function hasBaseType(type: InterfaceType, checkBase: InterfaceType) { return check(type); - function check(type: InterfaceType) { + function check(type: InterfaceType): boolean { var target = getTargetType(type); return target === checkBase || forEach(target.baseTypes, check); } @@ -2036,6 +2043,15 @@ module ts { } else if (signature.resolvedReturnType === resolvingType) { signature.resolvedReturnType = anyType; + if (compilerOptions.noImplicitAny) { + var declaration = signature.declaration; + if (declaration.name) { + error(declaration.name, Diagnostics._0_implicitly_has_return_type_any_because_type_inference_encountered_a_circularity, identifierToString(declaration.name)); + } + else { + error(declaration, Diagnostics.Function_implicitly_has_return_type_any_because_type_inference_encountered_a_circularity); + } + } } return signature.resolvedReturnType; } @@ -6587,7 +6603,7 @@ module ts { // Language service support function getNodeAtPosition(sourceFile: SourceFile, position: number): Node { - function findChildAtPosition(parent: Node) { + function findChildAtPosition(parent: Node): Node { var child = forEachChild(parent, node => { if (position >= node.pos && position <= node.end && position >= getTokenPosOfNode(node)) { return findChildAtPosition(node); diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index aa1b793f6ac..cadf64f0140 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -390,6 +390,9 @@ module ts { Object_literal_s_property_0_implicitly_has_an_1_type: { code: 7018, category: DiagnosticCategory.Error, key: "Object literal's property '{0}' implicitly has an '{1}' type." }, Rest_parameter_0_implicitly_has_an_any_type: { code: 7019, category: DiagnosticCategory.Error, key: "Rest parameter '{0}' implicitly has an 'any[]' type." }, Call_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type: { code: 7020, category: DiagnosticCategory.Error, key: "Call signature, which lacks return-type annotation, implicitly has an 'any' return type." }, + _0_implicitly_has_type_any_because_type_inference_encountered_a_circularity: { code: 7021, category: DiagnosticCategory.Error, key: "'{0}' implicitly has type 'any' because type inference encountered a circularity." }, + _0_implicitly_has_return_type_any_because_type_inference_encountered_a_circularity: { code: 7022, category: DiagnosticCategory.Error, key: "'{0}' implicitly has return type 'any' because type inference encountered a circularity." }, + Function_implicitly_has_return_type_any_because_type_inference_encountered_a_circularity: { code: 7023, category: DiagnosticCategory.Error, key: "Function implicitly has return type 'any' because type inference encountered a circularity." }, You_cannot_rename_this_element: { code: 8000, category: DiagnosticCategory.Error, key: "You cannot rename this element." }, }; } \ No newline at end of file diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index eef8da8c857..d30c4e399fc 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1557,6 +1557,18 @@ "category": "Error", "code": 7020 }, + "'{0}' implicitly has type 'any' because type inference encountered a circularity.": { + "category": "Error", + "code": 7021 + }, + "'{0}' implicitly has return type 'any' because type inference encountered a circularity.": { + "category": "Error", + "code": 7022 + }, + "Function implicitly has return type 'any' because type inference encountered a circularity.": { + "category": "Error", + "code": 7023 + }, "You cannot rename this element.": { "category": "Error", "code": 8000 diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 6b18ad4c133..317db201f3e 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1973,7 +1973,7 @@ module ts { } } - function emitNode(node: Node) { + function emitNode(node: Node): void { if (!node) { return; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index dde11576817..be9047f3568 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3533,7 +3533,7 @@ module ts { return finishNode(node); } - function isDeclaration() { + function isDeclaration(): boolean { switch (token) { case SyntaxKind.VarKeyword: case SyntaxKind.FunctionKeyword: diff --git a/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt b/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt new file mode 100644 index 00000000000..f1d32b750f5 --- /dev/null +++ b/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt @@ -0,0 +1,73 @@ +==== tests/cases/compiler/implicitAnyFromCircularInference.ts (9 errors) ==== + + // Error expected + var a: typeof a; + ~ +!!! 'a' implicitly has type 'any' because type inference encountered a circularity. + + // Error expected on b or c + var b: typeof c; + var c: typeof b; + ~ +!!! 'c' implicitly has type 'any' because type inference encountered a circularity. + + // Error expected + var d: Array; + ~ +!!! 'd' implicitly has type 'any' because type inference encountered a circularity. + + function f() { return f; } + + // Error expected + function g() { return g(); } + ~ +!!! 'g' implicitly has return type 'any' because type inference encountered a circularity. + + // Error expected + var f1 = function () { + ~~~~~~~~~~~~~ + return f1(); + ~~~~~~~~~~~~~~~~ + }; + ~ +!!! Function implicitly has return type 'any' because type inference encountered a circularity. + + // Error expected + var f2 = () => f2(); + ~~~~~~~~~~ +!!! Function implicitly has return type 'any' because type inference encountered a circularity. + + // Error expected + function h() { + ~ +!!! 'h' implicitly has return type 'any' because type inference encountered a circularity. + return foo(); + function foo() { + return h() || "hello"; + } + } + + interface A { + s: string; + } + + function foo(x: A): string { return "abc"; } + + class C { + // Error expected + s = foo(this); + ~~~~~~~~~~~~~~ +!!! 's' implicitly has type 'any' because type inference encountered a circularity. + } + + class D { + // Error expected + get x() { + ~~~~~~~~~ + return this.x; + ~~~~~~~~~~~~~~~~~~~~~~ + } + ~~~~~ +!!! 'x' implicitly has type 'any' because type inference encountered a circularity. + } + \ No newline at end of file diff --git a/tests/baselines/reference/implicitAnyFromCircularInference.js b/tests/baselines/reference/implicitAnyFromCircularInference.js new file mode 100644 index 00000000000..d97b8420ebf --- /dev/null +++ b/tests/baselines/reference/implicitAnyFromCircularInference.js @@ -0,0 +1,103 @@ +//// [implicitAnyFromCircularInference.ts] + +// Error expected +var a: typeof a; + +// Error expected on b or c +var b: typeof c; +var c: typeof b; + +// Error expected +var d: Array; + +function f() { return f; } + +// Error expected +function g() { return g(); } + +// Error expected +var f1 = function () { + return f1(); +}; + +// Error expected +var f2 = () => f2(); + +// Error expected +function h() { + return foo(); + function foo() { + return h() || "hello"; + } +} + +interface A { + s: string; +} + +function foo(x: A): string { return "abc"; } + +class C { + // Error expected + s = foo(this); +} + +class D { + // Error expected + get x() { + return this.x; + } +} + + +//// [implicitAnyFromCircularInference.js] +// Error expected +var a; +// Error expected on b or c +var b; +var c; +// Error expected +var d; +function f() { + return f; +} +// Error expected +function g() { + return g(); +} +// Error expected +var f1 = function () { + return f1(); +}; +// Error expected +var f2 = function () { return f2(); }; +// Error expected +function h() { + return foo(); + function foo() { + return h() || "hello"; + } +} +function foo(x) { + return "abc"; +} +var C = (function () { + function C() { + // Error expected + this.s = foo(this); + } + return C; +})(); +var D = (function () { + function D() { + } + Object.defineProperty(D.prototype, "x", { + // Error expected + get: function () { + return this.x; + }, + enumerable: true, + configurable: true + }); + return D; +})(); diff --git a/tests/cases/compiler/implicitAnyFromCircularInference.ts b/tests/cases/compiler/implicitAnyFromCircularInference.ts new file mode 100644 index 00000000000..a4c8b1ba78b --- /dev/null +++ b/tests/cases/compiler/implicitAnyFromCircularInference.ts @@ -0,0 +1,51 @@ +// @noimplicitany: true +// @target: es5 + +// Error expected +var a: typeof a; + +// Error expected on b or c +var b: typeof c; +var c: typeof b; + +// Error expected +var d: Array; + +function f() { return f; } + +// Error expected +function g() { return g(); } + +// Error expected +var f1 = function () { + return f1(); +}; + +// Error expected +var f2 = () => f2(); + +// Error expected +function h() { + return foo(); + function foo() { + return h() || "hello"; + } +} + +interface A { + s: string; +} + +function foo(x: A): string { return "abc"; } + +class C { + // Error expected + s = foo(this); +} + +class D { + // Error expected + get x() { + return this.x; + } +} From b805037cf297b8ca478d3e1b92cba06d39aa39c1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 12 Sep 2014 10:23:31 -0700 Subject: [PATCH 40/46] Improved error messages --- src/compiler/checker.ts | 11 +++++++---- .../diagnosticInformationMap.generated.ts | 7 ++++--- src/compiler/diagnosticMessages.json | 10 +++++++--- ...implicitAnyFromCircularInference.errors.txt | 18 +++++++++--------- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fa1ab99f036..01256d0f638 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1421,7 +1421,10 @@ module ts { else if (links.type === resolvingType) { links.type = anyType; if (compilerOptions.noImplicitAny) { - error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_type_inference_encountered_a_circularity, symbolToString(symbol)); + var diagnostic = (symbol.valueDeclaration).type ? + Diagnostics._0_implicitly_has_type_any_because_it_is_referenced_directly_or_indirectly_in_its_own_type_annotation : + Diagnostics._0_implicitly_has_type_any_because_it_is_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer; + error(symbol.valueDeclaration, diagnostic, symbolToString(symbol)); } } return links.type; @@ -1494,7 +1497,7 @@ module ts { links.type = anyType; if (compilerOptions.noImplicitAny) { var getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); - error(getter, Diagnostics._0_implicitly_has_type_any_because_type_inference_encountered_a_circularity, symbolToString(symbol)); + error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol)); } } } @@ -2046,10 +2049,10 @@ module ts { if (compilerOptions.noImplicitAny) { var declaration = signature.declaration; if (declaration.name) { - error(declaration.name, Diagnostics._0_implicitly_has_return_type_any_because_type_inference_encountered_a_circularity, identifierToString(declaration.name)); + error(declaration.name, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, identifierToString(declaration.name)); } else { - error(declaration, Diagnostics.Function_implicitly_has_return_type_any_because_type_inference_encountered_a_circularity); + error(declaration, Diagnostics.Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions); } } } diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index cadf64f0140..40d27c93dd0 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -390,9 +390,10 @@ module ts { Object_literal_s_property_0_implicitly_has_an_1_type: { code: 7018, category: DiagnosticCategory.Error, key: "Object literal's property '{0}' implicitly has an '{1}' type." }, Rest_parameter_0_implicitly_has_an_any_type: { code: 7019, category: DiagnosticCategory.Error, key: "Rest parameter '{0}' implicitly has an 'any[]' type." }, Call_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type: { code: 7020, category: DiagnosticCategory.Error, key: "Call signature, which lacks return-type annotation, implicitly has an 'any' return type." }, - _0_implicitly_has_type_any_because_type_inference_encountered_a_circularity: { code: 7021, category: DiagnosticCategory.Error, key: "'{0}' implicitly has type 'any' because type inference encountered a circularity." }, - _0_implicitly_has_return_type_any_because_type_inference_encountered_a_circularity: { code: 7022, category: DiagnosticCategory.Error, key: "'{0}' implicitly has return type 'any' because type inference encountered a circularity." }, - Function_implicitly_has_return_type_any_because_type_inference_encountered_a_circularity: { code: 7023, category: DiagnosticCategory.Error, key: "Function implicitly has return type 'any' because type inference encountered a circularity." }, + _0_implicitly_has_type_any_because_it_is_referenced_directly_or_indirectly_in_its_own_type_annotation: { code: 7021, category: DiagnosticCategory.Error, key: "'{0}' implicitly has type 'any' because it is referenced directly or indirectly in its own type annotation." }, + _0_implicitly_has_type_any_because_it_is_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer: { code: 7022, category: DiagnosticCategory.Error, key: "'{0}' implicitly has type 'any' because it is does not have a type annotation and is referenced directly or indirectly in its own initializer." }, + _0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions: { code: 7023, category: DiagnosticCategory.Error, key: "'{0}' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions." }, + Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions: { code: 7024, category: DiagnosticCategory.Error, key: "Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions." }, You_cannot_rename_this_element: { code: 8000, category: DiagnosticCategory.Error, key: "You cannot rename this element." }, }; } \ No newline at end of file diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d30c4e399fc..3c67e8de224 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1557,18 +1557,22 @@ "category": "Error", "code": 7020 }, - "'{0}' implicitly has type 'any' because type inference encountered a circularity.": { + "'{0}' implicitly has type 'any' because it is referenced directly or indirectly in its own type annotation.": { "category": "Error", "code": 7021 }, - "'{0}' implicitly has return type 'any' because type inference encountered a circularity.": { + "'{0}' implicitly has type 'any' because it is does not have a type annotation and is referenced directly or indirectly in its own initializer.": { "category": "Error", "code": 7022 }, - "Function implicitly has return type 'any' because type inference encountered a circularity.": { + "'{0}' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.": { "category": "Error", "code": 7023 }, + "Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.": { + "category": "Error", + "code": 7024 + }, "You cannot rename this element.": { "category": "Error", "code": 8000 diff --git a/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt b/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt index f1d32b750f5..84366f86ec7 100644 --- a/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt +++ b/tests/baselines/reference/implicitAnyFromCircularInference.errors.txt @@ -3,25 +3,25 @@ // Error expected var a: typeof a; ~ -!!! 'a' implicitly has type 'any' because type inference encountered a circularity. +!!! 'a' implicitly has type 'any' because it is referenced directly or indirectly in its own type annotation. // Error expected on b or c var b: typeof c; var c: typeof b; ~ -!!! 'c' implicitly has type 'any' because type inference encountered a circularity. +!!! 'c' implicitly has type 'any' because it is referenced directly or indirectly in its own type annotation. // Error expected var d: Array; ~ -!!! 'd' implicitly has type 'any' because type inference encountered a circularity. +!!! 'd' implicitly has type 'any' because it is referenced directly or indirectly in its own type annotation. function f() { return f; } // Error expected function g() { return g(); } ~ -!!! 'g' implicitly has return type 'any' because type inference encountered a circularity. +!!! 'g' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. // Error expected var f1 = function () { @@ -30,17 +30,17 @@ ~~~~~~~~~~~~~~~~ }; ~ -!!! Function implicitly has return type 'any' because type inference encountered a circularity. +!!! Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. // Error expected var f2 = () => f2(); ~~~~~~~~~~ -!!! Function implicitly has return type 'any' because type inference encountered a circularity. +!!! Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. // Error expected function h() { ~ -!!! 'h' implicitly has return type 'any' because type inference encountered a circularity. +!!! 'h' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. return foo(); function foo() { return h() || "hello"; @@ -57,7 +57,7 @@ // Error expected s = foo(this); ~~~~~~~~~~~~~~ -!!! 's' implicitly has type 'any' because type inference encountered a circularity. +!!! 's' implicitly has type 'any' because it is does not have a type annotation and is referenced directly or indirectly in its own initializer. } class D { @@ -68,6 +68,6 @@ ~~~~~~~~~~~~~~~~~~~~~~ } ~~~~~ -!!! 'x' implicitly has type 'any' because type inference encountered a circularity. +!!! 'x' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. } \ No newline at end of file From a6497d8b09bb57f005043add0bb2732471d39673 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Sep 2014 17:19:48 -0700 Subject: [PATCH 41/46] Add support for syntactic classification. Tests pending. --- src/compiler/types.ts | 2 +- src/services/services.ts | 169 +++++++++++++++++++++++++++++++++- src/services/shims.ts | 11 +++ src/services/text/textSpan.ts | 1 + 4 files changed, 179 insertions(+), 4 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 715d01926f0..0ad4ba471f3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -221,7 +221,7 @@ module ts { FirstTypeNode = TypeReference, LastTypeNode = ArrayType, FirstPunctuation = OpenBraceToken, - LastPunctuation = CaretEqualsToken + LastPunctuation = CaretEqualsToken, } export enum NodeFlags { diff --git a/src/services/services.ts b/src/services/services.ts index d51eeee97c0..7c7b910d29e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -75,7 +75,7 @@ module ts { var scanner: Scanner = createScanner(ScriptTarget.ES5); - var emptyArray: any [] = []; + var emptyArray: any[] = []; function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject { var node = new (getNodeConstructor(kind))(); @@ -259,7 +259,7 @@ module ts { getProperty(propertyName: string): Symbol { return this.checker.getPropertyOfType(this, propertyName); } - getApparentProperties(): Symbol[]{ + getApparentProperties(): Symbol[] { return this.checker.getAugmentedPropertiesOfApparentType(this); } getCallSignatures(): Signature[] { @@ -302,7 +302,7 @@ module ts { } } - var incrementalParse: IncrementalParse = TypeScript.IncrementalParser.parse; + var incrementalParse: IncrementalParse = TypeScript.IncrementalParser.parse; class SourceFileObject extends NodeObject implements SourceFile { public filename: string; @@ -430,6 +430,8 @@ module ts { getSemanticDiagnostics(fileName: string): Diagnostic[]; getCompilerOptionsDiagnostics(): Diagnostic[]; + getSyntacticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[]; + getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean): CompletionInfo; getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails; @@ -467,6 +469,32 @@ module ts { dispose(): void; } + class ClassificationTypeNames { + public static comment = "comment"; + public static identifier = "identifier"; + public static keyword = "keyword"; + public static numericLiteral = "number"; + public static operator = "operator"; + public static stringLiteral = "string"; + public static whiteSpace = "whitespace"; + public static text = "text"; + + public static punctuation = "punctuation"; + + public static className = "class name"; + public static enumName = "enum name"; + public static interfaceName = "interface name"; + public static moduleName = "module name"; + public static typeParameterName = "type parameter name"; + } + + export class ClassifiedSpan { + constructor(public textSpan: TypeScript.TextSpan, + public classificationType: string) { + + } + } + export class NavigationBarItem { constructor(public text: string, public kind: string, @@ -3124,6 +3152,140 @@ module ts { return new TypeScript.Services.NavigationBarItemGetter().getItems(syntaxTree.sourceUnit()); } + function getSyntacticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[] { + // doesn't use compiler - no need to synchronize with host + fileName = TypeScript.switchToForwardSlashes(fileName); + var sourceFile = getCurrentSourceFile(fileName); + + var result: ClassifiedSpan[] = []; + processElement(sourceFile.getSourceUnit()); + + return result; + + function classifyTrivia(trivia: TypeScript.ISyntaxTrivia) { + if (span.intersectsWith(trivia.fullStart(), trivia.fullWidth())) { + result.push(new ClassifiedSpan( + new TypeScript.TextSpan(trivia.fullStart(), trivia.fullWidth()), + ClassificationTypeNames.comment)); + } + } + + function classifyTriviaList(trivia: TypeScript.ISyntaxTriviaList) { + for (var i = 0, n = trivia.count(); i < n; i++) { + classifyTrivia(trivia.syntaxTriviaAt(i)); + } + } + + function classifyToken(token: TypeScript.ISyntaxToken) { + if (token.hasLeadingComment()) { + classifyTriviaList(token.leadingTrivia()); + } + + if (TypeScript.width(token) > 0) { + var span = new TypeScript.TextSpan(TypeScript.start(token), TypeScript.width(token)); + var type = classifyTokenType(token); + + result.push(new ClassifiedSpan(span, type)); + } + + if (token.hasTrailingComment()) { + classifyTriviaList(token.trailingTrivia()); + } + } + + function classifyTokenType(token: TypeScript.ISyntaxToken): string { + var tokenKind = token.kind(); + if (TypeScript.SyntaxFacts.isAnyKeyword(token.kind())) { + return ClassificationTypeNames.keyword; + } + + // Special case < and > If they appear in a generic context they are punctation, + // not operators. + if (tokenKind === TypeScript.SyntaxKind.LessThanToken || tokenKind === TypeScript.SyntaxKind.GreaterThanToken) { + var tokenParentKind = token.parent.kind(); + if (tokenParentKind === TypeScript.SyntaxKind.TypeArgumentList || + tokenParentKind === TypeScript.SyntaxKind.TypeParameterList) { + + return ClassificationTypeNames.punctuation; + } + } + + if (TypeScript.SyntaxFacts.isBinaryExpressionOperatorToken(tokenKind) || + TypeScript.SyntaxFacts.isPrefixUnaryExpressionOperatorToken(tokenKind)) { + return ClassificationTypeNames.operator; + } + else if (TypeScript.SyntaxFacts.isAnyPunctuation(tokenKind)) { + return ClassificationTypeNames.punctuation; + } + else if (tokenKind === TypeScript.SyntaxKind.NumericLiteral) { + return ClassificationTypeNames.numericLiteral; + } + else if (tokenKind === TypeScript.SyntaxKind.StringLiteral) { + return ClassificationTypeNames.stringLiteral; + } + else if (tokenKind === TypeScript.SyntaxKind.RegularExpressionLiteral) { + // TODO: we shoudl get another classification type for these literals. + return ClassificationTypeNames.stringLiteral; + } + else if (tokenKind === TypeScript.SyntaxKind.IdentifierName) { + var current: TypeScript.ISyntaxNodeOrToken = token; + var parent = token.parent; + while (parent.kind() === TypeScript.SyntaxKind.QualifiedName) { + current = parent; + parent = parent.parent; + } + + switch (parent.kind()) { + case TypeScript.SyntaxKind.ClassDeclaration: + if ((parent).identifier === token) { + return ClassificationTypeNames.className; + } + return; + case TypeScript.SyntaxKind.TypeParameter: + if ((parent).identifier === token) { + return ClassificationTypeNames.typeParameterName; + } + return; + case TypeScript.SyntaxKind.InterfaceDeclaration: + if ((parent).identifier === token) { + return ClassificationTypeNames.interfaceName; + } + return; + case TypeScript.SyntaxKind.EnumDeclaration: + if ((parent).identifier === token) { + return ClassificationTypeNames.enumName; + } + return; + case TypeScript.SyntaxKind.ModuleDeclaration: + if ((parent).name === current) { + return ClassificationTypeNames.moduleName; + } + return; + default: + return ClassificationTypeNames.text; + } + } + } + + function processElement(element: TypeScript.ISyntaxElement) { + // Ignore nodes that don't intersect the original span to classify. + if (!TypeScript.isShared(element) && span.intersectsWith(TypeScript.fullStart(element), TypeScript.fullWidth(element))) { + for (var i = 0, n = TypeScript.childCount(element); i < n; i++) { + var child = TypeScript.childAt(element, i); + if (child) { + if (TypeScript.isToken(child)) { + classifyToken(child); + } + else { + // Recurse into our child nodes. + processElement(child); + } + } + } + } + } + } + function getOutliningSpans(filename: string): OutliningSpan[] { // doesn't use compiler - no need to synchronize with host filename = TypeScript.switchToForwardSlashes(filename); @@ -3371,6 +3533,7 @@ module ts { getSyntacticDiagnostics: getSyntacticDiagnostics, getSemanticDiagnostics: getSemanticDiagnostics, getCompilerOptionsDiagnostics: getCompilerOptionsDiagnostics, + getSyntacticClassifications: getSyntacticClassifications, getCompletionsAtPosition: getCompletionsAtPosition, getCompletionEntryDetails: getCompletionEntryDetails, getTypeAtPosition: getTypeAtPosition, diff --git a/src/services/shims.ts b/src/services/shims.ts index 0659f9f2512..662c97e1cc5 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -80,6 +80,8 @@ module ts { getSemanticDiagnostics(fileName: string): string; getCompilerOptionsDiagnostics(): string; + getSyntacticClassifications(fileName: string, start: number, length: number): string; + getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean): string; getCompletionEntryDetails(fileName: string, position: number, entryName: string): string; @@ -477,6 +479,15 @@ module ts { }; } + public getSyntacticClassifications(fileName: string, start: number, length: number): string { + return this.forwardJSONCall( + "getSyntacticClassifications('" + fileName + "', " + start + ", " + length + ")", + () => { + var classifications = this.languageService.getSyntacticClassifications(fileName, new TypeScript.TextSpan(start, length)); + return classifications; + }); + } + public getSyntacticDiagnostics(fileName: string): string { return this.forwardJSONCall( "getSyntacticDiagnostics('" + fileName + "')", diff --git a/src/services/text/textSpan.ts b/src/services/text/textSpan.ts index d719e244010..999070a73b4 100644 --- a/src/services/text/textSpan.ts +++ b/src/services/text/textSpan.ts @@ -1,6 +1,7 @@ /// module TypeScript { + export interface ISpan { start(): number; end(): number; From 9825342d893d0c2e2a53094401714f80caefc585 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Sep 2014 18:28:54 -0700 Subject: [PATCH 42/46] Adding semantic classification. --- src/services/services.ts | 56 +++++++++++++++++++++++++++++++++-- src/services/shims.ts | 9 ++++++ src/services/text/textSpan.ts | 1 - 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 7c7b910d29e..6385f68c7ae 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -431,6 +431,7 @@ module ts { getCompilerOptionsDiagnostics(): Diagnostic[]; getSyntacticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[]; + getSemanticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[]; getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean): CompletionInfo; getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails; @@ -3152,6 +3153,55 @@ module ts { return new TypeScript.Services.NavigationBarItemGetter().getItems(syntaxTree.sourceUnit()); } + function getSemanticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[] { + synchronizeHostData(); + fileName = TypeScript.switchToForwardSlashes(fileName); + + var sourceFile = getSourceFile(fileName); + + var result: ClassifiedSpan[] = []; + processNode(sourceFile.getSourceFile()); + + return result; + + function classifySymbol(symbol: Symbol) { + var flags = symbol.getFlags(); + + if (flags & SymbolFlags.Class) { + return ClassificationTypeNames.className; + } + else if (flags & SymbolFlags.Enum) { + return ClassificationTypeNames.enumName; + } + else if (flags & SymbolFlags.Interface) { + return ClassificationTypeNames.interfaceName; + } + else if (flags & SymbolFlags.Module) { + return ClassificationTypeNames.moduleName; + } + else if (flags & SymbolFlags.TypeParameter) { + return ClassificationTypeNames.typeParameterName; + } + } + + function processNode(node: Node) { + if (span.intersectsWith(node.getStart(), node.getWidth())) { + if (node.kind === SyntaxKind.Identifier && node.getWidth()) { + var symbol = typeInfoResolver.getSymbolInfo(node); + if (symbol) { + var span = new TypeScript.TextSpan(node.getStart(), node.getWidth()); + var type = classifySymbol(symbol); + if (type) { + result.push(new ClassifiedSpan(span, type)); + } + } + } + + forEachChild(node, processNode); + } + } + } + function getSyntacticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[] { // doesn't use compiler - no need to synchronize with host fileName = TypeScript.switchToForwardSlashes(fileName); @@ -3184,8 +3234,9 @@ module ts { if (TypeScript.width(token) > 0) { var span = new TypeScript.TextSpan(TypeScript.start(token), TypeScript.width(token)); var type = classifyTokenType(token); - - result.push(new ClassifiedSpan(span, type)); + if (type) { + result.push(new ClassifiedSpan(span, type)); + } } if (token.hasTrailingComment()) { @@ -3534,6 +3585,7 @@ module ts { getSemanticDiagnostics: getSemanticDiagnostics, getCompilerOptionsDiagnostics: getCompilerOptionsDiagnostics, getSyntacticClassifications: getSyntacticClassifications, + getSemanticClassifications: getSemanticClassifications, getCompletionsAtPosition: getCompletionsAtPosition, getCompletionEntryDetails: getCompletionEntryDetails, getTypeAtPosition: getTypeAtPosition, diff --git a/src/services/shims.ts b/src/services/shims.ts index 662c97e1cc5..c626bba0b4c 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -488,6 +488,15 @@ module ts { }); } + public getSemanticClassifications(fileName: string, start: number, length: number): string { + return this.forwardJSONCall( + "getSemanticClassifications('" + fileName + "', " + start + ", " + length + ")", + () => { + var classifications = this.languageService.getSemanticClassifications(fileName, new TypeScript.TextSpan(start, length)); + return classifications; + }); + } + public getSyntacticDiagnostics(fileName: string): string { return this.forwardJSONCall( "getSyntacticDiagnostics('" + fileName + "')", diff --git a/src/services/text/textSpan.ts b/src/services/text/textSpan.ts index 999070a73b4..d719e244010 100644 --- a/src/services/text/textSpan.ts +++ b/src/services/text/textSpan.ts @@ -1,7 +1,6 @@ /// module TypeScript { - export interface ISpan { start(): number; end(): number; From 7f12b6dd31bcc7a4c30b74d15f784d5e8c867c95 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 15 Sep 2014 15:44:42 -0700 Subject: [PATCH 43/46] Spelling corrections. --- src/services/services.ts | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index d51eeee97c0..c3c43b53fad 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -362,7 +362,7 @@ module ts { public update(scriptSnapshot: TypeScript.IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TypeScript.TextChangeRange): SourceFile { // See if we are currently holding onto a syntax tree. We may not be because we're // either a closed file, or we've just been lazy and haven't had to create the syntax - // tree yet. Access the field instead of the method so we don't accidently realize + // tree yet. Access the field instead of the method so we don't accidentally realize // the old syntax tree. var oldSyntaxTree = this.syntaxTree; @@ -1371,7 +1371,7 @@ module ts { var program: Program; // this checker is used to answer all LS questions except errors var typeInfoResolver: TypeChecker; - // the sole purpose of this checkes is to reutrn semantic diagnostics + // the sole purpose of this check is to return semantic diagnostics // creation is deferred - use getFullTypeCheckChecker to get instance var fullTypeCheckChecker_doNotAccessDirectly: TypeChecker; var useCaseSensitivefilenames = false; @@ -1515,7 +1515,7 @@ module ts { sourceFile = documentRegistry.acquireDocument(filename, compilationSettings, scriptSnapshot, version, isOpen); } - // Remeber the new sourceFile + // Remember the new sourceFile sourceFilesByName[filename] = sourceFile; } @@ -1570,7 +1570,7 @@ module ts { var firstChar = displayName.charCodeAt(0); if (firstChar === TypeScript.CharacterCodes.singleQuote || firstChar === TypeScript.CharacterCodes.doubleQuote) { // If the user entered name for the symbol was quoted, removing the quotes is not enough, as the name could be an - // invalid identifer name. We need to check if whatever was inside the quotes is actually a valid identifier name. + // invalid identifier name. We need to check if whatever was inside the quotes is actually a valid identifier name. displayName = TypeScript.stripStartAndEndQuotes(displayName); } @@ -1612,9 +1612,9 @@ module ts { } function isCompletionListBlocker(sourceUnit: TypeScript.SourceUnitSyntax, position: number): boolean { - // We shouldn't be getting a possition that is outside the file because + // We shouldn't be getting a position that is outside the file because // isEntirelyInsideComment can't handle when the position is out of bounds, - // callers should be fixed, however we should be resiliant to bad inputs + // callers should be fixed, however we should be resilient to bad inputs // so we return true (this position is a blocker for getting completions) if (position < 0 || position > TypeScript.fullWidth(sourceUnit)) { return true; @@ -1704,12 +1704,12 @@ module ts { var positionedToken = TypeScript.Syntax.findCompleteTokenOnLeft(sourceUnit, position, /*includeSkippedTokens*/true); if (positionedToken && position === TypeScript.end(positionedToken) && positionedToken.kind() == TypeScript.SyntaxKind.EndOfFileToken) { - // EndOfFile token is not intresting, get the one before it + // EndOfFile token is not interesting, get the one before it positionedToken = TypeScript. previousToken(positionedToken, /*includeSkippedTokens*/true); } if (positionedToken && position === TypeScript.end(positionedToken) && positionedToken.kind() === TypeScript.SyntaxKind.IdentifierName) { - // The caret is at the end of an identifier, the decession to provide completion depends on the previous token + // The caret is at the end of an identifier, the decision to provide completion depends on the previous token positionedToken = TypeScript.previousToken(positionedToken, /*includeSkippedTokens*/true); } @@ -1839,14 +1839,14 @@ module ts { // // get existing members // var existingMembers = compiler.getVisibleMemberSymbolsFromAST(node, document); - // // Add filtterd items to the completion list + // // Add filtered items to the completion list // getCompletionEntriesFromSymbols({ // symbols: filterContextualMembersList(contextualMembers.symbols, existingMembers, filename, position), // enclosingScopeSymbol: contextualMembers.enclosingScopeSymbol // }, entries); //} } - // Get scope memebers + // Get scope members else { isMemberCompletion = false; /// TODO filter meaning based on the current context @@ -1870,7 +1870,7 @@ module ts { function getCompletionEntryDetails(filename: string, position: number, entryName: string) { // Note: No need to call synchronizeHostData, as we have captured all the data we need - // in the getCompletionsAtPosition erlier + // in the getCompletionsAtPosition earlier filename = TypeScript.switchToForwardSlashes(filename); var session = activeCompletionSession; @@ -2434,7 +2434,7 @@ module ts { // Could not find a symbol e.g. unknown identifier if (!symbol) { - // Even if we did not find a symbol, we have an identifer, so there is at least + // Even if we did not find a symbol, we have an identifier, so there is at least // one reference that we know of. return that instead of undefined. return [getReferenceEntryFromNode(node)]; } @@ -2824,7 +2824,7 @@ module ts { var propertySymbol = typeReferenceSymbol.members[propertyName]; if (propertySymbol) result.push(typeReferenceSymbol.members[propertyName]); - // Visit the typeReference as well to see if it directelly or indirectelly use that property + // Visit the typeReference as well to see if it directly or indirectly use that property getPropertySymbolsFromBaseTypes(typeReferenceSymbol, propertyName, result); } } @@ -2832,7 +2832,7 @@ module ts { } function isRelatableToSearchSet(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): boolean { - // Unwrap symbols to get to the root (e.g. triansient symbols as a result of widenning) + // Unwrap symbols to get to the root (e.g. transient symbols as a result of widening) var referenceSymbolTarget = typeInfoResolver.getRootSymbol(referenceSymbol); // if it is in the list, then we are done @@ -2850,7 +2850,7 @@ module ts { } } - // Finally, try all properties with the same name in any type the containing type extened or implemented, and + // Finally, try all properties with the same name in any type the containing type extend or implemented, and // see if any is in the list if (referenceSymbol.parent && referenceSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { var result: Symbol[] = []; @@ -2913,7 +2913,7 @@ module ts { case SyntaxKind.ImportDeclaration: return SearchMeaning.Value | SearchMeaning.Type | SearchMeaning.Namespace; } - Debug.fail("Unkown declaration type"); + Debug.fail("Unknown declaration type"); } function isTypeReference(node: Node): boolean { @@ -2994,7 +2994,7 @@ module ts { // intersects with the class in the value space. // To achieve that we will keep iterating until the result stabilizes. - // Remeber the last meaning + // Remember the last meaning var lastIterationMeaning = meaning; for (var i = 0, n = declarations.length; i < n; i++) { @@ -3022,7 +3022,7 @@ module ts { return new ReferenceEntry(node.getSourceFile().filename, TypeScript.TextSpan.fromBounds(start, end), isWriteAccess(node)); } - /// A node is considedered a writeAccess iff it is a name of a declaration or a target of an assignment + /// A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment function isWriteAccess(node: Node): boolean { if (node.kind === SyntaxKind.Identifier && isDeclarationOrFunctionExpressionOrCatchVariableName(node)) { return true; @@ -3308,7 +3308,7 @@ module ts { var preamble = matchArray[1]; var matchPosition = matchArray.index + preamble.length; - // Ok, we have found a match in the file. This is ony an acceptable match if + // Ok, we have found a match in the file. This is only an acceptable match if // it is contained within a comment. var token = TypeScript.findToken(syntaxTree.sourceUnit(), matchPosition); @@ -3318,7 +3318,7 @@ module ts { continue; } - // Looks to be within the trivia. See if we can find hte comment containing it. + // Looks to be within the trivia. See if we can find the comment containing it. var triviaList = matchPosition < TypeScript.start(token) ? token.leadingTrivia(syntaxTree.text) : token.trailingTrivia(syntaxTree.text); var trivia = findContainingComment(triviaList, matchPosition); if (trivia === null) { @@ -3526,7 +3526,7 @@ module ts { addResult(start - lastTokenOrCommentEnd, TokenClass.Whitespace); } - // Remeber the end of the last token + // Remember the end of the last token lastTokenOrCommentEnd = end; } From ebb0beb2035822537b54d0eeea48b95cd49aa657 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Sep 2014 17:23:38 -0700 Subject: [PATCH 44/46] Adding classification tests. --- src/harness/fourslash.ts | 40 +++++++++++++++++++ src/services/services.ts | 24 ++++++++---- src/services/text/textSpan.ts | 1 + tests/cases/fourslash/fourslash.ts | 63 ++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 8 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index c571a7d9f94..0bcea9367e4 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1428,6 +1428,46 @@ module FourSlash { Harness.IO.log(this.getNameOrDottedNameSpan(pos)); } + private verifyClassifications(expected: { classificationType: string; text: string }[], actual: ts.ClassifiedSpan[]) { + if (actual.length !== expected.length) { + throw new Error('verifySyntacticClassification failed - expected total classifications to be ' + expected.length + ', but was ' + actual.length); + } + + for (var i = 0; i < expected.length; i++) { + var expectedClassification = expected[i]; + var actualClassification = actual[i]; + + var expectedType: string = (ts.ClassificationTypeNames)[expectedClassification.classificationType]; + if (expectedType !== actualClassification.classificationType) { + throw new Error('verifySyntacticClassification failed - expected classifications type to be ' + + expectedType + ', but was ' + + actualClassification.classificationType); + } + + var actualSpan = actualClassification.textSpan; + var actualText = this.activeFile.content.substr(actualSpan.start(), actualSpan.length()); + if (expectedClassification.text !== actualText) { + throw new Error('verifySyntacticClassification failed - expected classificatied text to be ' + + expectedClassification.text + ', but was ' + + actualText); + } + } + } + + public verifySemanticClassifications(expected: { classificationType: string; text: string }[]) { + var actual = this.languageService.getSemanticClassifications(this.activeFile.fileName, + new TypeScript.TextSpan(0, this.activeFile.content.length)); + + this.verifyClassifications(expected, actual); + } + + public verifySyntacticClassifications(expected: { classificationType: string; text: string }[]) { + var actual = this.languageService.getSyntacticClassifications(this.activeFile.fileName, + new TypeScript.TextSpan(0, this.activeFile.content.length)); + + this.verifyClassifications(expected, actual); + } + public verifyOutliningSpans(spans: TextSpan[]) { this.taoInvalidReason = 'verifyOutliningSpans NYI'; diff --git a/src/services/services.ts b/src/services/services.ts index 6385f68c7ae..90170ad0b3f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -470,7 +470,7 @@ module ts { dispose(): void; } - class ClassificationTypeNames { + export class ClassificationTypeNames { public static comment = "comment"; public static identifier = "identifier"; public static keyword = "keyword"; @@ -3185,14 +3185,16 @@ module ts { } function processNode(node: Node) { - if (span.intersectsWith(node.getStart(), node.getWidth())) { - if (node.kind === SyntaxKind.Identifier && node.getWidth()) { + // Only walk into nodes that intersect the requested span. + if (node && span.intersectsWith(node.getStart(), node.getWidth())) { + if (node.kind === SyntaxKind.Identifier && node.getWidth() > 0) { var symbol = typeInfoResolver.getSymbolInfo(node); if (symbol) { - var span = new TypeScript.TextSpan(node.getStart(), node.getWidth()); var type = classifySymbol(symbol); if (type) { - result.push(new ClassifiedSpan(span, type)); + result.push(new ClassifiedSpan( + new TypeScript.TextSpan(node.getStart(), node.getWidth()), + type)); } } } @@ -3213,7 +3215,7 @@ module ts { return result; function classifyTrivia(trivia: TypeScript.ISyntaxTrivia) { - if (span.intersectsWith(trivia.fullStart(), trivia.fullWidth())) { + if (trivia.isComment() && span.intersectsWith(trivia.fullStart(), trivia.fullWidth())) { result.push(new ClassifiedSpan( new TypeScript.TextSpan(trivia.fullStart(), trivia.fullWidth()), ClassificationTypeNames.comment)); @@ -3232,10 +3234,11 @@ module ts { } if (TypeScript.width(token) > 0) { - var span = new TypeScript.TextSpan(TypeScript.start(token), TypeScript.width(token)); var type = classifyTokenType(token); if (type) { - result.push(new ClassifiedSpan(span, type)); + result.push(new ClassifiedSpan( + new TypeScript.TextSpan(TypeScript.start(token), TypeScript.width(token)), + type)); } } @@ -3287,6 +3290,11 @@ module ts { } switch (parent.kind()) { + case TypeScript.SyntaxKind.SimplePropertyAssignment: + if ((parent).propertyName === token) { + return ClassificationTypeNames.identifier; + } + return; case TypeScript.SyntaxKind.ClassDeclaration: if ((parent).identifier === token) { return ClassificationTypeNames.className; diff --git a/src/services/text/textSpan.ts b/src/services/text/textSpan.ts index d719e244010..999070a73b4 100644 --- a/src/services/text/textSpan.ts +++ b/src/services/text/textSpan.ts @@ -1,6 +1,7 @@ /// module TypeScript { + export interface ISpan { start(): number; end(): number; diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index b4080d12e2f..df4e3b31730 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -382,6 +382,10 @@ module FourSlashInterface { public completionEntryDetailIs(entryName: string, type: string, docComment?: string, fullSymbolName?: string, kind?: string) { FourSlash.currentTestState.verifyCompletionEntryDetails(entryName, type, docComment, fullSymbolName, kind); } + + public syntacticClassificationsAre(...classifications: { classificationType: string; text: string }[]) { + FourSlash.currentTestState.verifySyntacticClassifications(classifications); + } } export class edit { @@ -524,6 +528,64 @@ module FourSlashInterface { FourSlash.currentTestState.cancellationToken.setCancelled(numberOfCalls); } } + + export class classification { + public static comment(text: string): { classificationType: string; text: string } { + return { classificationType: "comment", text: text }; + } + + public static identifier(text: string): { classificationType: string; text: string } { + return { classificationType: "identifier", text: text }; + } + + public static keyword(text: string): { classificationType: string; text: string } { + return { classificationType: "keyword", text: text }; + } + + public static numericLiteral(text: string): { classificationType: string; text: string } { + return { classificationType: "numericLiteral", text: text }; + } + + public static operator(text: string): { classificationType: string; text: string } { + return { classificationType: "operator", text: text }; + } + + public static stringLiteral(text: string): { classificationType: string; text: string } { + return { classificationType: "stringLiteral", text: text }; + } + + public static whiteSpace(text: string): { classificationType: string; text: string } { + return { classificationType: "whiteSpace", text: text }; + } + + public static text(text: string): { classificationType: string; text: string } { + return { classificationType: "text", text: text }; + } + + public static punctuation(text: string): { classificationType: string; text: string } { + return { classificationType: "punctuation", text: text }; + } + + public static className(text: string): { classificationType: string; text: string } { + return { classificationType: "className", text: text }; + } + + public static enumName(text: string): { classificationType: string; text: string } { + return { classificationType: "enumName", text: text }; + } + + public static interfaceName(text: string): { classificationType: string; text: string } { + return { classificationType: "interfaceName", text: text }; + } + + public static moduleName(text: string): { classificationType: string; text: string } { + return { classificationType: "moduleName", text: text }; + } + + public static typeParameterName(text: string): { classificationType: string; text: string } { + return { classificationType: "typeParameterName", text: text }; + } + } } module fs { @@ -547,3 +609,4 @@ var debug = new FourSlashInterface.debug(); var format = new FourSlashInterface.format(); var diagnostics = new FourSlashInterface.diagnostics(); var cancellation = new FourSlashInterface.cancellation(); +var classification = FourSlashInterface.classification; From 7be2d2866b5f83af4b6ccbeef4a64bdc2ed9a1d9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Sep 2014 17:39:48 -0700 Subject: [PATCH 45/46] Classification tests. --- tests/cases/fourslash/fourslash.ts | 4 +++ .../fourslash/semanticClassification1.ts | 12 +++++++ .../fourslash/syntacticClassifications1.ts | 36 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 tests/cases/fourslash/semanticClassification1.ts create mode 100644 tests/cases/fourslash/syntacticClassifications1.ts diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index df4e3b31730..e1b1e26c3c2 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -386,6 +386,10 @@ module FourSlashInterface { public syntacticClassificationsAre(...classifications: { classificationType: string; text: string }[]) { FourSlash.currentTestState.verifySyntacticClassifications(classifications); } + + public semanticClassificationsAre(...classifications: { classificationType: string; text: string }[]) { + FourSlash.currentTestState.verifySemanticClassifications(classifications); + } } export class edit { diff --git a/tests/cases/fourslash/semanticClassification1.ts b/tests/cases/fourslash/semanticClassification1.ts new file mode 100644 index 00000000000..0a0cc68250e --- /dev/null +++ b/tests/cases/fourslash/semanticClassification1.ts @@ -0,0 +1,12 @@ +/// + +//// module M { +//// export interface I { +//// } +//// } +//// interface X extends M.I { } + +debugger; +var c = classification; +verify.semanticClassificationsAre( + c.moduleName("M"), c.interfaceName("I"), c.interfaceName("X"), c.moduleName("M"), c.interfaceName("I")); diff --git a/tests/cases/fourslash/syntacticClassifications1.ts b/tests/cases/fourslash/syntacticClassifications1.ts new file mode 100644 index 00000000000..e70a4b672ff --- /dev/null +++ b/tests/cases/fourslash/syntacticClassifications1.ts @@ -0,0 +1,36 @@ +/// + +//// // comment +//// module M { +//// var v = 0 + 1; +//// var s = "string"; +//// +//// class C { +//// } +//// +//// enum E { +//// } +//// +//// interface I { +//// } +//// +//// module M1.M2 { +//// } +//// } + +debugger; +var c = classification; +verify.syntacticClassificationsAre( + c.comment("// comment"), + c.keyword("module"), c.moduleName("M"), c.punctuation("{"), + c.keyword("var"), c.text("v"), c.operator("="), c.numericLiteral("0"), c.operator("+"), c.numericLiteral("1"), c.punctuation(";"), + c.keyword("var"), c.text("s"), c.operator("="), c.stringLiteral('"string"'), c.punctuation(";"), + c.keyword("class"), c.className("C"), c.punctuation("<"), c.typeParameterName("T"), c.punctuation(">"), c.punctuation("{"), + c.punctuation("}"), + c.keyword("enum"), c.enumName("E"), c.punctuation("{"), + c.punctuation("}"), + c.keyword("interface"), c.interfaceName("I"), c.punctuation("{"), + c.punctuation("}"), + c.keyword("module"), c.moduleName("M1"), c.punctuation("."), c.moduleName("M2"), c.punctuation("{"), + c.punctuation("}"), + c.punctuation("}")); \ No newline at end of file From 38d29249a4b883e027a66247fd714c0ba129c315 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Sep 2014 18:52:07 -0700 Subject: [PATCH 46/46] CR feedback. --- src/services/services.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/services.ts b/src/services/services.ts index 90170ad0b3f..e5b10b8056f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3160,7 +3160,7 @@ module ts { var sourceFile = getSourceFile(fileName); var result: ClassifiedSpan[] = []; - processNode(sourceFile.getSourceFile()); + processNode(sourceFile); return result;