From e12b708221ab7abe22e841d6b77a51d3d238c993 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 2 May 2017 15:33:13 -0700 Subject: [PATCH 01/34] For completions of union type, get all possible properties --- src/compiler/checker.ts | 19 ++++++++++++++++- src/compiler/types.ts | 5 +++++ src/services/completions.ts | 21 +++++++------------ .../cases/fourslash/completionListOfUnion.ts | 18 ++++++++++++++++ 4 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 tests/cases/fourslash/completionListOfUnion.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ca5d65068a0..3c45942fd62 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -204,7 +204,8 @@ namespace ts { // since we are only interested in declarations of the module itself return tryFindAmbientModule(moduleName, /*withAugmentations*/ false); }, - getApparentType + getApparentType, + getAllPossiblePropertiesOfType, }; const tupleTypes: GenericType[] = []; @@ -5648,6 +5649,22 @@ namespace ts { getPropertiesOfObjectType(type); } + function getAllPossiblePropertiesOfType(type: Type): Symbol[] { + if (type.flags & TypeFlags.Union) { + const props = createMap(); + for (const memberType of (type as UnionType).types) { + for (const { name } of getPropertiesOfType(memberType)) { + if (!props.has(name)) { + props.set(name, createUnionOrIntersectionProperty(type as UnionType, name)); + } + } + } + return arrayFrom(props.values()); + } else { + return getPropertiesOfType(type); + } + } + function getConstraintOfType(type: TypeVariable | UnionOrIntersectionType): Type { return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(type) : type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(type) : diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d9b4ca192f4..aad4f2680e3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2564,6 +2564,11 @@ namespace ts { /* @internal */ getIdentifierCount(): number; /* @internal */ getSymbolCount(): number; /* @internal */ getTypeCount(): number; + + /** For a union, will include a property if it's defined in *any* of the member types. + * So for `{ a } | { b }`, this will include both `a` and `b`. + */ + /* @internal */ getAllPossiblePropertiesOfType(type: Type): Symbol[]; } export enum NodeBuilderFlags { diff --git a/src/services/completions.ts b/src/services/completions.ts index 6ac3762b4c7..f429ed34b6b 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -813,19 +813,16 @@ namespace ts.Completions { // We're looking up possible property names from contextual/inferred/declared type. isMemberCompletion = true; - let typeForObject: Type; + let typeMembers: Symbol[]; let existingMembers: Declaration[]; if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { // We are completing on contextual types, but may also include properties // other than those within the declared type. isNewIdentifierLocation = true; - - // If the object literal is being assigned to something of type 'null | { hello: string }', - // it clearly isn't trying to satisfy the 'null' type. So we grab the non-nullable type if possible. - typeForObject = typeChecker.getContextualType(objectLikeContainer); - typeForObject = typeForObject && typeForObject.getNonNullableType(); - + const typeForObject = typeChecker.getContextualType(objectLikeContainer); + if (!typeForObject) return false; + typeMembers = typeChecker.getAllPossiblePropertiesOfType(typeForObject); existingMembers = (objectLikeContainer).properties; } else if (objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern) { @@ -849,7 +846,10 @@ namespace ts.Completions { } } if (canGetType) { - typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); + const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); + if (!typeForObject) return false; + // In a binding pattern, get only known properties. Everywhere else we will get all possible properties. + typeMembers = typeChecker.getPropertiesOfType(typeForObject); existingMembers = (objectLikeContainer).elements; } } @@ -861,11 +861,6 @@ namespace ts.Completions { Debug.fail("Expected object literal or binding pattern, got " + objectLikeContainer.kind); } - if (!typeForObject) { - return false; - } - - const typeMembers = typeChecker.getPropertiesOfType(typeForObject); if (typeMembers && typeMembers.length > 0) { // Add filtered items to the completion list symbols = filterObjectMembersList(typeMembers, existingMembers); diff --git a/tests/cases/fourslash/completionListOfUnion.ts b/tests/cases/fourslash/completionListOfUnion.ts new file mode 100644 index 00000000000..6026615f8c1 --- /dev/null +++ b/tests/cases/fourslash/completionListOfUnion.ts @@ -0,0 +1,18 @@ +/// + +// @strictNullChecks: true + +// Non-objects should be skipped, so `| number | null` should have no effect on completions. +////const x: { a: number, b: number } | { a: string, c: string } | { b: boolean } | number | null = { /*x*/ }; + +////interface I { a: number; } +////function f(...args: Array) {} +////f({ /*f*/ }); + +goTo.marker("x"); +verify.completionListContains("a", "(property) a: string | number"); +verify.completionListContains("b", "(property) b: number | boolean"); +verify.completionListContains("c", "(property) c: string"); + +goTo.marker("f"); +verify.completionListContains("a", "(property) a: number"); From b3d793608d8525b2dc3ee1f2a403bf733ba6d102 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 27 Apr 2017 15:46:07 -0700 Subject: [PATCH 02/34] Completion list for a class extending another class should contain members from base class Handles #7158 --- src/compiler/checker.ts | 23 --- src/compiler/utilities.ts | 23 +++ src/services/completions.ts | 92 +++++++++++- .../completionEntryForClassMembers.ts | 142 ++++++++++++++++++ .../completionListInNamedClassExpression.ts | 4 +- ...ListInNamedClassExpressionWithShadowing.ts | 8 +- .../completionListIsGlobalCompletion.ts | 2 +- ...pletionListWithModulesInsideModuleScope.ts | 41 ++--- ...etionListWithModulesOutsideModuleScope2.ts | 35 ++++- 9 files changed, 313 insertions(+), 57 deletions(-) create mode 100644 tests/cases/fourslash/completionEntryForClassMembers.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 84ff54391de..a4470a15ea3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -679,10 +679,6 @@ namespace ts { return type.flags & TypeFlags.Object ? (type).objectFlags : 0; } - function getCheckFlags(symbol: Symbol): CheckFlags { - return symbol.flags & SymbolFlags.Transient ? (symbol).checkFlags : 0; - } - function isGlobalSourceFile(node: Node) { return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node); } @@ -13928,25 +13924,6 @@ namespace ts { return s.valueDeclaration ? s.valueDeclaration.kind : SyntaxKind.PropertyDeclaration; } - function getDeclarationModifierFlagsFromSymbol(s: Symbol): ModifierFlags { - if (s.valueDeclaration) { - const flags = getCombinedModifierFlags(s.valueDeclaration); - return s.parent && s.parent.flags & SymbolFlags.Class ? flags : flags & ~ModifierFlags.AccessibilityModifier; - } - if (getCheckFlags(s) & CheckFlags.Synthetic) { - const checkFlags = (s).checkFlags; - const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private : - checkFlags & CheckFlags.ContainsPublic ? ModifierFlags.Public : - ModifierFlags.Protected; - const staticModifier = checkFlags & CheckFlags.ContainsStatic ? ModifierFlags.Static : 0; - return accessModifier | staticModifier; - } - if (s.flags & SymbolFlags.Prototype) { - return ModifierFlags.Public | ModifierFlags.Static; - } - return 0; - } - function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f1b181a03b0..f341e912ccc 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4200,6 +4200,29 @@ namespace ts { // Firefox has Object.prototype.watch return options.watch && options.hasOwnProperty("watch"); } + + export function getCheckFlags(symbol: Symbol): CheckFlags { + return symbol.flags & SymbolFlags.Transient ? (symbol).checkFlags : 0; + } + + export function getDeclarationModifierFlagsFromSymbol(s: Symbol): ModifierFlags { + if (s.valueDeclaration) { + const flags = getCombinedModifierFlags(s.valueDeclaration); + return s.parent && s.parent.flags & SymbolFlags.Class ? flags : flags & ~ModifierFlags.AccessibilityModifier; + } + if (getCheckFlags(s) & CheckFlags.Synthetic) { + const checkFlags = (s).checkFlags; + const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private : + checkFlags & CheckFlags.ContainsPublic ? ModifierFlags.Public : + ModifierFlags.Protected; + const staticModifier = checkFlags & CheckFlags.ContainsStatic ? ModifierFlags.Static : 0; + return accessModifier | staticModifier; + } + if (s.flags & SymbolFlags.Prototype) { + return ModifierFlags.Public | ModifierFlags.Static; + } + return 0; + } } namespace ts { diff --git a/src/services/completions.ts b/src/services/completions.ts index 6ac3762b4c7..44915a958c2 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -18,7 +18,7 @@ namespace ts.Completions { return undefined; } - const { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, requestJsDocTagName, requestJsDocTag } = completionData; + const { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, requestJsDocTagName, requestJsDocTag, hasFilteredClassMemberKeywords } = completionData; if (requestJsDocTagName) { // If the current position is a jsDoc tag name, only tag names should be provided for completion @@ -52,7 +52,7 @@ namespace ts.Completions { sortText: "0", }); } - else { + else if (!hasFilteredClassMemberKeywords) { return undefined; } } @@ -60,8 +60,11 @@ namespace ts.Completions { getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log); } + if (hasFilteredClassMemberKeywords) { + addRange(entries, classMemberKeywordCompletions); + } // Add keywords if this is not a member completion list - if (!isMemberCompletion && !requestJsDocTag && !requestJsDocTagName) { + else if (!isMemberCompletion && !requestJsDocTag && !requestJsDocTagName) { addRange(entries, keywordCompletions); } @@ -406,7 +409,7 @@ namespace ts.Completions { } if (requestJsDocTagName || requestJsDocTag) { - return { symbols: undefined, isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, requestJsDocTagName, requestJsDocTag }; + return { symbols: undefined, isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, requestJsDocTagName, requestJsDocTag, hasFilteredClassMemberKeywords: false }; } if (!insideJsDocTagExpression) { @@ -505,6 +508,7 @@ namespace ts.Completions { let isGlobalCompletion = false; let isMemberCompletion: boolean; let isNewIdentifierLocation: boolean; + let hasFilteredClassMemberKeywords = false; let symbols: Symbol[] = []; if (isRightOfDot) { @@ -542,7 +546,7 @@ namespace ts.Completions { log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); - return { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), requestJsDocTagName, requestJsDocTag }; + return { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), requestJsDocTagName, requestJsDocTag, hasFilteredClassMemberKeywords }; function getTypeScriptMemberSymbols(): void { // Right of dot member completion list @@ -599,6 +603,7 @@ namespace ts.Completions { function tryGetGlobalSymbols(): boolean { let objectLikeContainer: ObjectLiteralExpression | BindingPattern; let namedImportsOrExports: NamedImportsOrExports; + let classLikeContainer: ClassLikeDeclaration; let jsxContainer: JsxOpeningLikeElement; if (objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken)) { @@ -611,6 +616,11 @@ namespace ts.Completions { return tryGetImportOrExportClauseCompletionSymbols(namedImportsOrExports); } + if (classLikeContainer = tryGetClassLikeCompletionContainer(contextToken)) { + // cursor inside class declaration + return tryGetClassLikeCompletionSymbols(classLikeContainer); + } + if (jsxContainer = tryGetContainingJsxElement(contextToken)) { let attrsType: Type; if ((jsxContainer.kind === SyntaxKind.JsxSelfClosingElement) || (jsxContainer.kind === SyntaxKind.JsxOpeningElement)) { @@ -913,6 +923,31 @@ namespace ts.Completions { return true; } + /** + * Aggregates relevant symbols for completion in class declaration + * Relevant symbols are stored in the captured 'symbols' variable. + * + * @returns true if 'symbols' was successfully populated; false otherwise. + */ + function tryGetClassLikeCompletionSymbols(classLikeDeclaration: ClassLikeDeclaration): boolean { + // We're looking up possible property names from parent type. + isMemberCompletion = true; + // Declaring new property/method/accessor + isNewIdentifierLocation = true; + // Has keywords for class elements + hasFilteredClassMemberKeywords = true; + + const baseTypeNode = getClassExtendsHeritageClauseElement(classLikeDeclaration); + if (baseTypeNode) { + const baseType = typeChecker.getTypeAtLocation(baseTypeNode); + // List of property symbols of base type that are not private + symbols = filter(typeChecker.getPropertiesOfType(baseType), + baseProperty => !(getDeclarationModifierFlagsFromSymbol(baseProperty) & ModifierFlags.Private)); + } + + return true; + } + /** * Returns the immediate owning object literal or binding pattern of a context token, * on the condition that one exists and that the context implies completion should be given. @@ -953,6 +988,38 @@ namespace ts.Completions { return undefined; } + /** + * Returns the immediate owning class declaration of a context token, + * on the condition that one exists and that the context implies completion should be given. + */ + function tryGetClassLikeCompletionContainer(contextToken: Node): ClassLikeDeclaration { + if (contextToken) { + switch (contextToken.kind) { + case SyntaxKind.OpenBraceToken: // class c { | + if (isClassLike(contextToken.parent)) { + return contextToken.parent; + } + break; + + // class c {getValue(): number; | } + case SyntaxKind.CommaToken: + case SyntaxKind.SemicolonToken: + // class c { method() { } | } + case SyntaxKind.CloseBraceToken: + if (isClassLike(location)) { + return location; + } + break; + } + } + + // class c { method() { } | method2() { } } + if (location && location.kind === SyntaxKind.SyntaxList && isClassLike(location.parent)) { + return location.parent; + } + return undefined; + } + function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement { if (contextToken) { const parent = contextToken.parent; @@ -1306,6 +1373,21 @@ namespace ts.Completions { }); } + const classMemberKeywordCompletions = filter(keywordCompletions, entry => { + switch (stringToToken(entry.name)) { + case SyntaxKind.PublicKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.AbstractKeyword: + case SyntaxKind.StaticKeyword: + case SyntaxKind.ConstructorKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + return true; + } + }); + function isEqualityExpression(node: Node): node is BinaryExpression { return isBinaryExpression(node) && isEqualityOperatorKind(node.operatorToken.kind); } diff --git a/tests/cases/fourslash/completionEntryForClassMembers.ts b/tests/cases/fourslash/completionEntryForClassMembers.ts new file mode 100644 index 00000000000..da1051e784a --- /dev/null +++ b/tests/cases/fourslash/completionEntryForClassMembers.ts @@ -0,0 +1,142 @@ +/// + +////abstract class B { +//// abstract getValue(): number; +//// /*abstractClass*/ +////} +////class C extends B { +//// /*classThatIsEmptyAndExtendingAnotherClass*/ +////} +////class D extends B { +//// /*classThatHasAlreadyImplementedAnotherClassMethod*/ +//// getValue() { +//// return 10; +//// } +//// /*classThatHasAlreadyImplementedAnotherClassMethodAfterMethod*/ +////} +////class E { +//// /*classThatDoesNotExtendAnotherClass*/ +////} +////class F extends B { +//// public /*classThatHasWrittenPublicKeyword*/ +////} +////class G extends B { +//// static /*classElementContainingStatic*/ +////} +////class H extends B { +//// prop/*classThatStartedWritingIdentifier*/ +////} +////class I extends B { +//// prop0: number +//// /*propDeclarationWithoutSemicolon*/ +//// prop: number; +//// /*propDeclarationWithSemicolon*/ +//// prop1 = 10; +//// /*propAssignmentWithSemicolon*/ +//// prop2 = 10 +//// /*propAssignmentWithoutSemicolon*/ +//// method(): number +//// /*methodSignatureWithoutSemicolon*/ +//// method2(): number; +//// /*methodSignatureWithSemicolon*/ +//// method3() { +//// /*InsideMethod*/ +//// } +//// /*methodImplementation*/ +//// get c() +//// /*accessorSignatureWithoutSemicolon*/ +//// set c() +//// { +//// } +//// /*accessorSignatureImplementation*/ +////} +////class J extends B { +//// get /*classThatHasWrittenGetKeyword*/ +////} +////class K extends B { +//// set /*classThatHasWrittenSetKeyword*/ +////} +////class J extends B { +//// get identi/*classThatStartedWritingIdentifierOfGetAccessor*/ +////} +////class K extends B { +//// set identi/*classThatStartedWritingIdentifierOfSetAccessor*/ +////} +////class L extends B { +//// public identi/*classThatStartedWritingIdentifierAfterModifier*/ +////} +////class L extends B { +//// static identi/*classThatStartedWritingIdentifierAfterStaticModifier*/ +////} + +const allowedKeywords = [ + "public", + "private", + "protected", + "static", + "abstract", + "readonly", + "get", + "set", + "constructor" +]; + +const allowedKeywordCount = allowedKeywords.length; +function verifyAllowedKeyWords() { + for (const keyword of allowedKeywords) { + verify.completionListContains(keyword, keyword, /*documentation*/ undefined, "keyword"); + } +} + +const nonClassElementMarkers = [ + "InsideMethod" +]; +for (const marker of nonClassElementMarkers) { + goTo.marker(marker); + verify.not.completionListContains("getValue"); + verify.not.completionListIsEmpty(); +} + +// Only keywords allowed at this position since they dont extend the class +const onlyClassElementKeywordLocations = [ + "abstractClass", + "classThatDoesNotExtendAnotherClass" +]; +for (const marker of onlyClassElementKeywordLocations) { + goTo.marker(marker); + verifyAllowedKeyWords(); + verify.completionListCount(allowedKeywordCount); +} + +// Base members and class member keywords allowed +const classElementCompletionLocations = [ + "classThatIsEmptyAndExtendingAnotherClass", + "classThatHasAlreadyImplementedAnotherClassMethod", + "classThatHasAlreadyImplementedAnotherClassMethodAfterMethod", + // TODO should we give completion for these keywords + //"classThatHasWrittenPublicKeyword", + //"classElementContainingStatic", + "classThatStartedWritingIdentifier", + "propDeclarationWithoutSemicolon", + "propDeclarationWithSemicolon", + "propAssignmentWithSemicolon", + "propAssignmentWithoutSemicolon", + "methodSignatureWithoutSemicolon", + "methodSignatureWithSemicolon", + "methodImplementation", + "accessorSignatureWithoutSemicolon", + "accessorSignatureImplementation", + // TODO should we give completion for these keywords + //"classThatHasWrittenGetKeyword", + //"classThatHasWrittenSetKeyword", + //"classThatStartedWritingIdentifierOfGetAccessor", + //"classThatStartedWritingIdentifierOfSetAccessor", + //"classThatStartedWritingIdentifierAfterModifier", + //"classThatStartedWritingIdentifierAfterStaticModifier" +]; +for (const marker of classElementCompletionLocations) { + goTo.marker(marker); + verify.completionListContains("getValue", "(method) B.getValue(): number", /*documentation*/ undefined, "method"); + verifyAllowedKeyWords(); + verify.completionListCount(allowedKeywordCount + 1); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInNamedClassExpression.ts b/tests/cases/fourslash/completionListInNamedClassExpression.ts index cf0d170f6a5..8ca6806ce7f 100644 --- a/tests/cases/fourslash/completionListInNamedClassExpression.ts +++ b/tests/cases/fourslash/completionListInNamedClassExpression.ts @@ -1,4 +1,4 @@ -/// +/// //// var x = class myClass { //// getClassName (){ @@ -11,4 +11,4 @@ goTo.marker("0"); verify.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); goTo.marker("1"); -verify.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); \ No newline at end of file +verify.not.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInNamedClassExpressionWithShadowing.ts b/tests/cases/fourslash/completionListInNamedClassExpressionWithShadowing.ts index e1274e6f592..fd347d58037 100644 --- a/tests/cases/fourslash/completionListInNamedClassExpressionWithShadowing.ts +++ b/tests/cases/fourslash/completionListInNamedClassExpressionWithShadowing.ts @@ -1,4 +1,4 @@ -/// +/// //// class myClass { /*0*/ } //// /*1*/ @@ -16,7 +16,7 @@ //// } goTo.marker("0"); -verify.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); +verify.not.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); verify.not.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); goTo.marker("1"); @@ -28,7 +28,7 @@ verify.completionListContains("myClass", "(local class) myClass", /*documentatio verify.not.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); goTo.marker("3"); -verify.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); +verify.not.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); verify.not.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); goTo.marker("4"); @@ -36,5 +36,5 @@ verify.completionListContains("myClass", "class myClass", /*documentation*/ unde verify.not.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); goTo.marker("5"); -verify.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); +verify.not.completionListContains("myClass", "class myClass", /*documentation*/ undefined, "class"); verify.not.completionListContains("myClass", "(local class) myClass", /*documentation*/ undefined, "local class"); diff --git a/tests/cases/fourslash/completionListIsGlobalCompletion.ts b/tests/cases/fourslash/completionListIsGlobalCompletion.ts index a1fadc6bb12..121ab940d65 100644 --- a/tests/cases/fourslash/completionListIsGlobalCompletion.ts +++ b/tests/cases/fourslash/completionListIsGlobalCompletion.ts @@ -53,7 +53,7 @@ verify.completionListIsGlobal(false); goTo.marker("9"); verify.completionListIsGlobal(false); goTo.marker("10"); -verify.completionListIsGlobal(true); +verify.completionListIsGlobal(false); goTo.marker("11"); verify.completionListIsGlobal(true); goTo.marker("12"); diff --git a/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts b/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts index 51e4013e53a..916ad10fe9c 100644 --- a/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts +++ b/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts @@ -225,27 +225,28 @@ //// ////var shwvar = 1; -function goToMarkAndGeneralVerify(marker: string) +function goToMarkAndGeneralVerify(marker: string, isClassScope?: boolean) { goTo.marker(marker); - verify.completionListContains('mod1var', 'var mod1var: number'); - verify.completionListContains('mod1fn', 'function mod1fn(): void'); - verify.completionListContains('mod1cls', 'class mod1cls'); - verify.completionListContains('mod1int', 'interface mod1int'); - verify.completionListContains('mod1mod', 'namespace mod1mod'); - verify.completionListContains('mod1evar', 'var mod1.mod1evar: number'); - verify.completionListContains('mod1efn', 'function mod1.mod1efn(): void'); - verify.completionListContains('mod1ecls', 'class mod1.mod1ecls'); - verify.completionListContains('mod1eint', 'interface mod1.mod1eint'); - verify.completionListContains('mod1emod', 'namespace mod1.mod1emod'); - verify.completionListContains('mod1eexvar', 'var mod1.mod1eexvar: number'); - verify.completionListContains('mod2', 'namespace mod2'); - verify.completionListContains('mod3', 'namespace mod3'); - verify.completionListContains('shwvar', 'var shwvar: number'); - verify.completionListContains('shwfn', 'function shwfn(): void'); - verify.completionListContains('shwcls', 'class shwcls'); - verify.completionListContains('shwint', 'interface shwint'); + const verifyModule = isClassScope ? verify.not : verify; + verifyModule.completionListContains('mod1var', 'var mod1var: number'); + verifyModule.completionListContains('mod1fn', 'function mod1fn(): void'); + verifyModule.completionListContains('mod1cls', 'class mod1cls'); + verifyModule.completionListContains('mod1int', 'interface mod1int'); + verifyModule.completionListContains('mod1mod', 'namespace mod1mod'); + verifyModule.completionListContains('mod1evar', 'var mod1.mod1evar: number'); + verifyModule.completionListContains('mod1efn', 'function mod1.mod1efn(): void'); + verifyModule.completionListContains('mod1ecls', 'class mod1.mod1ecls'); + verifyModule.completionListContains('mod1eint', 'interface mod1.mod1eint'); + verifyModule.completionListContains('mod1emod', 'namespace mod1.mod1emod'); + verifyModule.completionListContains('mod1eexvar', 'var mod1.mod1eexvar: number'); + verifyModule.completionListContains('mod2', 'namespace mod2'); + verifyModule.completionListContains('mod3', 'namespace mod3'); + verifyModule.completionListContains('shwvar', 'var shwvar: number'); + verifyModule.completionListContains('shwfn', 'function shwfn(): void'); + verifyModule.completionListContains('shwcls', 'class shwcls'); + verifyModule.completionListContains('shwint', 'interface shwint'); verify.not.completionListContains('mod2var'); verify.not.completionListContains('mod2fn'); @@ -280,7 +281,7 @@ verify.completionListContains('bar', '(local var) bar: number'); verify.completionListContains('foob', '(local function) foob(): void'); // from class in mod1 -goToMarkAndGeneralVerify('class'); +goToMarkAndGeneralVerify('class', /*isClassScope*/ true); //verify.not.completionListContains('ceFunc'); //verify.not.completionListContains('ceVar'); @@ -306,7 +307,7 @@ verify.completionListContains('bar', '(local var) bar: number'); verify.completionListContains('foob', '(local function) foob(): void'); // from exported class in mod1 -goToMarkAndGeneralVerify('exportedClass'); +goToMarkAndGeneralVerify('exportedClass', /*isClassScope*/ true); //verify.not.completionListContains('ceFunc'); //verify.not.completionListContains('ceVar'); diff --git a/tests/cases/fourslash/completionListWithModulesOutsideModuleScope2.ts b/tests/cases/fourslash/completionListWithModulesOutsideModuleScope2.ts index 865887661e9..340d0bd3191 100644 --- a/tests/cases/fourslash/completionListWithModulesOutsideModuleScope2.ts +++ b/tests/cases/fourslash/completionListWithModulesOutsideModuleScope2.ts @@ -231,6 +231,39 @@ //// x: /*objectLiteral*/ ////} +goTo.marker('extendedClass'); + +verify.not.completionListContains('mod1'); +verify.not.completionListContains('mod2'); +verify.not.completionListContains('mod3'); +verify.not.completionListContains('shwvar', 'var shwvar: number'); +verify.not.completionListContains('shwfn', 'function shwfn(): void'); +verify.not.completionListContains('shwcls', 'class shwcls'); +verify.not.completionListContains('shwint', 'interface shwint'); + +verify.not.completionListContains('mod2var'); +verify.not.completionListContains('mod2fn'); +verify.not.completionListContains('mod2cls'); +verify.not.completionListContains('mod2int'); +verify.not.completionListContains('mod2mod'); +verify.not.completionListContains('mod2evar'); +verify.not.completionListContains('mod2efn'); +verify.not.completionListContains('mod2ecls'); +verify.not.completionListContains('mod2eint'); +verify.not.completionListContains('mod2emod'); +verify.not.completionListContains('sfvar'); +verify.not.completionListContains('sffn'); +verify.not.completionListContains('scvar'); +verify.not.completionListContains('scfn'); +verify.completionListContains('scpfn'); +verify.completionListContains('scpvar'); +verify.not.completionListContains('scsvar'); +verify.not.completionListContains('scsfn'); +verify.not.completionListContains('sivar'); +verify.not.completionListContains('sifn'); +verify.not.completionListContains('mod1exvar'); +verify.not.completionListContains('mod2eexvar'); + function goToMarkerAndVerify(marker: string) { goTo.marker(marker); @@ -267,8 +300,6 @@ function goToMarkerAndVerify(marker: string) verify.not.completionListContains('mod2eexvar'); } -goToMarkerAndVerify('extendedClass'); - goToMarkerAndVerify('objectLiteral'); goTo.marker('localVar'); From 37a2cddabc552e7e3ce86f94d92f171be76f2f97 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 1 May 2017 11:45:18 -0700 Subject: [PATCH 03/34] Give the class element completion on typing keywords like public, private, readonly Also when name of the function is location, make sure we are actually looking at the same symbol before using the declaration to get signature to display --- src/harness/fourslash.ts | 17 +++++ src/services/completions.ts | 41 ++++++++++-- src/services/symbolDisplay.ts | 44 +++++++------ .../completionEntryForClassMembers.ts | 63 +++++++++---------- ...mpletionListBuilderLocations_properties.ts | 2 +- tests/cases/fourslash/fourslash.ts | 2 + ...cesForClassMembersExtendingGenericClass.ts | 2 +- 7 files changed, 111 insertions(+), 60 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index a56d21ae345..74790400b98 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -3417,6 +3417,17 @@ namespace FourSlashInterface { export class VerifyNegatable { public not: VerifyNegatable; + public allowedClassElementKeywords = [ + "public", + "private", + "protected", + "static", + "abstract", + "readonly", + "get", + "set", + "constructor" + ]; constructor(protected state: FourSlash.TestState, private negative = false) { if (!negative) { @@ -3453,6 +3464,12 @@ namespace FourSlashInterface { this.state.verifyCompletionListIsEmpty(this.negative); } + public completionListContainsClassElementKeywords() { + for (const keyword of this.allowedClassElementKeywords) { + this.completionListContains(keyword, keyword, /*documentation*/ undefined, "keyword"); + } + } + public completionListIsGlobal(expected: boolean) { this.state.verifyCompletionListIsGlobal(expected); } diff --git a/src/services/completions.ts b/src/services/completions.ts index 44915a958c2..da59dab8828 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -988,6 +988,10 @@ namespace ts.Completions { return undefined; } + function isFromClassElementDeclaration(node: Node) { + return isClassElement(node.parent) && isClassLike(node.parent.parent); + } + /** * Returns the immediate owning class declaration of a context token, * on the condition that one exists and that the context implies completion should be given. @@ -1010,6 +1014,13 @@ namespace ts.Completions { return location; } break; + + default: + if (isFromClassElementDeclaration(contextToken) && + (isClassMemberCompletionKeyword(contextToken.kind) || + isClassMemberCompletionKeywordText(contextToken.getText()))) { + return contextToken.parent.parent as ClassLikeDeclaration; + } } } @@ -1148,7 +1159,7 @@ namespace ts.Completions { isFunction(containingNodeKind); case SyntaxKind.StaticKeyword: - return containingNodeKind === SyntaxKind.PropertyDeclaration; + return containingNodeKind === SyntaxKind.PropertyDeclaration && !isClassLike(contextToken.parent.parent); case SyntaxKind.DotDotDotToken: return containingNodeKind === SyntaxKind.Parameter || @@ -1165,13 +1176,17 @@ namespace ts.Completions { containingNodeKind === SyntaxKind.ExportSpecifier || containingNodeKind === SyntaxKind.NamespaceImport; + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + if (isFromClassElementDeclaration(contextToken)) { + return false; + } + // falls through case SyntaxKind.ClassKeyword: case SyntaxKind.EnumKeyword: case SyntaxKind.InterfaceKeyword: case SyntaxKind.FunctionKeyword: case SyntaxKind.VarKeyword: - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: case SyntaxKind.ImportKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.ConstKeyword: @@ -1180,6 +1195,13 @@ namespace ts.Completions { return true; } + // If the previous token is keyword correspoding to class member completion keyword + // there will be completion available here + if (isClassMemberCompletionKeywordText(contextToken.getText()) && + isFromClassElementDeclaration(contextToken)) { + return false; + } + // Previous token may have been a keyword that was converted to an identifier. switch (contextToken.getText()) { case "abstract": @@ -1373,8 +1395,8 @@ namespace ts.Completions { }); } - const classMemberKeywordCompletions = filter(keywordCompletions, entry => { - switch (stringToToken(entry.name)) { + function isClassMemberCompletionKeyword(kind: SyntaxKind) { + switch (kind) { case SyntaxKind.PublicKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.PrivateKeyword: @@ -1386,7 +1408,14 @@ namespace ts.Completions { case SyntaxKind.SetKeyword: return true; } - }); + } + + function isClassMemberCompletionKeywordText(text: string) { + return isClassMemberCompletionKeyword(stringToToken(text)); + } + + const classMemberKeywordCompletions = filter(keywordCompletions, entry => + isClassMemberCompletionKeywordText(entry.name)); function isEqualityExpression(node: Node): node is BinaryExpression { return isBinaryExpression(node) && isEqualityOperatorKind(node.operatorToken.kind); diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index 5bf3e37f28a..4268a52c419 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -200,27 +200,33 @@ namespace ts.SymbolDisplay { (location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration // get the signature from the declaration and write it const functionDeclaration = location.parent; - const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures(); - if (!typeChecker.isImplementationOfOverload(functionDeclaration)) { - signature = typeChecker.getSignatureFromDeclaration(functionDeclaration); - } - else { - signature = allSignatures[0]; - } + // Use function declaration to write the signatures only if the symbol corresponding to this declaration + const locationIsSymbolDeclaration = findDeclaration(symbol, declaration => + declaration === (location.kind === SyntaxKind.ConstructorKeyword ? functionDeclaration.parent : functionDeclaration)); - if (functionDeclaration.kind === SyntaxKind.Constructor) { - // show (constructor) Type(...) signature - symbolKind = ScriptElementKind.constructorImplementationElement; - addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); - } - else { - // (function/method) symbol(..signature) - addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature && - !(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind); - } + if (locationIsSymbolDeclaration) { + const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures(); + if (!typeChecker.isImplementationOfOverload(functionDeclaration)) { + signature = typeChecker.getSignatureFromDeclaration(functionDeclaration); + } + else { + signature = allSignatures[0]; + } - addSignatureDisplayParts(signature, allSignatures); - hasAddedSymbolInfo = true; + if (functionDeclaration.kind === SyntaxKind.Constructor) { + // show (constructor) Type(...) signature + symbolKind = ScriptElementKind.constructorImplementationElement; + addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); + } + else { + // (function/method) symbol(..signature) + addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature && + !(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind); + } + + addSignatureDisplayParts(signature, allSignatures); + hasAddedSymbolInfo = true; + } } } } diff --git a/tests/cases/fourslash/completionEntryForClassMembers.ts b/tests/cases/fourslash/completionEntryForClassMembers.ts index da1051e784a..c52977a0002 100644 --- a/tests/cases/fourslash/completionEntryForClassMembers.ts +++ b/tests/cases/fourslash/completionEntryForClassMembers.ts @@ -1,6 +1,9 @@ /// ////abstract class B { +//// private privateMethod() { } +//// protected protectedMethod() { }; +//// static staticMethod() { } //// abstract getValue(): number; //// /*abstractClass*/ ////} @@ -69,25 +72,7 @@ //// static identi/*classThatStartedWritingIdentifierAfterStaticModifier*/ ////} -const allowedKeywords = [ - "public", - "private", - "protected", - "static", - "abstract", - "readonly", - "get", - "set", - "constructor" -]; - -const allowedKeywordCount = allowedKeywords.length; -function verifyAllowedKeyWords() { - for (const keyword of allowedKeywords) { - verify.completionListContains(keyword, keyword, /*documentation*/ undefined, "keyword"); - } -} - +const allowedKeywordCount = verify.allowedClassElementKeywords.length; const nonClassElementMarkers = [ "InsideMethod" ]; @@ -104,7 +89,7 @@ const onlyClassElementKeywordLocations = [ ]; for (const marker of onlyClassElementKeywordLocations) { goTo.marker(marker); - verifyAllowedKeyWords(); + verify.completionListContainsClassElementKeywords(); verify.completionListCount(allowedKeywordCount); } @@ -113,9 +98,8 @@ const classElementCompletionLocations = [ "classThatIsEmptyAndExtendingAnotherClass", "classThatHasAlreadyImplementedAnotherClassMethod", "classThatHasAlreadyImplementedAnotherClassMethodAfterMethod", - // TODO should we give completion for these keywords - //"classThatHasWrittenPublicKeyword", - //"classElementContainingStatic", + "classThatHasWrittenPublicKeyword", + "classElementContainingStatic", "classThatStartedWritingIdentifier", "propDeclarationWithoutSemicolon", "propDeclarationWithSemicolon", @@ -126,17 +110,30 @@ const classElementCompletionLocations = [ "methodImplementation", "accessorSignatureWithoutSemicolon", "accessorSignatureImplementation", - // TODO should we give completion for these keywords - //"classThatHasWrittenGetKeyword", - //"classThatHasWrittenSetKeyword", - //"classThatStartedWritingIdentifierOfGetAccessor", - //"classThatStartedWritingIdentifierOfSetAccessor", - //"classThatStartedWritingIdentifierAfterModifier", - //"classThatStartedWritingIdentifierAfterStaticModifier" + "classThatHasWrittenGetKeyword", + "classThatHasWrittenSetKeyword", + "classThatStartedWritingIdentifierOfGetAccessor", + "classThatStartedWritingIdentifierOfSetAccessor", + "classThatStartedWritingIdentifierAfterModifier", + "classThatStartedWritingIdentifierAfterStaticModifier" +]; + +const validMembersOfBase = [ + ["getValue", "(method) B.getValue(): number"], + ["protectedMethod", "(method) B.protectedMethod(): void"] +]; +const invalidMembersOfBase = [ + ["privateMethod", "(method) B.privateMethod(): void"], + ["staticMethod", "(method) B.staticMethod(): void"] ]; for (const marker of classElementCompletionLocations) { goTo.marker(marker); - verify.completionListContains("getValue", "(method) B.getValue(): number", /*documentation*/ undefined, "method"); - verifyAllowedKeyWords(); - verify.completionListCount(allowedKeywordCount + 1); + for (const [validMemberOfBaseSymbol, validMemberOfBaseText] of validMembersOfBase) { + verify.completionListContains(validMemberOfBaseSymbol, validMemberOfBaseText, /*documentation*/ undefined, "method"); + } + for (const [invalidMemberOfBaseSymbol, invalidMemberOfBaseText] of invalidMembersOfBase) { + verify.not.completionListContains(invalidMemberOfBaseSymbol, invalidMemberOfBaseText, /*documentation*/ undefined, "method"); + } + verify.completionListContainsClassElementKeywords(); + verify.completionListCount(allowedKeywordCount + validMembersOfBase.length); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionListBuilderLocations_properties.ts b/tests/cases/fourslash/completionListBuilderLocations_properties.ts index 806d8c1de4f..2cdc3b7e7ba 100644 --- a/tests/cases/fourslash/completionListBuilderLocations_properties.ts +++ b/tests/cases/fourslash/completionListBuilderLocations_properties.ts @@ -10,4 +10,4 @@ //// public static a/*property2*/ ////} -goTo.eachMarker(() => verify.completionListIsEmpty()); +goTo.eachMarker(() => verify.completionListContainsClassElementKeywords()); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 374dfd33c7d..31d8b8b4ef2 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -133,11 +133,13 @@ declare namespace FourSlashInterface { class verifyNegatable { private negative; not: verifyNegatable; + allowedClassElementKeywords: string[]; constructor(negative?: boolean); completionListCount(expectedCount: number): void; completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number): void; completionListItemsCountIsGreaterThan(count: number): void; completionListIsEmpty(): void; + completionListContainsClassElementKeywords(): void; completionListAllowsNewIdentifier(): void; signatureHelpPresent(): void; errorExistsBetweenMarkers(startMarker: string, endMarker: string): void; diff --git a/tests/cases/fourslash/referencesForClassMembersExtendingGenericClass.ts b/tests/cases/fourslash/referencesForClassMembersExtendingGenericClass.ts index 3e453663f63..448c2627531 100644 --- a/tests/cases/fourslash/referencesForClassMembersExtendingGenericClass.ts +++ b/tests/cases/fourslash/referencesForClassMembersExtendingGenericClass.ts @@ -26,7 +26,7 @@ const methods = ranges.get("method"); const [m0, m1, m2] = methods; verify.referenceGroups(m0, [{ definition: "(method) Base.method(a?: T, b?: U): this", ranges: methods }]); verify.referenceGroups(m1, [ - { definition: "(method) Base.method(): void", ranges: [m0] }, + { definition: "(method) Base.method(a?: T, b?: U): this", ranges: [m0] }, { definition: "(method) MyClass.method(): void", ranges: [m1, m2] } ]); verify.referenceGroups(m2, [ From fcb0f4617812dae7fdf2c7dd97a508e2535e3c3f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 4 May 2017 17:44:34 -0700 Subject: [PATCH 04/34] Tune the completion list for static and private modifiers Do not show inherited members in completion for when writing private member Show only static inherited members when writing static member --- src/services/completions.ts | 39 +++++-- .../completionEntryForClassMembers.ts | 107 ++++++++++++------ 2 files changed, 107 insertions(+), 39 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index da59dab8828..cb4abb89b58 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -939,10 +939,31 @@ namespace ts.Completions { const baseTypeNode = getClassExtendsHeritageClauseElement(classLikeDeclaration); if (baseTypeNode) { - const baseType = typeChecker.getTypeAtLocation(baseTypeNode); - // List of property symbols of base type that are not private - symbols = filter(typeChecker.getPropertiesOfType(baseType), - baseProperty => !(getDeclarationModifierFlagsFromSymbol(baseProperty) & ModifierFlags.Private)); + const classElement = contextToken.parent; + let classElementModifierFlags = isClassElement(classElement) && getModifierFlags(classElement); + // If this is context token is not something we are editing now, consider if this would lead to be modifier + if (contextToken.kind === SyntaxKind.Identifier && !isCurrentlyEditingNode(contextToken)) { + switch (contextToken.getText()) { + case "private": + classElementModifierFlags = classElementModifierFlags | ModifierFlags.Private; + break; + case "static": + classElementModifierFlags = classElementModifierFlags | ModifierFlags.Static; + break; + } + } + + // No member list for private methods + if (!(classElementModifierFlags & ModifierFlags.Private)) { + const baseType = typeChecker.getTypeAtLocation(baseTypeNode); + const typeToGetPropertiesFrom = (classElementModifierFlags & ModifierFlags.Static) ? + typeChecker.getTypeOfSymbolAtLocation(baseType.symbol, classLikeDeclaration) : + baseType; + + // List of property symbols of base type that are not private + symbols = filter(typeChecker.getPropertiesOfType(typeToGetPropertiesFrom), + baseProperty => baseProperty.getDeclarations() && !(getDeclarationModifierFlagsFromSymbol(baseProperty) & ModifierFlags.Private)); + } } return true; @@ -1248,7 +1269,7 @@ namespace ts.Completions { for (const element of namedImportsOrExports) { // If this is the current item we are editing right now, do not filter it out - if (element.getStart() <= position && position <= element.getEnd()) { + if (isCurrentlyEditingNode(element)) { continue; } @@ -1287,7 +1308,7 @@ namespace ts.Completions { } // If this is the current item we are editing right now, do not filter it out - if (m.getStart() <= position && position <= m.getEnd()) { + if (isCurrentlyEditingNode(m)) { continue; } @@ -1322,7 +1343,7 @@ namespace ts.Completions { const seenNames = createMap(); for (const attr of attributes) { // If this is the current item we are editing right now, do not filter it out - if (attr.getStart() <= position && position <= attr.getEnd()) { + if (isCurrentlyEditingNode(attr)) { continue; } @@ -1333,6 +1354,10 @@ namespace ts.Completions { return filter(symbols, a => !seenNames.get(a.name)); } + + function isCurrentlyEditingNode(node: Node): boolean { + return node.getStart() <= position && position <= node.getEnd(); + } } /** diff --git a/tests/cases/fourslash/completionEntryForClassMembers.ts b/tests/cases/fourslash/completionEntryForClassMembers.ts index c52977a0002..672046f62a7 100644 --- a/tests/cases/fourslash/completionEntryForClassMembers.ts +++ b/tests/cases/fourslash/completionEntryForClassMembers.ts @@ -23,12 +23,19 @@ ////class F extends B { //// public /*classThatHasWrittenPublicKeyword*/ ////} +////class F2 extends B { +//// private /*classThatHasWrittenPrivateKeyword*/ +////} ////class G extends B { //// static /*classElementContainingStatic*/ ////} +////class G2 extends B { +//// private static /*classElementContainingPrivateStatic*/ +////} ////class H extends B { //// prop/*classThatStartedWritingIdentifier*/ ////} +//////Class for location verification ////class I extends B { //// prop0: number //// /*propDeclarationWithoutSemicolon*/ @@ -68,38 +75,87 @@ ////class L extends B { //// public identi/*classThatStartedWritingIdentifierAfterModifier*/ ////} -////class L extends B { +////class L2 extends B { +//// private identi/*classThatStartedWritingIdentifierAfterPrivateModifier*/ +////} +////class M extends B { //// static identi/*classThatStartedWritingIdentifierAfterStaticModifier*/ ////} +////class M extends B { +//// private static identi/*classThatStartedWritingIdentifierAfterPrivateStaticModifier*/ +////} const allowedKeywordCount = verify.allowedClassElementKeywords.length; +type CompletionInfo = [string, string]; +type CompletionInfoVerifier = { validMembers: CompletionInfo[], invalidMembers: CompletionInfo[] }; + +function verifyClassElementLocations({ validMembers, invalidMembers }: CompletionInfoVerifier, classElementCompletionLocations: string[]) { + for (const marker of classElementCompletionLocations) { + goTo.marker(marker); + verifyCompletionInfo(validMembers, verify); + verifyCompletionInfo(invalidMembers, verify.not); + verify.completionListContainsClassElementKeywords(); + verify.completionListCount(allowedKeywordCount + validMembers.length); + } +} + +function verifyCompletionInfo(memberInfo: CompletionInfo[], verify: FourSlashInterface.verifyNegatable) { + for (const [symbol, text] of memberInfo) { + verify.completionListContains(symbol, text, /*documentation*/ undefined, "method"); + } +} + +const allMembersOfBase: CompletionInfo[] = [ + ["getValue", "(method) B.getValue(): number"], + ["protectedMethod", "(method) B.protectedMethod(): void"], + ["privateMethod", "(method) B.privateMethod(): void"], + ["staticMethod", "(method) B.staticMethod(): void"] +]; +function filterCompletionInfo(fn: (a: CompletionInfo) => boolean): CompletionInfoVerifier { + const validMembers: CompletionInfo[] = []; + const invalidMembers: CompletionInfo[] = []; + for (const member of allMembersOfBase) { + if (fn(member)) { + validMembers.push(member); + } + else { + invalidMembers.push(member); + } + } + return { validMembers, invalidMembers }; +} + + +const instanceMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "getValue" || a === "protectedMethod"); +const staticMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "staticMethod"); + +// Not a class element declaration location const nonClassElementMarkers = [ "InsideMethod" ]; for (const marker of nonClassElementMarkers) { goTo.marker(marker); - verify.not.completionListContains("getValue"); + verifyCompletionInfo(allMembersOfBase, verify.not); verify.not.completionListIsEmpty(); } -// Only keywords allowed at this position since they dont extend the class +// Only keywords allowed at this position since they dont extend the class or are private const onlyClassElementKeywordLocations = [ "abstractClass", - "classThatDoesNotExtendAnotherClass" + "classThatDoesNotExtendAnotherClass", + "classThatHasWrittenPrivateKeyword", + "classElementContainingPrivateStatic", + "classThatStartedWritingIdentifierAfterPrivateModifier", + "classThatStartedWritingIdentifierAfterPrivateStaticModifier" ]; -for (const marker of onlyClassElementKeywordLocations) { - goTo.marker(marker); - verify.completionListContainsClassElementKeywords(); - verify.completionListCount(allowedKeywordCount); -} +verifyClassElementLocations({ validMembers: [], invalidMembers: allMembersOfBase }, onlyClassElementKeywordLocations); -// Base members and class member keywords allowed -const classElementCompletionLocations = [ +// Instance base members and class member keywords allowed +const classInstanceElementLocations = [ "classThatIsEmptyAndExtendingAnotherClass", "classThatHasAlreadyImplementedAnotherClassMethod", "classThatHasAlreadyImplementedAnotherClassMethodAfterMethod", "classThatHasWrittenPublicKeyword", - "classElementContainingStatic", "classThatStartedWritingIdentifier", "propDeclarationWithoutSemicolon", "propDeclarationWithSemicolon", @@ -115,25 +171,12 @@ const classElementCompletionLocations = [ "classThatStartedWritingIdentifierOfGetAccessor", "classThatStartedWritingIdentifierOfSetAccessor", "classThatStartedWritingIdentifierAfterModifier", +]; +verifyClassElementLocations(instanceMemberInfo, classInstanceElementLocations); + +// Static Base members and class member keywords allowed +const staticClassLocations = [ + "classElementContainingStatic", "classThatStartedWritingIdentifierAfterStaticModifier" ]; - -const validMembersOfBase = [ - ["getValue", "(method) B.getValue(): number"], - ["protectedMethod", "(method) B.protectedMethod(): void"] -]; -const invalidMembersOfBase = [ - ["privateMethod", "(method) B.privateMethod(): void"], - ["staticMethod", "(method) B.staticMethod(): void"] -]; -for (const marker of classElementCompletionLocations) { - goTo.marker(marker); - for (const [validMemberOfBaseSymbol, validMemberOfBaseText] of validMembersOfBase) { - verify.completionListContains(validMemberOfBaseSymbol, validMemberOfBaseText, /*documentation*/ undefined, "method"); - } - for (const [invalidMemberOfBaseSymbol, invalidMemberOfBaseText] of invalidMembersOfBase) { - verify.not.completionListContains(invalidMemberOfBaseSymbol, invalidMemberOfBaseText, /*documentation*/ undefined, "method"); - } - verify.completionListContainsClassElementKeywords(); - verify.completionListCount(allowedKeywordCount + validMembersOfBase.length); -} \ No newline at end of file +verifyClassElementLocations(staticMemberInfo, staticClassLocations); \ No newline at end of file From f05a85d56fe1a627cef508773ce04e6d52b50569 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 10 May 2017 15:49:40 -0700 Subject: [PATCH 05/34] Fix bug for goto-definition on a TSX constructor using an alias declaration --- src/compiler/checker.ts | 2 +- src/compiler/types.ts | 2 +- src/services/goToDefinition.ts | 23 +++---------------- .../tsxGoToDefinitionClassInDifferentFile.ts | 15 ++++++++++++ 4 files changed, 20 insertions(+), 22 deletions(-) create mode 100644 tests/cases/fourslash/tsxGoToDefinitionClassInDifferentFile.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dfccfca1b8f..97bebeadfb3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12755,7 +12755,7 @@ namespace ts { * @param node the expression whose contextual type will be returned. * @returns the contextual type of an expression. */ - function getContextualType(node: Expression): Type { + function getContextualType(node: Expression): Type | undefined { if (isInsideWithStatementBody(node)) { // We cannot answer semantic questions within a with block, do not proceed any further return undefined; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 74670352c34..d89c7877da6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2528,7 +2528,7 @@ namespace ts { getFullyQualifiedName(symbol: Symbol): string; getAugmentedPropertiesOfType(type: Type): Symbol[]; getRootSymbols(symbol: Symbol): Symbol[]; - getContextualType(node: Expression): Type; + getContextualType(node: Expression): Type | undefined; getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature; getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature; isImplementationOfOverload(node: FunctionLikeDeclaration): boolean; diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 09d1f2a248c..07b1e4448bd 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -85,17 +85,6 @@ namespace ts.GoToDefinition { declaration => createDefinitionInfo(declaration, shorthandSymbolKind, shorthandSymbolName, shorthandContainerName)); } - if (isJsxOpeningLikeElement(node.parent)) { - // If there are errors when trying to figure out stateless component function, just return the first declaration - // For example: - // declare function /*firstSource*/MainButton(buttonProps: ButtonProps): JSX.Element; - // declare function /*secondSource*/MainButton(linkProps: LinkProps): JSX.Element; - // declare function /*thirdSource*/MainButton(props: ButtonProps | LinkProps): JSX.Element; - // let opt =
; // Error - We get undefined for resolved signature indicating an error, then just return the first declaration - const {symbolName, symbolKind, containerName} = getSymbolInfo(typeChecker, symbol, node); - return [createDefinitionInfo(symbol.valueDeclaration, symbolKind, symbolName, containerName)]; - } - // If the current location we want to find its definition is in an object literal, try to get the contextual type for the // object literal, lookup the property symbol in the contextual type, and use this for goto-definition. // For example @@ -106,15 +95,9 @@ namespace ts.GoToDefinition { // function Foo(arg: Props) {} // Foo( { pr/*1*/op1: 10, prop2: true }) const element = getContainingObjectLiteralElement(node); - if (element) { - if (typeChecker.getContextualType(element.parent as Expression)) { - const result: DefinitionInfo[] = []; - const propertySymbols = getPropertySymbolsFromContextualType(typeChecker, element); - for (const propertySymbol of propertySymbols) { - result.push(...getDefinitionFromSymbol(typeChecker, propertySymbol, node)); - } - return result; - } + if (element && typeChecker.getContextualType(element.parent as Expression)) { + return flatMap(getPropertySymbolsFromContextualType(typeChecker, element), propertySymbol => + getDefinitionFromSymbol(typeChecker, propertySymbol, node)); } return getDefinitionFromSymbol(typeChecker, symbol, node); } diff --git a/tests/cases/fourslash/tsxGoToDefinitionClassInDifferentFile.ts b/tests/cases/fourslash/tsxGoToDefinitionClassInDifferentFile.ts new file mode 100644 index 00000000000..f217e4096fc --- /dev/null +++ b/tests/cases/fourslash/tsxGoToDefinitionClassInDifferentFile.ts @@ -0,0 +1,15 @@ +/// + +// @jsx: preserve + +// @Filename: C.tsx +////export default class C {} + +// @Filename: a.tsx +////import /*def*/C from "./C"; +////const foo = ; + +verify.noErrors(); + +goTo.marker("use"); +verify.goToDefinitionIs("def"); From 578826cd613bc6fb3a18e38d0758f05a8b0d0f3b Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 10 May 2017 16:41:30 -0700 Subject: [PATCH 06/34] Adds definitions for the es2017 Atomics global object --- src/lib/es2017.sharedmemory.d.ts | 93 +++++++++++++++++++++++++++++++- src/lib/es5.d.ts | 48 ++++++++++------- 2 files changed, 120 insertions(+), 21 deletions(-) diff --git a/src/lib/es2017.sharedmemory.d.ts b/src/lib/es2017.sharedmemory.d.ts index e18390a5591..b9a9b0f7e10 100644 --- a/src/lib/es2017.sharedmemory.d.ts +++ b/src/lib/es2017.sharedmemory.d.ts @@ -23,5 +23,96 @@ interface SharedArrayBufferConstructor { readonly prototype: SharedArrayBuffer; new (byteLength: number): SharedArrayBuffer; } +declare var SharedArrayBuffer: SharedArrayBufferConstructor; -declare var SharedArrayBuffer: SharedArrayBufferConstructor; \ No newline at end of file +interface ArrayBufferTypes { + SharedArrayBuffer: SharedArrayBuffer; +} + +interface Atomics { + /** + * Adds a value to the value at the given position in the array, returning the original value. + * Until this atomic operation completes, any other read or write operation against the array + * will block. + */ + add(typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, index: number, value: number): number; + + /** + * Stores the bitwise AND of a value with the value at the given position in the array, + * returning the original value. Until this atomic operation completes, any other read or + * write operation against the array will block. + */ + and(typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, index: number, value: number): number; + + /** + * Replaces the value at the given position in the array if the original value equals the given + * expected value, returning the original value. Until this atomic operation completes, any + * other read or write operation against the array will block. + */ + compareExchange(typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, index: number, expectedValue: number, replacementValue: number): number; + + /** + * Replaces the value at the given position in the array, returning the original value. Until + * this atomic operation completes, any other read or write operation against the array will + * block. + */ + exchange(typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, index: number, value: number): number; + + /** + * Returns a value indicating whether high-performance algorithms can use atomic operations + * (`true`) or must use locks (`false`) for the given number of bytes-per-element of a typed + * array. + */ + isLockFree(size: number): boolean; + + /** + * Returns the value at the given position in the array. Until this atomic operation completes, + * any other read or write operation against the array will block. + */ + load(typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, index: number): number; + + /** + * Stores the bitwise OR of a value with the value at the given position in the array, + * returning the original value. Until this atomic operation completes, any other read or write + * operation against the array will block. + */ + or(typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, index: number, value: number): number; + + /** + * Stores a value at the given position in the array, returning the new value. Until this + * atomic operation completes, any other read or write operation against the array will block. + */ + store(typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, index: number, value: number): number; + + /** + * Subtracts a value from the value at the given position in the array, returning the original + * value. Until this atomic operation completes, any other read or write operation against the + * array will block. + */ + sub(typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, index: number, value: number): number; + + /** + * If the value at the given position in the array is equal to the provided value, the current + * agent is put to sleep causing execution to suspend until the timeout expires (returning + * `"timed-out"`) or until the agent is awoken (returning `"ok"`); otherwise, returns + * `"not-equal"`. + */ + wait(typedArray: Int32Array, index: number, value: number, timeout?: number): "ok" | "not-equal" | "timed-out"; + + /** + * Wakes up sleeping agents that are waiting on the given index of the array, returning the + * number of agents that were awoken. + */ + wake(typedArray: Int32Array, index: number, count: number): number; + + /** + * Stores the bitwise XOR of a value with the value at the given position in the array, + * returning the original value. Until this atomic operation completes, any other read or write + * operation against the array will block. + */ + xor(typedArray: Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array, index: number, value: number): number; + + readonly [Symbol.toStringTag]: "Atomics"; +} + +declare var Atomics: Atomics; \ No newline at end of file diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index 23805495b69..3a85f4ddad9 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1386,6 +1386,14 @@ interface ArrayBuffer { slice(begin: number, end?: number): ArrayBuffer; } +/** + * Allowed ArrayBuffer types for the buffer of an ArrayBufferView and related Typed Arrays. + */ +interface ArrayBufferTypes { + ArrayBuffer: ArrayBuffer; +} +type ArrayBufferLike = ArrayBufferTypes[keyof ArrayBufferTypes]; + interface ArrayBufferConstructor { readonly prototype: ArrayBuffer; new (byteLength: number): ArrayBuffer; @@ -1397,7 +1405,7 @@ interface ArrayBufferView { /** * The ArrayBuffer instance referenced by the array. */ - buffer: ArrayBuffer; + buffer: ArrayBufferLike; /** * The length in bytes of the array. @@ -1539,7 +1547,7 @@ interface DataView { } interface DataViewConstructor { - new (buffer: ArrayBuffer, byteOffset?: number, byteLength?: number): DataView; + new (buffer: ArrayBufferLike, byteOffset?: number, byteLength?: number): DataView; } declare const DataView: DataViewConstructor; @@ -1556,7 +1564,7 @@ interface Int8Array { /** * The ArrayBuffer instance referenced by the array. */ - readonly buffer: ArrayBuffer; + readonly buffer: ArrayBufferLike; /** * The length in bytes of the array. @@ -1799,7 +1807,7 @@ interface Int8ArrayConstructor { readonly prototype: Int8Array; new (length: number): Int8Array; new (array: ArrayLike): Int8Array; - new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int8Array; + new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): Int8Array; /** * The size in bytes of each element in the array. @@ -1840,7 +1848,7 @@ interface Uint8Array { /** * The ArrayBuffer instance referenced by the array. */ - readonly buffer: ArrayBuffer; + readonly buffer: ArrayBufferLike; /** * The length in bytes of the array. @@ -2084,7 +2092,7 @@ interface Uint8ArrayConstructor { readonly prototype: Uint8Array; new (length: number): Uint8Array; new (array: ArrayLike): Uint8Array; - new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint8Array; + new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): Uint8Array; /** * The size in bytes of each element in the array. @@ -2125,7 +2133,7 @@ interface Uint8ClampedArray { /** * The ArrayBuffer instance referenced by the array. */ - readonly buffer: ArrayBuffer; + readonly buffer: ArrayBufferLike; /** * The length in bytes of the array. @@ -2369,7 +2377,7 @@ interface Uint8ClampedArrayConstructor { readonly prototype: Uint8ClampedArray; new (length: number): Uint8ClampedArray; new (array: ArrayLike): Uint8ClampedArray; - new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint8ClampedArray; + new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): Uint8ClampedArray; /** * The size in bytes of each element in the array. @@ -2409,7 +2417,7 @@ interface Int16Array { /** * The ArrayBuffer instance referenced by the array. */ - readonly buffer: ArrayBuffer; + readonly buffer: ArrayBufferLike; /** * The length in bytes of the array. @@ -2653,7 +2661,7 @@ interface Int16ArrayConstructor { readonly prototype: Int16Array; new (length: number): Int16Array; new (array: ArrayLike): Int16Array; - new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int16Array; + new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): Int16Array; /** * The size in bytes of each element in the array. @@ -2694,7 +2702,7 @@ interface Uint16Array { /** * The ArrayBuffer instance referenced by the array. */ - readonly buffer: ArrayBuffer; + readonly buffer: ArrayBufferLike; /** * The length in bytes of the array. @@ -2938,7 +2946,7 @@ interface Uint16ArrayConstructor { readonly prototype: Uint16Array; new (length: number): Uint16Array; new (array: ArrayLike): Uint16Array; - new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint16Array; + new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): Uint16Array; /** * The size in bytes of each element in the array. @@ -2978,7 +2986,7 @@ interface Int32Array { /** * The ArrayBuffer instance referenced by the array. */ - readonly buffer: ArrayBuffer; + readonly buffer: ArrayBufferLike; /** * The length in bytes of the array. @@ -3222,7 +3230,7 @@ interface Int32ArrayConstructor { readonly prototype: Int32Array; new (length: number): Int32Array; new (array: ArrayLike): Int32Array; - new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int32Array; + new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): Int32Array; /** * The size in bytes of each element in the array. @@ -3262,7 +3270,7 @@ interface Uint32Array { /** * The ArrayBuffer instance referenced by the array. */ - readonly buffer: ArrayBuffer; + readonly buffer: ArrayBufferLike; /** * The length in bytes of the array. @@ -3506,7 +3514,7 @@ interface Uint32ArrayConstructor { readonly prototype: Uint32Array; new (length: number): Uint32Array; new (array: ArrayLike): Uint32Array; - new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint32Array; + new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): Uint32Array; /** * The size in bytes of each element in the array. @@ -3546,7 +3554,7 @@ interface Float32Array { /** * The ArrayBuffer instance referenced by the array. */ - readonly buffer: ArrayBuffer; + readonly buffer: ArrayBufferLike; /** * The length in bytes of the array. @@ -3790,7 +3798,7 @@ interface Float32ArrayConstructor { readonly prototype: Float32Array; new (length: number): Float32Array; new (array: ArrayLike): Float32Array; - new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Float32Array; + new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): Float32Array; /** * The size in bytes of each element in the array. @@ -3831,7 +3839,7 @@ interface Float64Array { /** * The ArrayBuffer instance referenced by the array. */ - readonly buffer: ArrayBuffer; + readonly buffer: ArrayBufferLike; /** * The length in bytes of the array. @@ -4075,7 +4083,7 @@ interface Float64ArrayConstructor { readonly prototype: Float64Array; new (length: number): Float64Array; new (array: ArrayLike): Float64Array; - new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Float64Array; + new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): Float64Array; /** * The size in bytes of each element in the array. From 3c8732fab1cdc524c1cf3a506bda871b09842199 Mon Sep 17 00:00:00 2001 From: Charles Pierce Date: Wed, 10 May 2017 17:26:47 -0700 Subject: [PATCH 07/34] Prevent Duplicate String Literal Completions --- src/services/completions.ts | 23 +++++++++++-------- .../fourslash/completionForStringLiteral12.ts | 10 ++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteral12.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 318c02683ff..948d7349419 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -221,11 +221,12 @@ namespace ts.Completions { function getStringLiteralCompletionEntriesFromCallExpression(argumentInfo: SignatureHelp.ArgumentListInfo, typeChecker: TypeChecker): CompletionInfo | undefined { const candidates: Signature[] = []; const entries: CompletionEntry[] = []; + const uniques = createMap(); typeChecker.getResolvedSignature(argumentInfo.invocation, candidates); for (const candidate of candidates) { - addStringLiteralCompletionsFromType(typeChecker.getParameterType(candidate, argumentInfo.argumentIndex), entries, typeChecker); + addStringLiteralCompletionsFromType(typeChecker.getParameterType(candidate, argumentInfo.argumentIndex), entries, typeChecker, uniques); } if (entries.length) { @@ -258,7 +259,7 @@ namespace ts.Completions { return undefined; } - function addStringLiteralCompletionsFromType(type: Type, result: Push, typeChecker: TypeChecker): void { + function addStringLiteralCompletionsFromType(type: Type, result: Push, typeChecker: TypeChecker, uniques = createMap()): void { if (type && type.flags & TypeFlags.TypeParameter) { type = typeChecker.getBaseConstraintOfType(type); } @@ -267,16 +268,20 @@ namespace ts.Completions { } if (type.flags & TypeFlags.Union) { for (const t of (type).types) { - addStringLiteralCompletionsFromType(t, result, typeChecker); + addStringLiteralCompletionsFromType(t, result, typeChecker, uniques); } } else if (type.flags & TypeFlags.StringLiteral) { - result.push({ - name: (type).text, - kindModifiers: ScriptElementKindModifier.none, - kind: ScriptElementKind.variableElement, - sortText: "0" - }); + const name = (type).text; + if (!uniques.get(name)) { + uniques.set(name, name); + result.push({ + name, + kindModifiers: ScriptElementKindModifier.none, + kind: ScriptElementKind.variableElement, + sortText: "0" + }); + } } } diff --git a/tests/cases/fourslash/completionForStringLiteral12.ts b/tests/cases/fourslash/completionForStringLiteral12.ts new file mode 100644 index 00000000000..22bb2f00a43 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteral12.ts @@ -0,0 +1,10 @@ +/// + +////function foo(x: "bla"): void; +////function foo(x: "bla"): void; +////function foo(x: string) {} +////foo("/**/") + +goTo.marker(); +verify.completionListContains("bla"); +verify.completionListCount(1); From 5c5b5e9a3b3b22dd26ee08d9d670ef826dc13454 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 11 May 2017 09:06:01 -0700 Subject: [PATCH 08/34] Fail on bad moduleResolution kind --- src/compiler/moduleNameResolver.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index f984c12fb68..acf01bac905 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -442,6 +442,8 @@ namespace ts { case ModuleResolutionKind.Classic: result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache); break; + default: + Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`); } if (perFolderCache) { From cff19ab3d3fc2cb8d405a9c435b364b5b81985c8 Mon Sep 17 00:00:00 2001 From: Charles Pierce Date: Thu, 11 May 2017 10:42:25 -0700 Subject: [PATCH 09/34] Switch map to be used as a set rather than a map --- src/services/completions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 948d7349419..e850bfb4496 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -221,7 +221,7 @@ namespace ts.Completions { function getStringLiteralCompletionEntriesFromCallExpression(argumentInfo: SignatureHelp.ArgumentListInfo, typeChecker: TypeChecker): CompletionInfo | undefined { const candidates: Signature[] = []; const entries: CompletionEntry[] = []; - const uniques = createMap(); + const uniques = createMap(); typeChecker.getResolvedSignature(argumentInfo.invocation, candidates); @@ -259,7 +259,7 @@ namespace ts.Completions { return undefined; } - function addStringLiteralCompletionsFromType(type: Type, result: Push, typeChecker: TypeChecker, uniques = createMap()): void { + function addStringLiteralCompletionsFromType(type: Type, result: Push, typeChecker: TypeChecker, uniques = createMap()): void { if (type && type.flags & TypeFlags.TypeParameter) { type = typeChecker.getBaseConstraintOfType(type); } @@ -273,8 +273,8 @@ namespace ts.Completions { } else if (type.flags & TypeFlags.StringLiteral) { const name = (type).text; - if (!uniques.get(name)) { - uniques.set(name, name); + if (!uniques.has(name)) { + uniques.set(name, true); result.push({ name, kindModifiers: ScriptElementKindModifier.none, From e76c96ce547e670789da978cdcb64e7c72c6cb27 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 11 May 2017 11:37:28 -0700 Subject: [PATCH 10/34] Allow references to ambient block scoped variables Even when 1. --out or --outfile is specified 2. The block scoped variable's file is after the referrer's file. --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 861279b2225..82c3eaaf04b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -737,7 +737,8 @@ namespace ts { const useFile = getSourceFileOfNode(usage); if (declarationFile !== useFile) { if ((modulekind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || - (!compilerOptions.outFile && !compilerOptions.out)) { + (!compilerOptions.outFile && !compilerOptions.out) || + isInAmbientContext(declaration)) { // nodes are in different files and order cannot be determined return true; } From 7803c1ce17ec8a862c3d3c207587fd72eb1d3d94 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 11 May 2017 11:38:26 -0700 Subject: [PATCH 11/34] Test:reference ambient block scoped vars in different file --- .../blockScopedNamespaceDifferentFile.js | 35 ++++++++++++++ .../blockScopedNamespaceDifferentFile.symbols | 44 ++++++++++++++++++ .../blockScopedNamespaceDifferentFile.types | 46 +++++++++++++++++++ .../blockScopedNamespaceDifferentFile.ts | 22 +++++++++ 4 files changed, 147 insertions(+) create mode 100644 tests/baselines/reference/blockScopedNamespaceDifferentFile.js create mode 100644 tests/baselines/reference/blockScopedNamespaceDifferentFile.symbols create mode 100644 tests/baselines/reference/blockScopedNamespaceDifferentFile.types create mode 100644 tests/cases/compiler/blockScopedNamespaceDifferentFile.ts diff --git a/tests/baselines/reference/blockScopedNamespaceDifferentFile.js b/tests/baselines/reference/blockScopedNamespaceDifferentFile.js new file mode 100644 index 00000000000..3eab7477c09 --- /dev/null +++ b/tests/baselines/reference/blockScopedNamespaceDifferentFile.js @@ -0,0 +1,35 @@ +//// [tests/cases/compiler/blockScopedNamespaceDifferentFile.ts] //// + +//// [test.ts] +// #15734 failed when test.ts comes before typings.d.ts +namespace C { + export class Name { + static funcData = A.AA.func(); + static someConst = A.AA.foo; + + constructor(parameters) {} + } +} + +//// [typings.d.ts] +declare namespace A { + namespace AA { + function func(): number; + const foo = ""; + } +} + + +//// [out.js] +// #15734 failed when test.ts comes before typings.d.ts +var C; +(function (C) { + var Name = (function () { + function Name(parameters) { + } + return Name; + }()); + Name.funcData = A.AA.func(); + Name.someConst = A.AA.foo; + C.Name = Name; +})(C || (C = {})); diff --git a/tests/baselines/reference/blockScopedNamespaceDifferentFile.symbols b/tests/baselines/reference/blockScopedNamespaceDifferentFile.symbols new file mode 100644 index 00000000000..b7192d0e12b --- /dev/null +++ b/tests/baselines/reference/blockScopedNamespaceDifferentFile.symbols @@ -0,0 +1,44 @@ +=== tests/cases/compiler/test.ts === +// #15734 failed when test.ts comes before typings.d.ts +namespace C { +>C : Symbol(C, Decl(test.ts, 0, 0)) + + export class Name { +>Name : Symbol(Name, Decl(test.ts, 1, 13)) + + static funcData = A.AA.func(); +>funcData : Symbol(Name.funcData, Decl(test.ts, 2, 23)) +>A.AA.func : Symbol(A.AA.func, Decl(typings.d.ts, 1, 18)) +>A.AA : Symbol(A.AA, Decl(typings.d.ts, 0, 21)) +>A : Symbol(A, Decl(typings.d.ts, 0, 0)) +>AA : Symbol(A.AA, Decl(typings.d.ts, 0, 21)) +>func : Symbol(A.AA.func, Decl(typings.d.ts, 1, 18)) + + static someConst = A.AA.foo; +>someConst : Symbol(Name.someConst, Decl(test.ts, 3, 38)) +>A.AA.foo : Symbol(A.AA.foo, Decl(typings.d.ts, 3, 13)) +>A.AA : Symbol(A.AA, Decl(typings.d.ts, 0, 21)) +>A : Symbol(A, Decl(typings.d.ts, 0, 0)) +>AA : Symbol(A.AA, Decl(typings.d.ts, 0, 21)) +>foo : Symbol(A.AA.foo, Decl(typings.d.ts, 3, 13)) + + constructor(parameters) {} +>parameters : Symbol(parameters, Decl(test.ts, 6, 20)) + } +} + +=== tests/cases/compiler/typings.d.ts === +declare namespace A { +>A : Symbol(A, Decl(typings.d.ts, 0, 0)) + + namespace AA { +>AA : Symbol(AA, Decl(typings.d.ts, 0, 21)) + + function func(): number; +>func : Symbol(func, Decl(typings.d.ts, 1, 18)) + + const foo = ""; +>foo : Symbol(foo, Decl(typings.d.ts, 3, 13)) + } +} + diff --git a/tests/baselines/reference/blockScopedNamespaceDifferentFile.types b/tests/baselines/reference/blockScopedNamespaceDifferentFile.types new file mode 100644 index 00000000000..c03d51d2bc4 --- /dev/null +++ b/tests/baselines/reference/blockScopedNamespaceDifferentFile.types @@ -0,0 +1,46 @@ +=== tests/cases/compiler/test.ts === +// #15734 failed when test.ts comes before typings.d.ts +namespace C { +>C : typeof C + + export class Name { +>Name : Name + + static funcData = A.AA.func(); +>funcData : number +>A.AA.func() : number +>A.AA.func : () => number +>A.AA : typeof A.AA +>A : typeof A +>AA : typeof A.AA +>func : () => number + + static someConst = A.AA.foo; +>someConst : string +>A.AA.foo : "" +>A.AA : typeof A.AA +>A : typeof A +>AA : typeof A.AA +>foo : "" + + constructor(parameters) {} +>parameters : any + } +} + +=== tests/cases/compiler/typings.d.ts === +declare namespace A { +>A : typeof A + + namespace AA { +>AA : typeof AA + + function func(): number; +>func : () => number + + const foo = ""; +>foo : "" +>"" : "" + } +} + diff --git a/tests/cases/compiler/blockScopedNamespaceDifferentFile.ts b/tests/cases/compiler/blockScopedNamespaceDifferentFile.ts new file mode 100644 index 00000000000..4a9fe4a2a85 --- /dev/null +++ b/tests/cases/compiler/blockScopedNamespaceDifferentFile.ts @@ -0,0 +1,22 @@ +// @target: es5 +// @outFile: out.js +// @module: amd + +// #15734 failed when test.ts comes before typings.d.ts +// @Filename: test.ts +namespace C { + export class Name { + static funcData = A.AA.func(); + static someConst = A.AA.foo; + + constructor(parameters) {} + } +} + +// @Filename: typings.d.ts +declare namespace A { + namespace AA { + function func(): number; + const foo = ""; + } +} From a8ad40f131c0459d6316ff872cd7e4909d36d71b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 11 May 2017 11:42:36 -0700 Subject: [PATCH 12/34] Add async to the list of class element completion keyword and refactoring according to feedback --- src/harness/fourslash.ts | 3 ++- src/services/completions.ts | 8 ++++---- tests/cases/fourslash/completionEntryForClassMembers.ts | 4 ++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 74790400b98..57d0eb59ee2 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -3426,7 +3426,8 @@ namespace FourSlashInterface { "readonly", "get", "set", - "constructor" + "constructor", + "async" ]; constructor(protected state: FourSlash.TestState, private negative = false) { diff --git a/src/services/completions.ts b/src/services/completions.ts index cb4abb89b58..986ad0fe5ad 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -618,7 +618,8 @@ namespace ts.Completions { if (classLikeContainer = tryGetClassLikeCompletionContainer(contextToken)) { // cursor inside class declaration - return tryGetClassLikeCompletionSymbols(classLikeContainer); + getGetClassLikeCompletionSymbols(classLikeContainer); + return true; } if (jsxContainer = tryGetContainingJsxElement(contextToken)) { @@ -929,7 +930,7 @@ namespace ts.Completions { * * @returns true if 'symbols' was successfully populated; false otherwise. */ - function tryGetClassLikeCompletionSymbols(classLikeDeclaration: ClassLikeDeclaration): boolean { + function getGetClassLikeCompletionSymbols(classLikeDeclaration: ClassLikeDeclaration) { // We're looking up possible property names from parent type. isMemberCompletion = true; // Declaring new property/method/accessor @@ -965,8 +966,6 @@ namespace ts.Completions { baseProperty => baseProperty.getDeclarations() && !(getDeclarationModifierFlagsFromSymbol(baseProperty) & ModifierFlags.Private)); } } - - return true; } /** @@ -1431,6 +1430,7 @@ namespace ts.Completions { case SyntaxKind.ReadonlyKeyword: case SyntaxKind.GetKeyword: case SyntaxKind.SetKeyword: + case SyntaxKind.AsyncKeyword: return true; } } diff --git a/tests/cases/fourslash/completionEntryForClassMembers.ts b/tests/cases/fourslash/completionEntryForClassMembers.ts index 672046f62a7..8cea37d3b1e 100644 --- a/tests/cases/fourslash/completionEntryForClassMembers.ts +++ b/tests/cases/fourslash/completionEntryForClassMembers.ts @@ -84,6 +84,9 @@ ////class M extends B { //// private static identi/*classThatStartedWritingIdentifierAfterPrivateStaticModifier*/ ////} +////class N extends B { +//// async /*classThatHasWrittenAsyncKeyword*/ +////} const allowedKeywordCount = verify.allowedClassElementKeywords.length; type CompletionInfo = [string, string]; @@ -171,6 +174,7 @@ const classInstanceElementLocations = [ "classThatStartedWritingIdentifierOfGetAccessor", "classThatStartedWritingIdentifierOfSetAccessor", "classThatStartedWritingIdentifierAfterModifier", + "classThatHasWrittenAsyncKeyword" ]; verifyClassElementLocations(instanceMemberInfo, classInstanceElementLocations); From 20a70f99b108870327d5807ff1b5d97370d46111 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 11 May 2017 11:58:54 -0700 Subject: [PATCH 13/34] findAllReferences: Clean up uses of `getWidth` and `getStart` --- src/services/findAllReferences.ts | 34 +++++++++++++------------------ 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 7be2229877a..2363f59a774 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -637,7 +637,9 @@ namespace ts.FindAllReferences.Core { return parent ? scope.getSourceFile() : scope; } - function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, start: number, end: number): number[] { + function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile, fullStart = false): number[] { + const start = fullStart ? container.getFullStart() : container.getStart(sourceFile); + const end = container.getEnd(); const positions: number[] = []; /// TODO: Cache symbol existence for files to save text search @@ -676,16 +678,11 @@ namespace ts.FindAllReferences.Core { const references: Entry[] = []; const sourceFile = container.getSourceFile(); const labelName = targetLabel.text; - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container.getStart(), container.getEnd()); + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container); for (const position of possiblePositions) { const node = getTouchingWord(sourceFile, position); - if (!node || node.getWidth() !== labelName.length) { - continue; - } - // 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)) { + if (node && (node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel))) { references.push(nodeEntry(node)); } } @@ -697,15 +694,14 @@ namespace ts.FindAllReferences.Core { // Compare the length so we filter out strict superstrings of the symbol we are looking for switch (node && node.kind) { case SyntaxKind.Identifier: - return node.getWidth() === searchSymbolName.length; + return (node as Identifier).text.length === searchSymbolName.length; case SyntaxKind.StringLiteral: return (isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isNameOfExternalModuleImportOrDeclaration(node)) && - // For string literals we have two additional chars for the quotes - node.getWidth() === searchSymbolName.length + 2; + (node as StringLiteral).text.length === searchSymbolName.length; case SyntaxKind.NumericLiteral: - return isLiteralNameOfPropertyDeclarationOrIndexAccess(node) && node.getWidth() === searchSymbolName.length; + return isLiteralNameOfPropertyDeclarationOrIndexAccess(node) && (node as NumericLiteral).text.length === searchSymbolName.length; default: return false; @@ -722,7 +718,7 @@ namespace ts.FindAllReferences.Core { } function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, references: Push): void { - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, sourceFile.getStart(), sourceFile.getEnd()); + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText); for (const position of possiblePositions) { const referenceLocation = getTouchingPropertyName(sourceFile, position); if (referenceLocation.kind === kind) { @@ -746,9 +742,7 @@ namespace ts.FindAllReferences.Core { return; } - const start = state.findInComments ? container.getFullStart() : container.getStart(); - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, search.text, start, container.getEnd()); - for (const position of possiblePositions) { + for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container, /*fullStart*/ state.findInComments)) { getReferencesAtLocation(sourceFile, position, search, state); } } @@ -1192,7 +1186,7 @@ namespace ts.FindAllReferences.Core { const references: Entry[] = []; const sourceFile = searchSpaceNode.getSourceFile(); - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode.getStart(), searchSpaceNode.getEnd()); + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode); for (const position of possiblePositions) { const node = getTouchingWord(sourceFile, position); @@ -1254,13 +1248,13 @@ namespace ts.FindAllReferences.Core { if (searchSpaceNode.kind === SyntaxKind.SourceFile) { forEach(sourceFiles, sourceFile => { cancellationToken.throwIfCancellationRequested(); - possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", sourceFile.getStart(), sourceFile.getEnd()); + possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this"); getThisReferencesInFile(sourceFile, sourceFile, possiblePositions, references); }); } else { const sourceFile = searchSpaceNode.getSourceFile(); - possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", searchSpaceNode.getStart(), searchSpaceNode.getEnd()); + possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", searchSpaceNode); getThisReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, references); } @@ -1314,7 +1308,7 @@ namespace ts.FindAllReferences.Core { for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested(); - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, node.text, sourceFile.getStart(), sourceFile.getEnd()); + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, node.text); getReferencesForStringLiteralInFile(sourceFile, node.text, possiblePositions, references); } From 6d6cdac645226c27d43950e1030ff31fe5bbf9b8 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 11 May 2017 12:22:10 -0700 Subject: [PATCH 14/34] Handle escaped identifiers --- src/compiler/types.ts | 6 +++++- src/services/findAllReferences.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 74670352c34..d3e087434d9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -571,7 +571,11 @@ namespace ts { export interface Identifier extends PrimaryExpression { kind: SyntaxKind.Identifier; - text: string; // Text of identifier (with escapes converted to characters) + /** + * Text of identifier (with escapes converted to characters). + * If the identifier begins with two underscores, this will begin with three. + */ + text: string; originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later /*@internal*/ autoGenerateKind?: GeneratedIdentifierKind; // Specifies whether to auto-generate the text for an identifier. /*@internal*/ autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name. diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 2363f59a774..395c282f6d5 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -694,7 +694,7 @@ namespace ts.FindAllReferences.Core { // Compare the length so we filter out strict superstrings of the symbol we are looking for switch (node && node.kind) { case SyntaxKind.Identifier: - return (node as Identifier).text.length === searchSymbolName.length; + return unescapeIdentifier((node as Identifier).text).length === searchSymbolName.length; case SyntaxKind.StringLiteral: return (isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isNameOfExternalModuleImportOrDeclaration(node)) && From 9520108b9f2fda65034f6f6a84a82f15b40cac4d Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 11 May 2017 13:18:46 -0700 Subject: [PATCH 15/34] Move Levenshtein distance out of public API I had put it in the wrong half of utilities.ts. --- src/compiler/utilities.ts | 46 +++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 95b76286c59..5725d04a9cf 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4234,6 +4234,29 @@ namespace ts { // Firefox has Object.prototype.watch return options.watch && options.hasOwnProperty("watch"); } + + export function levenshtein(s1: string, s2: string): number { + let previous: number[] = new Array(s2.length + 1); + let current: number[] = new Array(s2.length + 1); + for (let i = 0; i < s2.length + 1; i++) { + previous[i] = i; + current[i] = -1; + } + for (let i = 1; i < s1.length + 1; i++) { + current[0] = i; + for (let j = 1; j < s2.length + 1; j++) { + current[j] = Math.min( + previous[j] + 1, + current[j - 1] + 1, + previous[j - 1] + (s1[i - 1] === s2[j - 1] ? 0 : 2)); + } + // shift current back to previous, and then reuse previous' array + const tmp = previous; + previous = current; + current = tmp; + } + return previous[previous.length - 1]; + } } namespace ts { @@ -4664,27 +4687,4 @@ namespace ts { export function unescapeIdentifier(identifier: string): string { return identifier.length >= 3 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ && identifier.charCodeAt(2) === CharacterCodes._ ? identifier.substr(1) : identifier; } - - export function levenshtein(s1: string, s2: string): number { - let previous: number[] = new Array(s2.length + 1); - let current: number[] = new Array(s2.length + 1); - for (let i = 0; i < s2.length + 1; i++) { - previous[i] = i; - current[i] = -1; - } - for (let i = 1; i < s1.length + 1; i++) { - current[0] = i; - for (let j = 1; j < s2.length + 1; j++) { - current[j] = Math.min( - previous[j] + 1, - current[j - 1] + 1, - previous[j - 1] + (s1[i - 1] === s2[j - 1] ? 0 : 2)); - } - // shift current back to previous, and then reuse previous' array - const tmp = previous; - previous = current; - current = tmp; - } - return previous[previous.length - 1]; - } } From 7c89ff7d77b41b64ac440eab34dddfc2c7e3f057 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 11 May 2017 13:39:37 -0700 Subject: [PATCH 16/34] Inline resolvedModuleFromResolved --- src/compiler/moduleNameResolver.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index acf01bac905..12a42b6899c 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -47,13 +47,11 @@ namespace ts { return resolved.path; } - /** Adds `isExernalLibraryImport` to a Resolved to get a ResolvedModule. */ - function resolvedModuleFromResolved({ path, extension }: Resolved, isExternalLibraryImport: boolean): ResolvedModuleFull { - return { resolvedFileName: path, extension, isExternalLibraryImport }; - } - function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations { - return { resolvedModule: resolved && resolvedModuleFromResolved(resolved, isExternalLibraryImport), failedLookupLocations }; + return { + resolvedModule: resolved && { resolvedFileName: resolved.path, extension: resolved.extension, isExternalLibraryImport }, + failedLookupLocations + }; } export function moduleHasNonRelativeName(moduleName: string): boolean { From 588c4eca427fe598147b17c23b8c47637a5fdac8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 11 May 2017 13:59:06 -0700 Subject: [PATCH 17/34] Filter out existing members of the class from the completion list --- src/services/completions.ts | 53 +++++++++++++++++-- .../completionEntryForClassMembers.ts | 33 ++++++++++-- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 986ad0fe5ad..6be420c0919 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -927,8 +927,6 @@ namespace ts.Completions { /** * Aggregates relevant symbols for completion in class declaration * Relevant symbols are stored in the captured 'symbols' variable. - * - * @returns true if 'symbols' was successfully populated; false otherwise. */ function getGetClassLikeCompletionSymbols(classLikeDeclaration: ClassLikeDeclaration) { // We're looking up possible property names from parent type. @@ -961,9 +959,8 @@ namespace ts.Completions { typeChecker.getTypeOfSymbolAtLocation(baseType.symbol, classLikeDeclaration) : baseType; - // List of property symbols of base type that are not private - symbols = filter(typeChecker.getPropertiesOfType(typeToGetPropertiesFrom), - baseProperty => baseProperty.getDeclarations() && !(getDeclarationModifierFlagsFromSymbol(baseProperty) & ModifierFlags.Private)); + // List of property symbols of base type that are not private and already implemented + symbols = filterClassMembersList(typeChecker.getPropertiesOfType(typeToGetPropertiesFrom), classLikeDeclaration.members, classElementModifierFlags); } } } @@ -1332,6 +1329,52 @@ namespace ts.Completions { return filter(contextualMemberSymbols, m => !existingMemberNames.get(m.name)); } + /** + * Filters out completion suggestions for class elements. + * + * @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags + */ + function filterClassMembersList(baseSymbols: Symbol[], existingMembers: ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] { + const existingMemberNames = createMap(); + for (const m of existingMembers) { + // Ignore omitted expressions for missing members + if (m.kind !== SyntaxKind.PropertyDeclaration && + m.kind !== SyntaxKind.MethodDeclaration && + m.kind !== SyntaxKind.GetAccessor && + m.kind !== SyntaxKind.SetAccessor) { + continue; + } + + // If this is the current item we are editing right now, do not filter it out + if (isCurrentlyEditingNode(m)) { + continue; + } + + // Dont filter member even if the name matches if it is declared private in the list + if (hasModifier(m, ModifierFlags.Private)) { + continue; + } + + // do not filter it out if the static presence doesnt match + const mIsStatic = hasModifier(m, ModifierFlags.Static); + const currentElementIsStatic = !!(currentClassElementModifierFlags & ModifierFlags.Static); + if ((mIsStatic && currentElementIsStatic) || + (!mIsStatic && currentElementIsStatic)) { + continue; + } + + const existingName = getPropertyNameForPropertyNameNode(m.name); + if (existingName) { + existingMemberNames.set(existingName, true); + } + } + + return filter(baseSymbols, baseProperty => + !existingMemberNames.get(baseProperty.name) && + baseProperty.getDeclarations() && + !(getDeclarationModifierFlagsFromSymbol(baseProperty) & ModifierFlags.Private)); + } + /** * Filters out completion suggestions from 'symbols' according to existing JSX attributes. * diff --git a/tests/cases/fourslash/completionEntryForClassMembers.ts b/tests/cases/fourslash/completionEntryForClassMembers.ts index 8cea37d3b1e..b242ca7ed78 100644 --- a/tests/cases/fourslash/completionEntryForClassMembers.ts +++ b/tests/cases/fourslash/completionEntryForClassMembers.ts @@ -17,6 +17,19 @@ //// } //// /*classThatHasAlreadyImplementedAnotherClassMethodAfterMethod*/ ////} +////class D1 extends B { +//// /*classThatHasDifferentMethodThanBase*/ +//// getValue1() { +//// return 10; +//// } +//// /*classThatHasDifferentMethodThanBaseAfterMethod*/ +////} +////class D2 extends B { +//// /*classThatHasAlreadyImplementedAnotherClassProtectedMethod*/ +//// protectedMethod() { +//// } +//// /*classThatHasDifferentMethodThanBaseAfterProtectedMethod*/ +////} ////class E { //// /*classThatDoesNotExtendAnotherClass*/ ////} @@ -131,6 +144,8 @@ function filterCompletionInfo(fn: (a: CompletionInfo) => boolean): CompletionInf const instanceMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "getValue" || a === "protectedMethod"); const staticMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "staticMethod"); +const instanceWithoutProtectedMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "getValue"); +const instanceWithoutPublicMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "protectedMethod"); // Not a class element declaration location const nonClassElementMarkers = [ @@ -156,8 +171,8 @@ verifyClassElementLocations({ validMembers: [], invalidMembers: allMembersOfBase // Instance base members and class member keywords allowed const classInstanceElementLocations = [ "classThatIsEmptyAndExtendingAnotherClass", - "classThatHasAlreadyImplementedAnotherClassMethod", - "classThatHasAlreadyImplementedAnotherClassMethodAfterMethod", + "classThatHasDifferentMethodThanBase", + "classThatHasDifferentMethodThanBaseAfterMethod", "classThatHasWrittenPublicKeyword", "classThatStartedWritingIdentifier", "propDeclarationWithoutSemicolon", @@ -183,4 +198,16 @@ const staticClassLocations = [ "classElementContainingStatic", "classThatStartedWritingIdentifierAfterStaticModifier" ]; -verifyClassElementLocations(staticMemberInfo, staticClassLocations); \ No newline at end of file +verifyClassElementLocations(staticMemberInfo, staticClassLocations); + +const classInstanceElementWithoutPublicMethodLocations = [ + "classThatHasAlreadyImplementedAnotherClassMethod", + "classThatHasAlreadyImplementedAnotherClassMethodAfterMethod", +]; +verifyClassElementLocations(instanceWithoutPublicMemberInfo, classInstanceElementWithoutPublicMethodLocations); + +const classInstanceElementWithoutProtectedMethodLocations = [ + "classThatHasAlreadyImplementedAnotherClassProtectedMethod", + "classThatHasDifferentMethodThanBaseAfterProtectedMethod", +]; +verifyClassElementLocations(instanceWithoutProtectedMemberInfo, classInstanceElementWithoutProtectedMethodLocations); \ No newline at end of file From d6fa91edcd1cf3bf277958881dc1e5f81e8a183a Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 11 May 2017 15:48:17 -0700 Subject: [PATCH 18/34] goToDefinition: Skip default and `=` imports --- src/services/goToDefinition.ts | 39 ++++++++++++------- .../cases/fourslash/goToDefinitionImports.ts | 26 +++++++++++++ 2 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 tests/cases/fourslash/goToDefinitionImports.ts diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 07b1e4448bd..946924bcf87 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -50,20 +50,8 @@ namespace ts.GoToDefinition { // get the aliased symbol instead. This allows for goto def on an import e.g. // import {A, B} from "mod"; // to jump to the implementation directly. - if (symbol.flags & SymbolFlags.Alias) { - const declaration = symbol.declarations[0]; - - // Go to the original declaration for cases: - // - // (1) when the aliased symbol was declared in the location(parent). - // (2) when the aliased symbol is originating from a named import. - // - if (node.kind === SyntaxKind.Identifier && - (node.parent === declaration || - (declaration.kind === SyntaxKind.ImportSpecifier && declaration.parent && declaration.parent.kind === SyntaxKind.NamedImports))) { - - symbol = typeChecker.getAliasedSymbol(symbol); - } + if (symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) { + symbol = typeChecker.getAliasedSymbol(symbol); } // Because name in short-hand property assignment has two different meanings: property name and property value, @@ -136,6 +124,29 @@ namespace ts.GoToDefinition { return getDefinitionFromSymbol(typeChecker, type.symbol, node); } + // Go to the original declaration for cases: + // + // (1) when the aliased symbol was declared in the location(parent). + // (2) when the aliased symbol is originating from an import. + // + function shouldSkipAlias(node: Node, declaration: Node): boolean { + if (node.kind !== SyntaxKind.Identifier) { + return false; + } + if (node.parent === declaration) { + return true; + } + switch (declaration.kind) { + case SyntaxKind.ImportClause: + case SyntaxKind.ImportEqualsDeclaration: + return true; + case SyntaxKind.ImportSpecifier: + return declaration.parent.kind === SyntaxKind.NamedImports; + default: + return false; + } + } + function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node): DefinitionInfo[] { const result: DefinitionInfo[] = []; const declarations = symbol.getDeclarations(); diff --git a/tests/cases/fourslash/goToDefinitionImports.ts b/tests/cases/fourslash/goToDefinitionImports.ts new file mode 100644 index 00000000000..4fdaedb5c3d --- /dev/null +++ b/tests/cases/fourslash/goToDefinitionImports.ts @@ -0,0 +1,26 @@ +/// + +// @Filename: /a.ts +////export default function /*fDef*/f() {} +////export const /*xDef*/x = 0; + +// @Filename: /b.ts +/////*bDef*/declare const b: number; +////export = b; + +// @Filename: /b.ts +////import f, { x } from "./a"; +////import * as /*aDef*/a from "./a"; +////import b = require("./b"); +/////*fUse*/f; +/////*xUse*/x; +/////*aUse*/a; +/////*bUse*/b; + +verify.goToDefinition({ + aUse: "aDef", // Namespace import isn't "skipped" + fUse: "fDef", + xUse: "xDef", + bUse: "bDef", +}); + From 3521b9c54692ee6147b778c861e25127eca25ef6 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 11 May 2017 16:22:54 -0700 Subject: [PATCH 19/34] if a JSSpecialPropertyDeclaration has a JSDoc type, use it --- src/compiler/checker.ts | 28 ++++++++++++------- .../baselines/reference/checkJsFiles7.symbols | 20 +++++++++++++ tests/baselines/reference/checkJsFiles7.types | 25 +++++++++++++++++ tests/cases/compiler/checkJsFiles7.ts | 12 ++++++++ 4 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 tests/baselines/reference/checkJsFiles7.symbols create mode 100644 tests/baselines/reference/checkJsFiles7.types create mode 100644 tests/cases/compiler/checkJsFiles7.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dde60c19f4e..64a4095162b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4158,6 +4158,7 @@ namespace ts { const types: Type[] = []; let definedInConstructor = false; let definedInMethod = false; + let jsDocType: Type; for (const declaration of symbol.declarations) { const expression = declaration.kind === SyntaxKind.BinaryExpression ? declaration : declaration.kind === SyntaxKind.PropertyAccessExpression ? getAncestor(declaration, SyntaxKind.BinaryExpression) : @@ -4176,19 +4177,26 @@ namespace ts { } } - if (expression.flags & NodeFlags.JavaScriptFile) { - // If there is a JSDoc type, use it - const type = getTypeForDeclarationFromJSDocComment(expression.parent); - if (type && type !== unknownType) { - types.push(getWidenedType(type)); - continue; + // If there is a JSDoc type, use it + const type = getTypeForDeclarationFromJSDocComment(expression.parent); + if (type) { + const declarationType = getWidenedType(type); + if (!jsDocType) { + jsDocType = declarationType; + } + else if (jsDocType !== unknownType && declarationType !== unknownType && !isTypeIdenticalTo(jsDocType, declarationType)) { + const name = getNameOfDeclaration(declaration); + error(name, Diagnostics.Subsequent_variable_declarations_must_have_the_same_type_Variable_0_must_be_of_type_1_but_here_has_type_2, declarationNameToString(name), typeToString(jsDocType), typeToString(declarationType)); } } - - types.push(getWidenedLiteralType(checkExpressionCached(expression.right))); + else if (!jsDocType) { + // If we don't have an explicit JSDoc type, get the type from the expression. + types.push(getWidenedLiteralType(checkExpressionCached(expression.right))); + } } - return getWidenedType(addOptionality(getUnionType(types, /*subtypeReduction*/ true), definedInMethod && !definedInConstructor)); + const type = jsDocType || getUnionType(types, /*subtypeReduction*/ true); + return getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor)); } // Return the type implied by a binding pattern element. This is the type of the initializer of the element if @@ -13229,7 +13237,7 @@ namespace ts { } return result; } - } + } function isValidSpreadType(type: Type): boolean { return !!(type.flags & (TypeFlags.Any | TypeFlags.Null | TypeFlags.Undefined | TypeFlags.NonPrimitive) || diff --git a/tests/baselines/reference/checkJsFiles7.symbols b/tests/baselines/reference/checkJsFiles7.symbols new file mode 100644 index 00000000000..a8214483b86 --- /dev/null +++ b/tests/baselines/reference/checkJsFiles7.symbols @@ -0,0 +1,20 @@ +=== tests/cases/compiler/a.js === +class C { +>C : Symbol(C, Decl(a.js, 0, 0)) + + constructor() { + /** @type {boolean} */ + this.a = true; +>this.a : Symbol(C.a, Decl(a.js, 1, 16), Decl(a.js, 3, 16)) +>this : Symbol(C, Decl(a.js, 0, 0)) +>a : Symbol(C.a, Decl(a.js, 1, 16), Decl(a.js, 3, 16)) + + this.a = !!this.a; +>this.a : Symbol(C.a, Decl(a.js, 1, 16), Decl(a.js, 3, 16)) +>this : Symbol(C, Decl(a.js, 0, 0)) +>a : Symbol(C.a, Decl(a.js, 1, 16), Decl(a.js, 3, 16)) +>this.a : Symbol(C.a, Decl(a.js, 1, 16), Decl(a.js, 3, 16)) +>this : Symbol(C, Decl(a.js, 0, 0)) +>a : Symbol(C.a, Decl(a.js, 1, 16), Decl(a.js, 3, 16)) + } +} diff --git a/tests/baselines/reference/checkJsFiles7.types b/tests/baselines/reference/checkJsFiles7.types new file mode 100644 index 00000000000..a86767b5e44 --- /dev/null +++ b/tests/baselines/reference/checkJsFiles7.types @@ -0,0 +1,25 @@ +=== tests/cases/compiler/a.js === +class C { +>C : C + + constructor() { + /** @type {boolean} */ + this.a = true; +>this.a = true : true +>this.a : boolean +>this : this +>a : boolean +>true : true + + this.a = !!this.a; +>this.a = !!this.a : boolean +>this.a : boolean +>this : this +>a : boolean +>!!this.a : boolean +>!this.a : boolean +>this.a : true +>this : this +>a : true + } +} diff --git a/tests/cases/compiler/checkJsFiles7.ts b/tests/cases/compiler/checkJsFiles7.ts new file mode 100644 index 00000000000..2c3d528da00 --- /dev/null +++ b/tests/cases/compiler/checkJsFiles7.ts @@ -0,0 +1,12 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @noImplicitAny: true +// @fileName: a.js +class C { + constructor() { + /** @type {boolean} */ + this.a = true; + this.a = !!this.a; + } +} \ No newline at end of file From 181ff8615064edeb5fdf33121973c1d0ec07dc50 Mon Sep 17 00:00:00 2001 From: Kanchalai Tanglertsampan Date: Thu, 11 May 2017 16:30:43 -0700 Subject: [PATCH 20/34] getApparentType for each constituent of props type of the targetAttributesType --- src/compiler/checker.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 861279b2225..ce89fb72b52 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13548,6 +13548,17 @@ namespace ts { return _jsxElementChildrenPropertyName; } + function createIntersectionOfApparentTypeOfJsxPropsType(propsType: Type): Type { + if (propsType && propsType.flags & TypeFlags.Intersection) { + const propsApprentType: Type[] = []; + for (const t of (propsType).types) { + propsApprentType.push(getApparentType(t)); + } + return getIntersectionType(propsApprentType); + } + return propsType; + } + /** * Get JSX attributes type by trying to resolve openingLikeElement as a stateless function component. * Return only attributes type of successfully resolved call signature. @@ -13568,6 +13579,7 @@ namespace ts { if (callSignature !== unknownSignature) { const callReturnType = callSignature && getReturnTypeOfSignature(callSignature); let paramType = callReturnType && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0])); + paramType = createIntersectionOfApparentTypeOfJsxPropsType(paramType); if (callReturnType && isTypeAssignableTo(callReturnType, jsxStatelessElementType)) { // Intersect in JSX.IntrinsicAttributes if it exists const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes); @@ -13605,7 +13617,8 @@ namespace ts { let allMatchingAttributesType: Type; for (const candidate of candidatesOutArray) { const callReturnType = getReturnTypeOfSignature(candidate); - const paramType = callReturnType && (candidate.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(candidate.parameters[0])); + let paramType = callReturnType && (candidate.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(candidate.parameters[0])); + paramType = createIntersectionOfApparentTypeOfJsxPropsType(paramType); if (callReturnType && isTypeAssignableTo(callReturnType, jsxStatelessElementType)) { let shouldBeCandidate = true; for (const attribute of openingLikeElement.attributes.properties) { From bce7ddb5c555725e7b3a23d9042d194fca86e82b Mon Sep 17 00:00:00 2001 From: Kanchalai Tanglertsampan Date: Thu, 11 May 2017 16:30:56 -0700 Subject: [PATCH 21/34] Add tests and update lib tests file --- .../jsx/tsxGenericAttributesType1.tsx | 18 ++++++++++++++++++ .../jsx/tsxGenericAttributesType2.tsx | 10 ++++++++++ .../jsx/tsxGenericAttributesType3.tsx | 17 +++++++++++++++++ .../jsx/tsxGenericAttributesType4.tsx | 18 ++++++++++++++++++ tests/lib/react.d.ts | 2 +- 5 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests/cases/conformance/jsx/tsxGenericAttributesType1.tsx create mode 100644 tests/cases/conformance/jsx/tsxGenericAttributesType2.tsx create mode 100644 tests/cases/conformance/jsx/tsxGenericAttributesType3.tsx create mode 100644 tests/cases/conformance/jsx/tsxGenericAttributesType4.tsx diff --git a/tests/cases/conformance/jsx/tsxGenericAttributesType1.tsx b/tests/cases/conformance/jsx/tsxGenericAttributesType1.tsx new file mode 100644 index 00000000000..e6b7fc18ef7 --- /dev/null +++ b/tests/cases/conformance/jsx/tsxGenericAttributesType1.tsx @@ -0,0 +1,18 @@ +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); + +const decorator = function (Component: React.StatelessComponent): React.StatelessComponent { + return (props) => +}; + +const decorator2 = function (Component: React.StatelessComponent): React.StatelessComponent { + return (props) => +}; + +const decorator3 = function (Component: React.StatelessComponent): React.StatelessComponent { + return (props) => +}; \ No newline at end of file diff --git a/tests/cases/conformance/jsx/tsxGenericAttributesType2.tsx b/tests/cases/conformance/jsx/tsxGenericAttributesType2.tsx new file mode 100644 index 00000000000..48acd55546f --- /dev/null +++ b/tests/cases/conformance/jsx/tsxGenericAttributesType2.tsx @@ -0,0 +1,10 @@ +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); + +const decorator4 = function (Component: React.StatelessComponent): React.StatelessComponent { + return (props) => +}; \ No newline at end of file diff --git a/tests/cases/conformance/jsx/tsxGenericAttributesType3.tsx b/tests/cases/conformance/jsx/tsxGenericAttributesType3.tsx new file mode 100644 index 00000000000..b683c5e7970 --- /dev/null +++ b/tests/cases/conformance/jsx/tsxGenericAttributesType3.tsx @@ -0,0 +1,17 @@ +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); + +class B1 extends React.Component { + render() { + return
hi
; + } +} +class B extends React.Component { + render() { + return ; + } +} \ No newline at end of file diff --git a/tests/cases/conformance/jsx/tsxGenericAttributesType4.tsx b/tests/cases/conformance/jsx/tsxGenericAttributesType4.tsx new file mode 100644 index 00000000000..b207f2b4398 --- /dev/null +++ b/tests/cases/conformance/jsx/tsxGenericAttributesType4.tsx @@ -0,0 +1,18 @@ +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); + +class B1 extends React.Component { + render() { + return
hi
; + } +} +class B extends React.Component { + render() { + // Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object + return ; + } +} \ No newline at end of file diff --git a/tests/lib/react.d.ts b/tests/lib/react.d.ts index 1d7b787b999..7f03899eb6c 100644 --- a/tests/lib/react.d.ts +++ b/tests/lib/react.d.ts @@ -197,7 +197,7 @@ declare namespace __React { type SFC

= StatelessComponent

; interface StatelessComponent

{ - (props: P, context?: any): ReactElement; + (props: P & { children?: ReactNode }, context?: any): ReactElement; propTypes?: ValidationMap

; contextTypes?: ValidationMap; defaultProps?: P; From 705771d8746aaf5eee67883a1d282776d54dc183 Mon Sep 17 00:00:00 2001 From: Kanchalai Tanglertsampan Date: Thu, 11 May 2017 16:31:40 -0700 Subject: [PATCH 22/34] Update baselines --- .../reference/tsxGenericAttributesType1.js | 28 +++++++ .../tsxGenericAttributesType1.symbols | 66 ++++++++++++++++ .../reference/tsxGenericAttributesType1.types | 77 +++++++++++++++++++ .../tsxGenericAttributesType2.errors.txt | 11 +++ .../reference/tsxGenericAttributesType2.js | 14 ++++ .../reference/tsxGenericAttributesType3.js | 48 ++++++++++++ .../tsxGenericAttributesType3.symbols | 41 ++++++++++ .../reference/tsxGenericAttributesType3.types | 43 +++++++++++ .../tsxGenericAttributesType4.errors.txt | 19 +++++ .../reference/tsxGenericAttributesType4.js | 50 ++++++++++++ 10 files changed, 397 insertions(+) create mode 100644 tests/baselines/reference/tsxGenericAttributesType1.js create mode 100644 tests/baselines/reference/tsxGenericAttributesType1.symbols create mode 100644 tests/baselines/reference/tsxGenericAttributesType1.types create mode 100644 tests/baselines/reference/tsxGenericAttributesType2.errors.txt create mode 100644 tests/baselines/reference/tsxGenericAttributesType2.js create mode 100644 tests/baselines/reference/tsxGenericAttributesType3.js create mode 100644 tests/baselines/reference/tsxGenericAttributesType3.symbols create mode 100644 tests/baselines/reference/tsxGenericAttributesType3.types create mode 100644 tests/baselines/reference/tsxGenericAttributesType4.errors.txt create mode 100644 tests/baselines/reference/tsxGenericAttributesType4.js diff --git a/tests/baselines/reference/tsxGenericAttributesType1.js b/tests/baselines/reference/tsxGenericAttributesType1.js new file mode 100644 index 00000000000..5d3d388396a --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType1.js @@ -0,0 +1,28 @@ +//// [file.tsx] +import React = require('react'); + +const decorator = function (Component: React.StatelessComponent): React.StatelessComponent { + return (props) => +}; + +const decorator2 = function (Component: React.StatelessComponent): React.StatelessComponent { + return (props) => +}; + +const decorator3 = function (Component: React.StatelessComponent): React.StatelessComponent { + return (props) => +}; + +//// [file.jsx] +"use strict"; +exports.__esModule = true; +var React = require("react"); +var decorator = function (Component) { + return function (props) { return ; }; +}; +var decorator2 = function (Component) { + return function (props) { return ; }; +}; +var decorator3 = function (Component) { + return function (props) { return ; }; +}; diff --git a/tests/baselines/reference/tsxGenericAttributesType1.symbols b/tests/baselines/reference/tsxGenericAttributesType1.symbols new file mode 100644 index 00000000000..473de9b5c72 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType1.symbols @@ -0,0 +1,66 @@ +=== tests/cases/conformance/jsx/file.tsx === +import React = require('react'); +>React : Symbol(React, Decl(file.tsx, 0, 0)) + +const decorator = function (Component: React.StatelessComponent): React.StatelessComponent { +>decorator : Symbol(decorator, Decl(file.tsx, 2, 5)) +>T : Symbol(T, Decl(file.tsx, 2, 28)) +>Component : Symbol(Component, Decl(file.tsx, 2, 31)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>StatelessComponent : Symbol(React.StatelessComponent, Decl(react.d.ts, 197, 40)) +>T : Symbol(T, Decl(file.tsx, 2, 28)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>StatelessComponent : Symbol(React.StatelessComponent, Decl(react.d.ts, 197, 40)) +>T : Symbol(T, Decl(file.tsx, 2, 28)) + + return (props) => +>props : Symbol(props, Decl(file.tsx, 3, 12)) +>Component : Symbol(Component, Decl(file.tsx, 2, 31)) +>props : Symbol(props, Decl(file.tsx, 3, 12)) +>Component : Symbol(Component, Decl(file.tsx, 2, 31)) + +}; + +const decorator2 = function (Component: React.StatelessComponent): React.StatelessComponent { +>decorator2 : Symbol(decorator2, Decl(file.tsx, 6, 5)) +>T : Symbol(T, Decl(file.tsx, 6, 29)) +>x : Symbol(x, Decl(file.tsx, 6, 40)) +>Component : Symbol(Component, Decl(file.tsx, 6, 54)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>StatelessComponent : Symbol(React.StatelessComponent, Decl(react.d.ts, 197, 40)) +>T : Symbol(T, Decl(file.tsx, 6, 29)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>StatelessComponent : Symbol(React.StatelessComponent, Decl(react.d.ts, 197, 40)) +>T : Symbol(T, Decl(file.tsx, 6, 29)) + + return (props) => +>props : Symbol(props, Decl(file.tsx, 7, 12)) +>Component : Symbol(Component, Decl(file.tsx, 6, 54)) +>props : Symbol(props, Decl(file.tsx, 7, 12)) +>x : Symbol(x, Decl(file.tsx, 7, 43)) +>Component : Symbol(Component, Decl(file.tsx, 6, 54)) + +}; + +const decorator3 = function (Component: React.StatelessComponent): React.StatelessComponent { +>decorator3 : Symbol(decorator3, Decl(file.tsx, 10, 5)) +>T : Symbol(T, Decl(file.tsx, 10, 29)) +>x : Symbol(x, Decl(file.tsx, 10, 40)) +>U : Symbol(U, Decl(file.tsx, 10, 53)) +>x : Symbol(x, Decl(file.tsx, 10, 65)) +>Component : Symbol(Component, Decl(file.tsx, 10, 80)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>StatelessComponent : Symbol(React.StatelessComponent, Decl(react.d.ts, 197, 40)) +>T : Symbol(T, Decl(file.tsx, 10, 29)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>StatelessComponent : Symbol(React.StatelessComponent, Decl(react.d.ts, 197, 40)) +>T : Symbol(T, Decl(file.tsx, 10, 29)) + + return (props) => +>props : Symbol(props, Decl(file.tsx, 11, 12)) +>Component : Symbol(Component, Decl(file.tsx, 10, 80)) +>x : Symbol(x, Decl(file.tsx, 11, 32)) +>props : Symbol(props, Decl(file.tsx, 11, 12)) +>Component : Symbol(Component, Decl(file.tsx, 10, 80)) + +}; diff --git a/tests/baselines/reference/tsxGenericAttributesType1.types b/tests/baselines/reference/tsxGenericAttributesType1.types new file mode 100644 index 00000000000..50be358302e --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType1.types @@ -0,0 +1,77 @@ +=== tests/cases/conformance/jsx/file.tsx === +import React = require('react'); +>React : typeof React + +const decorator = function (Component: React.StatelessComponent): React.StatelessComponent { +>decorator : (Component: React.StatelessComponent) => React.StatelessComponent +>function (Component: React.StatelessComponent): React.StatelessComponent { return (props) => } : (Component: React.StatelessComponent) => React.StatelessComponent +>T : T +>Component : React.StatelessComponent +>React : any +>StatelessComponent : React.StatelessComponent

+>T : T +>React : any +>StatelessComponent : React.StatelessComponent

+>T : T + + return (props) => +>(props) => : (props: T & { children?: React.ReactNode; }) => JSX.Element +>props : T & { children?: React.ReactNode; } +> : JSX.Element +>Component : React.StatelessComponent +>props : T & { children?: React.ReactNode; } +>Component : React.StatelessComponent + +}; + +const decorator2 = function (Component: React.StatelessComponent): React.StatelessComponent { +>decorator2 : (Component: React.StatelessComponent) => React.StatelessComponent +>function (Component: React.StatelessComponent): React.StatelessComponent { return (props) => } : (Component: React.StatelessComponent) => React.StatelessComponent +>T : T +>x : number +>Component : React.StatelessComponent +>React : any +>StatelessComponent : React.StatelessComponent

+>T : T +>React : any +>StatelessComponent : React.StatelessComponent

+>T : T + + return (props) => +>(props) => : (props: T & { children?: React.ReactNode; }) => JSX.Element +>props : T & { children?: React.ReactNode; } +> : JSX.Element +>Component : React.StatelessComponent +>props : T & { children?: React.ReactNode; } +>x : number +>2 : 2 +>Component : React.StatelessComponent + +}; + +const decorator3 = function (Component: React.StatelessComponent): React.StatelessComponent { +>decorator3 : (Component: React.StatelessComponent) => React.StatelessComponent +>function (Component: React.StatelessComponent): React.StatelessComponent { return (props) => } : (Component: React.StatelessComponent) => React.StatelessComponent +>T : T +>x : number +>U : U +>x : number +>Component : React.StatelessComponent +>React : any +>StatelessComponent : React.StatelessComponent

+>T : T +>React : any +>StatelessComponent : React.StatelessComponent

+>T : T + + return (props) => +>(props) => : (props: T & { children?: React.ReactNode; }) => JSX.Element +>props : T & { children?: React.ReactNode; } +> : JSX.Element +>Component : React.StatelessComponent +>x : number +>2 : 2 +>props : T & { children?: React.ReactNode; } +>Component : React.StatelessComponent + +}; diff --git a/tests/baselines/reference/tsxGenericAttributesType2.errors.txt b/tests/baselines/reference/tsxGenericAttributesType2.errors.txt new file mode 100644 index 00000000000..cbd926c9b69 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType2.errors.txt @@ -0,0 +1,11 @@ +tests/cases/conformance/jsx/file.tsx(4,45): error TS2339: Property 'y' does not exist on type 'IntrinsicAttributes & { x: number; } & { children?: ReactNode; }'. + + +==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== + import React = require('react'); + + const decorator4 = function (Component: React.StatelessComponent): React.StatelessComponent { + return (props) => + ~~~~~~~~~~ +!!! error TS2339: Property 'y' does not exist on type 'IntrinsicAttributes & { x: number; } & { children?: ReactNode; }'. + }; \ No newline at end of file diff --git a/tests/baselines/reference/tsxGenericAttributesType2.js b/tests/baselines/reference/tsxGenericAttributesType2.js new file mode 100644 index 00000000000..7b46d08c2c4 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType2.js @@ -0,0 +1,14 @@ +//// [file.tsx] +import React = require('react'); + +const decorator4 = function (Component: React.StatelessComponent): React.StatelessComponent { + return (props) => +}; + +//// [file.jsx] +"use strict"; +exports.__esModule = true; +var React = require("react"); +var decorator4 = function (Component) { + return function (props) { return ; }; +}; diff --git a/tests/baselines/reference/tsxGenericAttributesType3.js b/tests/baselines/reference/tsxGenericAttributesType3.js new file mode 100644 index 00000000000..51491d99aa2 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType3.js @@ -0,0 +1,48 @@ +//// [file.tsx] +import React = require('react'); + +class B1 extends React.Component { + render() { + return

hi
; + } +} +class B extends React.Component { + render() { + return ; + } +} + +//// [file.jsx] +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +var React = require("react"); +var B1 = (function (_super) { + __extends(B1, _super); + function B1() { + return _super !== null && _super.apply(this, arguments) || this; + } + B1.prototype.render = function () { + return
hi
; + }; + return B1; +}(React.Component)); +var B = (function (_super) { + __extends(B, _super); + function B() { + return _super !== null && _super.apply(this, arguments) || this; + } + B.prototype.render = function () { + return ; + }; + return B; +}(React.Component)); diff --git a/tests/baselines/reference/tsxGenericAttributesType3.symbols b/tests/baselines/reference/tsxGenericAttributesType3.symbols new file mode 100644 index 00000000000..8c235257e8d --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType3.symbols @@ -0,0 +1,41 @@ +=== tests/cases/conformance/jsx/file.tsx === +import React = require('react'); +>React : Symbol(React, Decl(file.tsx, 0, 0)) + +class B1 extends React.Component { +>B1 : Symbol(B1, Decl(file.tsx, 0, 32)) +>T : Symbol(T, Decl(file.tsx, 2, 9)) +>x : Symbol(x, Decl(file.tsx, 2, 20)) +>x : Symbol(x, Decl(file.tsx, 2, 36)) +>React.Component : Symbol(React.Component, Decl(react.d.ts, 158, 55)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>Component : Symbol(React.Component, Decl(react.d.ts, 158, 55)) +>T : Symbol(T, Decl(file.tsx, 2, 9)) + + render() { +>render : Symbol(B1.render, Decl(file.tsx, 2, 82)) + + return
hi
; +>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45)) +>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45)) + } +} +class B extends React.Component { +>B : Symbol(B, Decl(file.tsx, 6, 1)) +>U : Symbol(U, Decl(file.tsx, 7, 8)) +>React.Component : Symbol(React.Component, Decl(react.d.ts, 158, 55)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>Component : Symbol(React.Component, Decl(react.d.ts, 158, 55)) +>U : Symbol(U, Decl(file.tsx, 7, 8)) + + render() { +>render : Symbol(B.render, Decl(file.tsx, 7, 43)) + + return ; +>B1 : Symbol(B1, Decl(file.tsx, 0, 32)) +>this.props : Symbol(React.Component.props, Decl(react.d.ts, 166, 37)) +>this : Symbol(B, Decl(file.tsx, 6, 1)) +>props : Symbol(React.Component.props, Decl(react.d.ts, 166, 37)) +>x : Symbol(x, Decl(file.tsx, 9, 34)) + } +} diff --git a/tests/baselines/reference/tsxGenericAttributesType3.types b/tests/baselines/reference/tsxGenericAttributesType3.types new file mode 100644 index 00000000000..72674ac5bf7 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType3.types @@ -0,0 +1,43 @@ +=== tests/cases/conformance/jsx/file.tsx === +import React = require('react'); +>React : typeof React + +class B1 extends React.Component { +>B1 : B1 +>T : T +>x : string +>x : string +>React.Component : React.Component +>React : typeof React +>Component : typeof React.Component +>T : T + + render() { +>render : () => JSX.Element + + return
hi
; +>
hi
: JSX.Element +>div : any +>div : any + } +} +class B extends React.Component { +>B : B +>U : U +>React.Component : React.Component +>React : typeof React +>Component : typeof React.Component +>U : U + + render() { +>render : () => JSX.Element + + return ; +> : JSX.Element +>B1 : typeof B1 +>this.props : U & { children?: React.ReactNode; } +>this : this +>props : U & { children?: React.ReactNode; } +>x : string + } +} diff --git a/tests/baselines/reference/tsxGenericAttributesType4.errors.txt b/tests/baselines/reference/tsxGenericAttributesType4.errors.txt new file mode 100644 index 00000000000..ee91f938b9f --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType4.errors.txt @@ -0,0 +1,19 @@ +tests/cases/conformance/jsx/file.tsx(11,36): error TS2339: Property 'x' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes> & { children?: ReactNode; }'. + + +==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== + import React = require('react'); + + class B1 extends React.Component { + render() { + return
hi
; + } + } + class B extends React.Component { + render() { + // Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object + return ; + ~~~~~~ +!!! error TS2339: Property 'x' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes> & { children?: ReactNode; }'. + } + } \ No newline at end of file diff --git a/tests/baselines/reference/tsxGenericAttributesType4.js b/tests/baselines/reference/tsxGenericAttributesType4.js new file mode 100644 index 00000000000..a00d03cd61b --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType4.js @@ -0,0 +1,50 @@ +//// [file.tsx] +import React = require('react'); + +class B1 extends React.Component { + render() { + return
hi
; + } +} +class B extends React.Component { + render() { + // Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object + return ; + } +} + +//// [file.jsx] +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +var React = require("react"); +var B1 = (function (_super) { + __extends(B1, _super); + function B1() { + return _super !== null && _super.apply(this, arguments) || this; + } + B1.prototype.render = function () { + return
hi
; + }; + return B1; +}(React.Component)); +var B = (function (_super) { + __extends(B, _super); + function B() { + return _super !== null && _super.apply(this, arguments) || this; + } + B.prototype.render = function () { + // Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object + return ; + }; + return B; +}(React.Component)); From 84f419b533cd3382573320be4b5796a1a0eef144 Mon Sep 17 00:00:00 2001 From: Yui T Date: Thu, 11 May 2017 22:40:21 -0700 Subject: [PATCH 23/34] getApparentType of the propsType --- src/compiler/checker.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ce89fb72b52..03302ec5f32 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13548,13 +13548,16 @@ namespace ts { return _jsxElementChildrenPropertyName; } - function createIntersectionOfApparentTypeOfJsxPropsType(propsType: Type): Type { - if (propsType && propsType.flags & TypeFlags.Intersection) { - const propsApprentType: Type[] = []; - for (const t of (propsType).types) { - propsApprentType.push(getApparentType(t)); + function getApparentTypeOfJsxPropsType(propsType: Type): Type { + if (propsType) { + if (propsType.flags & TypeFlags.Intersection) { + const propsApprentType: Type[] = []; + for (const t of (propsType).types) { + propsApprentType.push(getApparentType(t)); + } + return getIntersectionType(propsApprentType); } - return getIntersectionType(propsApprentType); + return getApparentType(propsType); } return propsType; } @@ -13579,7 +13582,7 @@ namespace ts { if (callSignature !== unknownSignature) { const callReturnType = callSignature && getReturnTypeOfSignature(callSignature); let paramType = callReturnType && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0])); - paramType = createIntersectionOfApparentTypeOfJsxPropsType(paramType); + paramType = getApparentTypeOfJsxPropsType(paramType); if (callReturnType && isTypeAssignableTo(callReturnType, jsxStatelessElementType)) { // Intersect in JSX.IntrinsicAttributes if it exists const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes); @@ -13618,7 +13621,7 @@ namespace ts { for (const candidate of candidatesOutArray) { const callReturnType = getReturnTypeOfSignature(candidate); let paramType = callReturnType && (candidate.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(candidate.parameters[0])); - paramType = createIntersectionOfApparentTypeOfJsxPropsType(paramType); + paramType = getApparentTypeOfJsxPropsType(paramType); if (callReturnType && isTypeAssignableTo(callReturnType, jsxStatelessElementType)) { let shouldBeCandidate = true; for (const attribute of openingLikeElement.attributes.properties) { From 8d09085800f131f0a5e70ed0fd2c8cb81d5afdc5 Mon Sep 17 00:00:00 2001 From: Yui T Date: Thu, 11 May 2017 22:40:34 -0700 Subject: [PATCH 24/34] Add more tests and update baselines --- .../tsxGenericAttributesType5.errors.txt | 20 ++++++++ .../reference/tsxGenericAttributesType5.js | 51 +++++++++++++++++++ .../reference/tsxGenericAttributesType6.js | 49 ++++++++++++++++++ .../tsxGenericAttributesType6.symbols | 45 ++++++++++++++++ .../reference/tsxGenericAttributesType6.types | 47 +++++++++++++++++ .../reference/tsxGenericAttributesType7.js | 22 ++++++++ .../tsxGenericAttributesType7.symbols | 35 +++++++++++++ .../reference/tsxGenericAttributesType7.types | 39 ++++++++++++++ .../reference/tsxGenericAttributesType8.js | 22 ++++++++ .../tsxGenericAttributesType8.symbols | 34 +++++++++++++ .../reference/tsxGenericAttributesType8.types | 38 ++++++++++++++ .../tsxStatelessFunctionComponents3.types | 6 +-- .../jsx/tsxGenericAttributesType5.tsx | 19 +++++++ .../jsx/tsxGenericAttributesType6.tsx | 18 +++++++ .../jsx/tsxGenericAttributesType7.tsx | 15 ++++++ .../jsx/tsxGenericAttributesType8.tsx | 15 ++++++ 16 files changed, 472 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/tsxGenericAttributesType5.errors.txt create mode 100644 tests/baselines/reference/tsxGenericAttributesType5.js create mode 100644 tests/baselines/reference/tsxGenericAttributesType6.js create mode 100644 tests/baselines/reference/tsxGenericAttributesType6.symbols create mode 100644 tests/baselines/reference/tsxGenericAttributesType6.types create mode 100644 tests/baselines/reference/tsxGenericAttributesType7.js create mode 100644 tests/baselines/reference/tsxGenericAttributesType7.symbols create mode 100644 tests/baselines/reference/tsxGenericAttributesType7.types create mode 100644 tests/baselines/reference/tsxGenericAttributesType8.js create mode 100644 tests/baselines/reference/tsxGenericAttributesType8.symbols create mode 100644 tests/baselines/reference/tsxGenericAttributesType8.types create mode 100644 tests/cases/conformance/jsx/tsxGenericAttributesType5.tsx create mode 100644 tests/cases/conformance/jsx/tsxGenericAttributesType6.tsx create mode 100644 tests/cases/conformance/jsx/tsxGenericAttributesType7.tsx create mode 100644 tests/cases/conformance/jsx/tsxGenericAttributesType8.tsx diff --git a/tests/baselines/reference/tsxGenericAttributesType5.errors.txt b/tests/baselines/reference/tsxGenericAttributesType5.errors.txt new file mode 100644 index 00000000000..83f07326f92 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType5.errors.txt @@ -0,0 +1,20 @@ +tests/cases/conformance/jsx/file.tsx(12,36): error TS2339: Property 'x' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes> & { children?: ReactNode; }'. + + +==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== + import React = require('react'); + + class B1 extends React.Component { + render() { + return
hi
; + } + } + class B extends React.Component { + props: U; + render() { + // Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object + return ; + ~~~~~~ +!!! error TS2339: Property 'x' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes> & { children?: ReactNode; }'. + } + } \ No newline at end of file diff --git a/tests/baselines/reference/tsxGenericAttributesType5.js b/tests/baselines/reference/tsxGenericAttributesType5.js new file mode 100644 index 00000000000..cb535417dd6 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType5.js @@ -0,0 +1,51 @@ +//// [file.tsx] +import React = require('react'); + +class B1 extends React.Component { + render() { + return
hi
; + } +} +class B extends React.Component { + props: U; + render() { + // Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object + return ; + } +} + +//// [file.jsx] +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +var React = require("react"); +var B1 = (function (_super) { + __extends(B1, _super); + function B1() { + return _super !== null && _super.apply(this, arguments) || this; + } + B1.prototype.render = function () { + return
hi
; + }; + return B1; +}(React.Component)); +var B = (function (_super) { + __extends(B, _super); + function B() { + return _super !== null && _super.apply(this, arguments) || this; + } + B.prototype.render = function () { + // Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object + return ; + }; + return B; +}(React.Component)); diff --git a/tests/baselines/reference/tsxGenericAttributesType6.js b/tests/baselines/reference/tsxGenericAttributesType6.js new file mode 100644 index 00000000000..84cab3e1f94 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType6.js @@ -0,0 +1,49 @@ +//// [file.tsx] +import React = require('react'); + +class B1 extends React.Component { + render() { + return
hi
; + } +} +class B extends React.Component { + props: U; + render() { + return ; + } +} + +//// [file.jsx] +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +var React = require("react"); +var B1 = (function (_super) { + __extends(B1, _super); + function B1() { + return _super !== null && _super.apply(this, arguments) || this; + } + B1.prototype.render = function () { + return
hi
; + }; + return B1; +}(React.Component)); +var B = (function (_super) { + __extends(B, _super); + function B() { + return _super !== null && _super.apply(this, arguments) || this; + } + B.prototype.render = function () { + return ; + }; + return B; +}(React.Component)); diff --git a/tests/baselines/reference/tsxGenericAttributesType6.symbols b/tests/baselines/reference/tsxGenericAttributesType6.symbols new file mode 100644 index 00000000000..27c8ed2d6dc --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType6.symbols @@ -0,0 +1,45 @@ +=== tests/cases/conformance/jsx/file.tsx === +import React = require('react'); +>React : Symbol(React, Decl(file.tsx, 0, 0)) + +class B1 extends React.Component { +>B1 : Symbol(B1, Decl(file.tsx, 0, 32)) +>T : Symbol(T, Decl(file.tsx, 2, 9)) +>x : Symbol(x, Decl(file.tsx, 2, 20)) +>x : Symbol(x, Decl(file.tsx, 2, 36)) +>React.Component : Symbol(React.Component, Decl(react.d.ts, 158, 55)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>Component : Symbol(React.Component, Decl(react.d.ts, 158, 55)) +>T : Symbol(T, Decl(file.tsx, 2, 9)) + + render() { +>render : Symbol(B1.render, Decl(file.tsx, 2, 82)) + + return
hi
; +>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45)) +>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2399, 45)) + } +} +class B extends React.Component { +>B : Symbol(B, Decl(file.tsx, 6, 1)) +>U : Symbol(U, Decl(file.tsx, 7, 8)) +>React.Component : Symbol(React.Component, Decl(react.d.ts, 158, 55)) +>React : Symbol(React, Decl(file.tsx, 0, 0)) +>Component : Symbol(React.Component, Decl(react.d.ts, 158, 55)) +>U : Symbol(U, Decl(file.tsx, 7, 8)) + + props: U; +>props : Symbol(B.props, Decl(file.tsx, 7, 43)) +>U : Symbol(U, Decl(file.tsx, 7, 8)) + + render() { +>render : Symbol(B.render, Decl(file.tsx, 8, 13)) + + return ; +>B1 : Symbol(B1, Decl(file.tsx, 0, 32)) +>this.props : Symbol(B.props, Decl(file.tsx, 7, 43)) +>this : Symbol(B, Decl(file.tsx, 6, 1)) +>props : Symbol(B.props, Decl(file.tsx, 7, 43)) +>x : Symbol(x, Decl(file.tsx, 10, 34)) + } +} diff --git a/tests/baselines/reference/tsxGenericAttributesType6.types b/tests/baselines/reference/tsxGenericAttributesType6.types new file mode 100644 index 00000000000..662b6299dad --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType6.types @@ -0,0 +1,47 @@ +=== tests/cases/conformance/jsx/file.tsx === +import React = require('react'); +>React : typeof React + +class B1 extends React.Component { +>B1 : B1 +>T : T +>x : string +>x : string +>React.Component : React.Component +>React : typeof React +>Component : typeof React.Component +>T : T + + render() { +>render : () => JSX.Element + + return
hi
; +>
hi
: JSX.Element +>div : any +>div : any + } +} +class B extends React.Component { +>B : B +>U : U +>React.Component : React.Component +>React : typeof React +>Component : typeof React.Component +>U : U + + props: U; +>props : U +>U : U + + render() { +>render : () => JSX.Element + + return ; +> : JSX.Element +>B1 : typeof B1 +>this.props : U +>this : this +>props : U +>x : string + } +} diff --git a/tests/baselines/reference/tsxGenericAttributesType7.js b/tests/baselines/reference/tsxGenericAttributesType7.js new file mode 100644 index 00000000000..4e254f25e55 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType7.js @@ -0,0 +1,22 @@ +//// [file.tsx] +import React = require('react'); + +declare function Component(props: T) : JSX.Element; +const decorator = function (props: U) { + return ; +} + +const decorator1 = function (props: U) { + return ; +} + +//// [file.jsx] +"use strict"; +exports.__esModule = true; +var React = require("react"); +var decorator = function (props) { + return ; +}; +var decorator1 = function (props) { + return ; +}; diff --git a/tests/baselines/reference/tsxGenericAttributesType7.symbols b/tests/baselines/reference/tsxGenericAttributesType7.symbols new file mode 100644 index 00000000000..e31042bd843 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType7.symbols @@ -0,0 +1,35 @@ +=== tests/cases/conformance/jsx/file.tsx === +import React = require('react'); +>React : Symbol(React, Decl(file.tsx, 0, 0)) + +declare function Component(props: T) : JSX.Element; +>Component : Symbol(Component, Decl(file.tsx, 0, 32)) +>T : Symbol(T, Decl(file.tsx, 2, 27)) +>props : Symbol(props, Decl(file.tsx, 2, 30)) +>T : Symbol(T, Decl(file.tsx, 2, 27)) +>JSX : Symbol(JSX, Decl(react.d.ts, 2352, 1)) +>Element : Symbol(JSX.Element, Decl(react.d.ts, 2355, 27)) + +const decorator = function (props: U) { +>decorator : Symbol(decorator, Decl(file.tsx, 3, 5)) +>U : Symbol(U, Decl(file.tsx, 3, 28)) +>props : Symbol(props, Decl(file.tsx, 3, 31)) +>U : Symbol(U, Decl(file.tsx, 3, 28)) + + return ; +>Component : Symbol(Component, Decl(file.tsx, 0, 32)) +>props : Symbol(props, Decl(file.tsx, 3, 31)) +} + +const decorator1 = function (props: U) { +>decorator1 : Symbol(decorator1, Decl(file.tsx, 7, 5)) +>U : Symbol(U, Decl(file.tsx, 7, 29)) +>x : Symbol(x, Decl(file.tsx, 7, 40)) +>props : Symbol(props, Decl(file.tsx, 7, 52)) +>U : Symbol(U, Decl(file.tsx, 7, 29)) + + return ; +>Component : Symbol(Component, Decl(file.tsx, 0, 32)) +>props : Symbol(props, Decl(file.tsx, 7, 52)) +>x : Symbol(x, Decl(file.tsx, 8, 32)) +} diff --git a/tests/baselines/reference/tsxGenericAttributesType7.types b/tests/baselines/reference/tsxGenericAttributesType7.types new file mode 100644 index 00000000000..58d853e4881 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType7.types @@ -0,0 +1,39 @@ +=== tests/cases/conformance/jsx/file.tsx === +import React = require('react'); +>React : typeof React + +declare function Component(props: T) : JSX.Element; +>Component : (props: T) => JSX.Element +>T : T +>props : T +>T : T +>JSX : any +>Element : JSX.Element + +const decorator = function (props: U) { +>decorator : (props: U) => JSX.Element +>function (props: U) { return ;} : (props: U) => JSX.Element +>U : U +>props : U +>U : U + + return ; +> : JSX.Element +>Component : (props: T) => JSX.Element +>props : U +} + +const decorator1 = function (props: U) { +>decorator1 : (props: U) => JSX.Element +>function (props: U) { return ;} : (props: U) => JSX.Element +>U : U +>x : string +>props : U +>U : U + + return ; +> : JSX.Element +>Component : (props: T) => JSX.Element +>props : U +>x : string +} diff --git a/tests/baselines/reference/tsxGenericAttributesType8.js b/tests/baselines/reference/tsxGenericAttributesType8.js new file mode 100644 index 00000000000..435bf425c6a --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType8.js @@ -0,0 +1,22 @@ +//// [file.tsx] +import React = require('react'); + +declare function Component(props: T) : JSX.Element; +const decorator = function (props: U) { + return ; +} + +const decorator1 = function (props: U) { + return ; +} + +//// [file.jsx] +"use strict"; +exports.__esModule = true; +var React = require("react"); +var decorator = function (props) { + return ; +}; +var decorator1 = function (props) { + return ; +}; diff --git a/tests/baselines/reference/tsxGenericAttributesType8.symbols b/tests/baselines/reference/tsxGenericAttributesType8.symbols new file mode 100644 index 00000000000..516dbe9c322 --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType8.symbols @@ -0,0 +1,34 @@ +=== tests/cases/conformance/jsx/file.tsx === +import React = require('react'); +>React : Symbol(React, Decl(file.tsx, 0, 0)) + +declare function Component(props: T) : JSX.Element; +>Component : Symbol(Component, Decl(file.tsx, 0, 32)) +>T : Symbol(T, Decl(file.tsx, 2, 27)) +>props : Symbol(props, Decl(file.tsx, 2, 30)) +>T : Symbol(T, Decl(file.tsx, 2, 27)) +>JSX : Symbol(JSX, Decl(react.d.ts, 2352, 1)) +>Element : Symbol(JSX.Element, Decl(react.d.ts, 2355, 27)) + +const decorator = function (props: U) { +>decorator : Symbol(decorator, Decl(file.tsx, 3, 5)) +>U : Symbol(U, Decl(file.tsx, 3, 28)) +>props : Symbol(props, Decl(file.tsx, 3, 31)) +>U : Symbol(U, Decl(file.tsx, 3, 28)) + + return ; +>Component : Symbol(Component, Decl(file.tsx, 0, 32)) +>props : Symbol(props, Decl(file.tsx, 3, 31)) +} + +const decorator1 = function (props: U) { +>decorator1 : Symbol(decorator1, Decl(file.tsx, 7, 5)) +>U : Symbol(U, Decl(file.tsx, 7, 29)) +>x : Symbol(x, Decl(file.tsx, 7, 40)) +>props : Symbol(props, Decl(file.tsx, 7, 52)) +>U : Symbol(U, Decl(file.tsx, 7, 29)) + + return ; +>Component : Symbol(Component, Decl(file.tsx, 0, 32)) +>props : Symbol(props, Decl(file.tsx, 7, 52)) +} diff --git a/tests/baselines/reference/tsxGenericAttributesType8.types b/tests/baselines/reference/tsxGenericAttributesType8.types new file mode 100644 index 00000000000..b2a62a575ff --- /dev/null +++ b/tests/baselines/reference/tsxGenericAttributesType8.types @@ -0,0 +1,38 @@ +=== tests/cases/conformance/jsx/file.tsx === +import React = require('react'); +>React : typeof React + +declare function Component(props: T) : JSX.Element; +>Component : (props: T) => JSX.Element +>T : T +>props : T +>T : T +>JSX : any +>Element : JSX.Element + +const decorator = function (props: U) { +>decorator : (props: U) => JSX.Element +>function (props: U) { return ;} : (props: U) => JSX.Element +>U : U +>props : U +>U : U + + return ; +> : JSX.Element +>Component : (props: T) => JSX.Element +>props : U +} + +const decorator1 = function (props: U) { +>decorator1 : (props: U) => JSX.Element +>function (props: U) { return ;} : (props: U) => JSX.Element +>U : U +>x : string +>props : U +>U : U + + return ; +> : JSX.Element +>Component : (props: T) => JSX.Element +>props : U +} diff --git a/tests/baselines/reference/tsxStatelessFunctionComponents3.types b/tests/baselines/reference/tsxStatelessFunctionComponents3.types index 714781058eb..879f8c75cdf 100644 --- a/tests/baselines/reference/tsxStatelessFunctionComponents3.types +++ b/tests/baselines/reference/tsxStatelessFunctionComponents3.types @@ -21,8 +21,8 @@ var MainMenu: React.StatelessComponent<{}> = (props) => (
>MainMenu : React.StatelessComponent<{}> >React : any >StatelessComponent : React.StatelessComponent

->(props) => (

Main Menu

) : (props: {}) => JSX.Element ->props : {} +>(props) => (

Main Menu

) : (props: { children?: React.ReactNode; }) => JSX.Element +>props : { children?: React.ReactNode; } >(

Main Menu

) : JSX.Element >

Main Menu

: JSX.Element >div : any @@ -40,7 +40,7 @@ var App: React.StatelessComponent<{ children }> = ({children}) => ( >React : any >StatelessComponent : React.StatelessComponent

>children : any ->({children}) => (

) : ({children}: { children: any; }) => JSX.Element +>({children}) => (
) : ({children}: { children: any; } & { children?: React.ReactNode; }) => JSX.Element >children : any >(
) : JSX.Element diff --git a/tests/cases/conformance/jsx/tsxGenericAttributesType5.tsx b/tests/cases/conformance/jsx/tsxGenericAttributesType5.tsx new file mode 100644 index 00000000000..d0215da7397 --- /dev/null +++ b/tests/cases/conformance/jsx/tsxGenericAttributesType5.tsx @@ -0,0 +1,19 @@ +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); + +class B1 extends React.Component { + render() { + return
hi
; + } +} +class B extends React.Component { + props: U; + render() { + // Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object + return ; + } +} \ No newline at end of file diff --git a/tests/cases/conformance/jsx/tsxGenericAttributesType6.tsx b/tests/cases/conformance/jsx/tsxGenericAttributesType6.tsx new file mode 100644 index 00000000000..d70df8a0cf7 --- /dev/null +++ b/tests/cases/conformance/jsx/tsxGenericAttributesType6.tsx @@ -0,0 +1,18 @@ +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); + +class B1 extends React.Component { + render() { + return
hi
; + } +} +class B extends React.Component { + props: U; + render() { + return ; + } +} \ No newline at end of file diff --git a/tests/cases/conformance/jsx/tsxGenericAttributesType7.tsx b/tests/cases/conformance/jsx/tsxGenericAttributesType7.tsx new file mode 100644 index 00000000000..3044fda23df --- /dev/null +++ b/tests/cases/conformance/jsx/tsxGenericAttributesType7.tsx @@ -0,0 +1,15 @@ +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); + +declare function Component(props: T) : JSX.Element; +const decorator = function (props: U) { + return ; +} + +const decorator1 = function (props: U) { + return ; +} \ No newline at end of file diff --git a/tests/cases/conformance/jsx/tsxGenericAttributesType8.tsx b/tests/cases/conformance/jsx/tsxGenericAttributesType8.tsx new file mode 100644 index 00000000000..b1d3a7445c8 --- /dev/null +++ b/tests/cases/conformance/jsx/tsxGenericAttributesType8.tsx @@ -0,0 +1,15 @@ +// @filename: file.tsx +// @jsx: preserve +// @noLib: true +// @libFiles: react.d.ts,lib.d.ts + +import React = require('react'); + +declare function Component(props: T) : JSX.Element; +const decorator = function (props: U) { + return ; +} + +const decorator1 = function (props: U) { + return ; +} \ No newline at end of file From f45f7579fc33617b9595a441823dce22e2c0c32a Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 11 May 2017 23:51:20 -0700 Subject: [PATCH 25/34] Adds CommaList to avoid large deeply nested comma expressions --- src/compiler/emitter.ts | 8 ++++++++ src/compiler/factory.ts | 16 +++++++++++++++- src/compiler/parser.ts | 2 ++ src/compiler/types.ts | 6 ++++++ src/compiler/utilities.ts | 4 ++++ src/compiler/visitor.ts | 8 ++++++++ 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 669c9f5dd36..c0d8cae861f 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -733,6 +733,9 @@ namespace ts { // Transformation nodes case SyntaxKind.PartiallyEmittedExpression: return emitPartiallyEmittedExpression(node); + + case SyntaxKind.CommaList: + return emitCommaList(node); } } @@ -2101,6 +2104,10 @@ namespace ts { emitExpression(node.expression); } + function emitCommaList(node: CommaList) { + emitExpressionList(node, node.elements, ListFormat.CommaListElements); + } + /** * Emits any prologue directives at the start of a Statement list, returning the * number of prologue directives written to the output. @@ -2951,6 +2958,7 @@ namespace ts { ArrayBindingPatternElements = SingleLine | AllowTrailingComma | CommaDelimited | SpaceBetweenSiblings, ObjectLiteralExpressionProperties = PreserveLines | CommaDelimited | SpaceBetweenSiblings | SpaceBetweenBraces | Indented | Braces, ArrayLiteralExpressionElements = PreserveLines | CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | Indented | SquareBrackets, + CommaListElements = CommaDelimited | SpaceBetweenSiblings | SingleLine, CallExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis, NewExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis | OptionalIfUndefined, TemplateExpressionSpans = SingleLine | NoInterveningComments, diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 2b781b6dbb3..7f84ffd534f 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2077,6 +2077,18 @@ namespace ts { return node; } + export function createCommaList(elements: Expression[]) { + const node = createSynthesizedNode(SyntaxKind.CommaList); + node.elements = createNodeArray(elements); + return node; + } + + export function updateCommaList(node: CommaList, elements: Expression[]) { + return node.elements !== elements + ? updateNode(createCommaList(elements), node) + : node; + } + export function createBundle(sourceFiles: SourceFile[]) { const node = createNode(SyntaxKind.Bundle); node.sourceFiles = sourceFiles; @@ -2865,7 +2877,9 @@ namespace ts { } export function inlineExpressions(expressions: Expression[]) { - return reduceLeft(expressions, createComma); + return expressions.length > 10 + ? createCommaList(expressions) + : reduceLeft(expressions, createComma); } export function createExpressionFromEntityName(node: EntityName | Expression): Expression { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index cc3fb24bdb8..1e8f7a1c904 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -362,6 +362,8 @@ namespace ts { return visitNode(cbNode, (node).expression); case SyntaxKind.MissingDeclaration: return visitNodes(cbNodes, node.decorators); + case SyntaxKind.CommaList: + return visitNodes(cbNodes, (node).elements); case SyntaxKind.JsxElement: return visitNode(cbNode, (node).openingElement) || diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a36378ade1d..5ad89bc469e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -391,6 +391,7 @@ namespace ts { PartiallyEmittedExpression, MergeDeclarationMarker, EndOfDeclarationMarker, + CommaList, // Enum value count Count, @@ -1603,6 +1604,11 @@ namespace ts { kind: SyntaxKind.EndOfDeclarationMarker; } + export interface CommaList extends Expression { + kind: SyntaxKind.CommaList; + elements: NodeArray; + } + /** * Marks the beginning of a merged transformed declaration. */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 95b76286c59..ae54f9d3e76 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2327,6 +2327,9 @@ namespace ts { case SyntaxKind.SpreadElement: return 1; + case SyntaxKind.CommaList: + return 0; + default: return -1; } @@ -3915,6 +3918,7 @@ namespace ts { || kind === SyntaxKind.SpreadElement || kind === SyntaxKind.AsExpression || kind === SyntaxKind.OmittedExpression + || kind === SyntaxKind.CommaList || isUnaryExpressionKind(kind); } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index e434dadeaf7..7788c313139 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -876,6 +876,10 @@ namespace ts { return updatePartiallyEmittedExpression(node, visitNode((node).expression, visitor, isExpression)); + case SyntaxKind.CommaList: + return updateCommaList(node, + nodesVisitor((node).elements, visitor, isExpression)); + default: // No need to visit nodes with no children. return node; @@ -1389,6 +1393,10 @@ namespace ts { result = reduceNode((node).expression, cbNode, result); break; + case SyntaxKind.CommaList: + result = reduceNodes((node).elements, cbNodes, result); + break; + default: break; } From 22cf036ed91e638220520bb5ff5b167ee723180c Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 12 May 2017 09:57:39 -0700 Subject: [PATCH 26/34] Clean up naming, add documentation, flatten (some) nested commas --- src/compiler/core.ts | 29 +++++++++++++++++++++++++++++ src/compiler/emitter.ts | 6 +++--- src/compiler/factory.ts | 22 ++++++++++++++++++---- src/compiler/parser.ts | 4 ++-- src/compiler/types.ts | 9 ++++++--- src/compiler/utilities.ts | 4 ++-- src/compiler/visitor.ts | 10 +++++----- 7 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 321cc06f765..5e4afce98c8 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -490,6 +490,35 @@ namespace ts { return result; } + /** + * Maps an array. If the mapped value is an array, it is spread into the result. + * Avoids allocation if all elements map to themselves. + * + * @param array The array to map. + * @param mapfn The callback used to map the result into one or more values. + */ + export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | T[]): T[] { + let result: T[]; + if (array) { + for (let i = 0; i < array.length; i++) { + const item = array[i]; + const mapped = mapfn(item, i); + if (result || item !== mapped || isArray(mapped)) { + if (!result) { + result = array.slice(0, i); + } + if (isArray(mapped)) { + addRange(result, mapped); + } + else { + result.push(mapped); + } + } + } + } + return result || array; + } + /** * Computes the first matching span of elements and returns a tuple of the first span * and the remaining elements. diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c0d8cae861f..76423e45bd9 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -734,8 +734,8 @@ namespace ts { case SyntaxKind.PartiallyEmittedExpression: return emitPartiallyEmittedExpression(node); - case SyntaxKind.CommaList: - return emitCommaList(node); + case SyntaxKind.CommaListExpression: + return emitCommaList(node); } } @@ -2104,7 +2104,7 @@ namespace ts { emitExpression(node.expression); } - function emitCommaList(node: CommaList) { + function emitCommaList(node: CommaListExpression) { emitExpressionList(node, node.elements, ListFormat.CommaListElements); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 7f84ffd534f..42f6a1267bc 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2077,13 +2077,25 @@ namespace ts { return node; } - export function createCommaList(elements: Expression[]) { - const node = createSynthesizedNode(SyntaxKind.CommaList); - node.elements = createNodeArray(elements); + function flattenCommaElements(node: Expression): Expression | Expression[] { + if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { + if (node.kind === SyntaxKind.CommaListExpression) { + return (node).elements; + } + if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) { + return [node.left, node.right]; + } + } return node; } - export function updateCommaList(node: CommaList, elements: Expression[]) { + export function createCommaList(elements: Expression[]) { + const node = createSynthesizedNode(SyntaxKind.CommaListExpression); + node.elements = createNodeArray(sameFlatMap(elements, flattenCommaElements)); + return node; + } + + export function updateCommaList(node: CommaListExpression, elements: Expression[]) { return node.elements !== elements ? updateNode(createCommaList(elements), node) : node; @@ -2877,6 +2889,8 @@ namespace ts { } export function inlineExpressions(expressions: Expression[]) { + // Avoid deeply nested comma expressions as traversing them during emit can result in "Maximum call + // stack size exceeded" errors. return expressions.length > 10 ? createCommaList(expressions) : reduceLeft(expressions, createComma); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1e8f7a1c904..dd189784a17 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -362,8 +362,8 @@ namespace ts { return visitNode(cbNode, (node).expression); case SyntaxKind.MissingDeclaration: return visitNodes(cbNodes, node.decorators); - case SyntaxKind.CommaList: - return visitNodes(cbNodes, (node).elements); + case SyntaxKind.CommaListExpression: + return visitNodes(cbNodes, (node).elements); case SyntaxKind.JsxElement: return visitNode(cbNode, (node).openingElement) || diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5ad89bc469e..f38ec9abd9c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -389,9 +389,9 @@ namespace ts { // Transformation nodes NotEmittedStatement, PartiallyEmittedExpression, + CommaListExpression, MergeDeclarationMarker, EndOfDeclarationMarker, - CommaList, // Enum value count Count, @@ -1604,8 +1604,11 @@ namespace ts { kind: SyntaxKind.EndOfDeclarationMarker; } - export interface CommaList extends Expression { - kind: SyntaxKind.CommaList; + /** + * A list of comma-seperated expressions. This node is only created by transformations. + */ + export interface CommaListExpression extends Expression { + kind: SyntaxKind.CommaListExpression; elements: NodeArray; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ae54f9d3e76..1a981a0844f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2327,7 +2327,7 @@ namespace ts { case SyntaxKind.SpreadElement: return 1; - case SyntaxKind.CommaList: + case SyntaxKind.CommaListExpression: return 0; default: @@ -3918,7 +3918,7 @@ namespace ts { || kind === SyntaxKind.SpreadElement || kind === SyntaxKind.AsExpression || kind === SyntaxKind.OmittedExpression - || kind === SyntaxKind.CommaList + || kind === SyntaxKind.CommaListExpression || isUnaryExpressionKind(kind); } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 7788c313139..517854e21ce 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -876,9 +876,9 @@ namespace ts { return updatePartiallyEmittedExpression(node, visitNode((node).expression, visitor, isExpression)); - case SyntaxKind.CommaList: - return updateCommaList(node, - nodesVisitor((node).elements, visitor, isExpression)); + case SyntaxKind.CommaListExpression: + return updateCommaList(node, + nodesVisitor((node).elements, visitor, isExpression)); default: // No need to visit nodes with no children. @@ -1393,8 +1393,8 @@ namespace ts { result = reduceNode((node).expression, cbNode, result); break; - case SyntaxKind.CommaList: - result = reduceNodes((node).elements, cbNodes, result); + case SyntaxKind.CommaListExpression: + result = reduceNodes((node).elements, cbNodes, result); break; default: From aaf6b83cb573c33c838f85a05d5029135a620b7c Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 12 May 2017 07:32:00 -0700 Subject: [PATCH 27/34] Don't goto aliased symbol with no declarations; and update tests --- src/services/goToDefinition.ts | 5 ++++- tests/cases/fourslash/ambientShorthandGotoDefinition.ts | 4 ++-- tests/cases/fourslash/goToDefinition_untypedModule.ts | 6 +++--- tests/cases/fourslash/quickInfoMeaning.ts | 8 ++++---- .../fourslash/tsxGoToDefinitionClassInDifferentFile.ts | 4 ++-- tests/cases/fourslash/untypedModuleImport.ts | 2 +- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 946924bcf87..d0901de9a47 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -51,7 +51,10 @@ namespace ts.GoToDefinition { // import {A, B} from "mod"; // to jump to the implementation directly. if (symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) { - symbol = typeChecker.getAliasedSymbol(symbol); + const aliased = typeChecker.getAliasedSymbol(symbol); + if (aliased.declarations) { + symbol = aliased; + } } // Because name in short-hand property assignment has two different meanings: property name and property value, diff --git a/tests/cases/fourslash/ambientShorthandGotoDefinition.ts b/tests/cases/fourslash/ambientShorthandGotoDefinition.ts index 410fc932cec..74e348beb74 100644 --- a/tests/cases/fourslash/ambientShorthandGotoDefinition.ts +++ b/tests/cases/fourslash/ambientShorthandGotoDefinition.ts @@ -12,7 +12,7 @@ verify.quickInfoAt("useFoo", "import foo"); verify.goToDefinition({ - useFoo: "importFoo", + useFoo: "module", importFoo: "module" }); @@ -27,6 +27,6 @@ verify.goToDefinition({ verify.quickInfoAt("useBang", "import bang = require(\"jquery\")"); verify.goToDefinition({ - useBang: "importBang", + useBang: "module", importBang: "module" }); diff --git a/tests/cases/fourslash/goToDefinition_untypedModule.ts b/tests/cases/fourslash/goToDefinition_untypedModule.ts index b4571438434..fe29f7104cf 100644 --- a/tests/cases/fourslash/goToDefinition_untypedModule.ts +++ b/tests/cases/fourslash/goToDefinition_untypedModule.ts @@ -4,7 +4,7 @@ ////not read // @Filename: /a.ts -////import { f } from "foo"; -/////**/f(); +////import { /*def*/f } from "foo"; +/////*use*/f(); -verify.goToDefinition("", []); +verify.goToDefinition("use", "def"); diff --git a/tests/cases/fourslash/quickInfoMeaning.ts b/tests/cases/fourslash/quickInfoMeaning.ts index a3b9f988e1d..2c7aa5a0ccd 100644 --- a/tests/cases/fourslash/quickInfoMeaning.ts +++ b/tests/cases/fourslash/quickInfoMeaning.ts @@ -8,13 +8,13 @@ // @Filename: foo.d.ts ////declare const /*foo_value_declaration*/foo: number; ////declare module "foo_module" { -//// interface I { x: number; y: number } +//// interface /*foo_type_declaration*/I { x: number; y: number } //// export = I; ////} // @Filename: foo_user.ts /////// -////import /*foo_type_declaration*/foo = require("foo_module"); +////import foo = require("foo_module"); ////const x = foo/*foo_value*/; ////const i: foo/*foo_type*/ = { x: 1, y: 2 }; @@ -39,13 +39,13 @@ verify.goToDefinitionIs("foo_type_declaration"); // @Filename: bar.d.ts ////declare interface /*bar_type_declaration*/bar { x: number; y: number } ////declare module "bar_module" { -//// const x: number; +//// const /*bar_value_declaration*/x: number; //// export = x; ////} // @Filename: bar_user.ts /////// -////import /*bar_value_declaration*/bar = require("bar_module"); +////import bar = require("bar_module"); ////const x = bar/*bar_value*/; ////const i: bar/*bar_type*/ = { x: 1, y: 2 }; diff --git a/tests/cases/fourslash/tsxGoToDefinitionClassInDifferentFile.ts b/tests/cases/fourslash/tsxGoToDefinitionClassInDifferentFile.ts index f217e4096fc..7aa1c11fdc3 100644 --- a/tests/cases/fourslash/tsxGoToDefinitionClassInDifferentFile.ts +++ b/tests/cases/fourslash/tsxGoToDefinitionClassInDifferentFile.ts @@ -3,10 +3,10 @@ // @jsx: preserve // @Filename: C.tsx -////export default class C {} +////export default class /*def*/C {} // @Filename: a.tsx -////import /*def*/C from "./C"; +////import C from "./C"; ////const foo = ; verify.noErrors(); diff --git a/tests/cases/fourslash/untypedModuleImport.ts b/tests/cases/fourslash/untypedModuleImport.ts index 4dac6ac76bb..5501cec4792 100644 --- a/tests/cases/fourslash/untypedModuleImport.ts +++ b/tests/cases/fourslash/untypedModuleImport.ts @@ -17,6 +17,6 @@ const [r0, r1, r2] = test.ranges(); verify.singleReferenceGroup('"foo"', [r1]); goTo.marker("foo"); -verify.goToDefinitionIs([]); +verify.goToDefinitionIs("foo"); verify.quickInfoIs("import foo"); verify.singleReferenceGroup("import foo", [r0, r2]); From 8907c70a86843a3bc0f17c0cd815da017cafb950 Mon Sep 17 00:00:00 2001 From: Yui T Date: Fri, 12 May 2017 11:54:23 -0700 Subject: [PATCH 28/34] Address PR --- src/compiler/checker.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 03302ec5f32..14e789f17ae 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13549,17 +13549,17 @@ namespace ts { } function getApparentTypeOfJsxPropsType(propsType: Type): Type { - if (propsType) { - if (propsType.flags & TypeFlags.Intersection) { - const propsApprentType: Type[] = []; - for (const t of (propsType).types) { - propsApprentType.push(getApparentType(t)); - } - return getIntersectionType(propsApprentType); - } - return getApparentType(propsType); + if (!propsType) { + return undefined; } - return propsType; + if (propsType.flags & TypeFlags.Intersection) { + const propsApparentType: Type[] = []; + for (const t of (propsType).types) { + propsApparentType.push(getApparentType(t)); + } + return getIntersectionType(propsApparentType); + } + return getApparentType(propsType); } /** From 99ea9c730f415ed150693f42acbd5f769b3ee012 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 11 May 2017 13:59:06 -0700 Subject: [PATCH 29/34] Add the members of interfaces that need to be implemented to class element completion --- src/services/completions.ts | 45 +- .../completionEntryForClassMembers.ts | 45 +- .../completionEntryForClassMembers2.ts | 456 ++++++++++++++++++ 3 files changed, 533 insertions(+), 13 deletions(-) create mode 100644 tests/cases/fourslash/completionEntryForClassMembers2.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 6be420c0919..1d7d27f92f4 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -937,7 +937,8 @@ namespace ts.Completions { hasFilteredClassMemberKeywords = true; const baseTypeNode = getClassExtendsHeritageClauseElement(classLikeDeclaration); - if (baseTypeNode) { + const implementsTypeNodes = getClassImplementsHeritageClauseElements(classLikeDeclaration); + if (baseTypeNode || implementsTypeNodes) { const classElement = contextToken.parent; let classElementModifierFlags = isClassElement(classElement) && getModifierFlags(classElement); // If this is context token is not something we are editing now, consider if this would lead to be modifier @@ -954,13 +955,27 @@ namespace ts.Completions { // No member list for private methods if (!(classElementModifierFlags & ModifierFlags.Private)) { - const baseType = typeChecker.getTypeAtLocation(baseTypeNode); - const typeToGetPropertiesFrom = (classElementModifierFlags & ModifierFlags.Static) ? - typeChecker.getTypeOfSymbolAtLocation(baseType.symbol, classLikeDeclaration) : - baseType; + let baseClassTypeToGetPropertiesFrom: Type; + if (baseTypeNode) { + baseClassTypeToGetPropertiesFrom = typeChecker.getTypeAtLocation(baseTypeNode); + if (classElementModifierFlags & ModifierFlags.Static) { + // Use static class to get property symbols from + baseClassTypeToGetPropertiesFrom = typeChecker.getTypeOfSymbolAtLocation( + baseClassTypeToGetPropertiesFrom.symbol, classLikeDeclaration); + } + } + const implementedInterfaceTypePropertySymbols = (classElementModifierFlags & ModifierFlags.Static) ? + undefined : + flatMap(implementsTypeNodes, typeNode => typeChecker.getPropertiesOfType(typeChecker.getTypeAtLocation(typeNode))); // List of property symbols of base type that are not private and already implemented - symbols = filterClassMembersList(typeChecker.getPropertiesOfType(typeToGetPropertiesFrom), classLikeDeclaration.members, classElementModifierFlags); + symbols = filterClassMembersList( + baseClassTypeToGetPropertiesFrom ? + typeChecker.getPropertiesOfType(baseClassTypeToGetPropertiesFrom) : + undefined, + implementedInterfaceTypePropertySymbols, + classLikeDeclaration.members, + classElementModifierFlags); } } } @@ -1334,7 +1349,7 @@ namespace ts.Completions { * * @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags */ - function filterClassMembersList(baseSymbols: Symbol[], existingMembers: ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] { + function filterClassMembersList(baseSymbols: Symbol[], implementingTypeSymbols: Symbol[], existingMembers: ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] { const existingMemberNames = createMap(); for (const m of existingMembers) { // Ignore omitted expressions for missing members @@ -1358,7 +1373,7 @@ namespace ts.Completions { // do not filter it out if the static presence doesnt match const mIsStatic = hasModifier(m, ModifierFlags.Static); const currentElementIsStatic = !!(currentClassElementModifierFlags & ModifierFlags.Static); - if ((mIsStatic && currentElementIsStatic) || + if ((mIsStatic && !currentElementIsStatic) || (!mIsStatic && currentElementIsStatic)) { continue; } @@ -1369,10 +1384,16 @@ namespace ts.Completions { } } - return filter(baseSymbols, baseProperty => - !existingMemberNames.get(baseProperty.name) && - baseProperty.getDeclarations() && - !(getDeclarationModifierFlagsFromSymbol(baseProperty) & ModifierFlags.Private)); + return concatenate( + filter(baseSymbols, baseProperty => isValidProperty(baseProperty, ModifierFlags.Private)), + filter(implementingTypeSymbols, implementingProperty => isValidProperty(implementingProperty, ModifierFlags.NonPublicAccessibilityModifier)) + ); + + function isValidProperty(propertySymbol: Symbol, inValidModifierFlags: ModifierFlags) { + return !existingMemberNames.get(propertySymbol.name) && + propertySymbol.getDeclarations() && + !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & inValidModifierFlags); + } } /** diff --git a/tests/cases/fourslash/completionEntryForClassMembers.ts b/tests/cases/fourslash/completionEntryForClassMembers.ts index b242ca7ed78..527611b85bf 100644 --- a/tests/cases/fourslash/completionEntryForClassMembers.ts +++ b/tests/cases/fourslash/completionEntryForClassMembers.ts @@ -30,6 +30,18 @@ //// } //// /*classThatHasDifferentMethodThanBaseAfterProtectedMethod*/ ////} +////class D3 extends D1 { +//// /*classThatExtendsClassExtendingAnotherClass*/ +////} +////class D4 extends D1 { +//// static /*classThatExtendsClassExtendingAnotherClassAndTypesStatic*/ +////} +////class D5 extends D2 { +//// /*classThatExtendsClassExtendingAnotherClassWithOverridingMember*/ +////} +////class D6 extends D2 { +//// static /*classThatExtendsClassExtendingAnotherClassWithOverridingMemberAndTypesStatic*/ +////} ////class E { //// /*classThatDoesNotExtendAnotherClass*/ ////} @@ -127,6 +139,12 @@ const allMembersOfBase: CompletionInfo[] = [ ["privateMethod", "(method) B.privateMethod(): void"], ["staticMethod", "(method) B.staticMethod(): void"] ]; +const publicCompletionInfoOfD1: CompletionInfo[] = [ + ["getValue1", "(method) D1.getValue1(): number"] +]; +const publicCompletionInfoOfD2: CompletionInfo[] = [ + ["protectedMethod", "(method) D2.protectedMethod(): void"] +]; function filterCompletionInfo(fn: (a: CompletionInfo) => boolean): CompletionInfoVerifier { const validMembers: CompletionInfo[] = []; const invalidMembers: CompletionInfo[] = []; @@ -147,6 +165,19 @@ const staticMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "st const instanceWithoutProtectedMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "getValue"); const instanceWithoutPublicMemberInfo = filterCompletionInfo(([a]: CompletionInfo) => a === "protectedMethod"); +const instanceMemberInfoD1: CompletionInfoVerifier = { + validMembers: instanceMemberInfo.validMembers.concat(publicCompletionInfoOfD1), + invalidMembers: instanceMemberInfo.invalidMembers +}; +const instanceMemberInfoD2: CompletionInfoVerifier = { + validMembers: instanceWithoutProtectedMemberInfo.validMembers.concat(publicCompletionInfoOfD2), + invalidMembers: instanceWithoutProtectedMemberInfo.invalidMembers +}; +const staticMemberInfoDn: CompletionInfoVerifier = { + validMembers: staticMemberInfo.validMembers, + invalidMembers: staticMemberInfo.invalidMembers.concat(publicCompletionInfoOfD1, publicCompletionInfoOfD2) +}; + // Not a class element declaration location const nonClassElementMarkers = [ "InsideMethod" @@ -210,4 +241,16 @@ const classInstanceElementWithoutProtectedMethodLocations = [ "classThatHasAlreadyImplementedAnotherClassProtectedMethod", "classThatHasDifferentMethodThanBaseAfterProtectedMethod", ]; -verifyClassElementLocations(instanceWithoutProtectedMemberInfo, classInstanceElementWithoutProtectedMethodLocations); \ No newline at end of file +verifyClassElementLocations(instanceWithoutProtectedMemberInfo, classInstanceElementWithoutProtectedMethodLocations); + +// instance memebers in D1 and base class are shown +verifyClassElementLocations(instanceMemberInfoD1, ["classThatExtendsClassExtendingAnotherClass"]); + +// instance memebers in D2 and base class are shown +verifyClassElementLocations(instanceMemberInfoD2, ["classThatExtendsClassExtendingAnotherClassWithOverridingMember"]); + +// static base members and class member keywords allowed +verifyClassElementLocations(staticMemberInfoDn, [ + "classThatExtendsClassExtendingAnotherClassAndTypesStatic", + "classThatExtendsClassExtendingAnotherClassWithOverridingMemberAndTypesStatic" +]); \ No newline at end of file diff --git a/tests/cases/fourslash/completionEntryForClassMembers2.ts b/tests/cases/fourslash/completionEntryForClassMembers2.ts new file mode 100644 index 00000000000..539702196a3 --- /dev/null +++ b/tests/cases/fourslash/completionEntryForClassMembers2.ts @@ -0,0 +1,456 @@ +/// + +////interface I { +//// methodOfInterface(): number; +////} +////interface I2 { +//// methodOfInterface2(): number; +////} +////interface I3 { +//// getValue(): string; +//// method(): string; +////} +////interface I4 { +//// staticMethod(): void; +//// method(): string; +////} +////class B0 { +//// private privateMethod() { } +//// protected protectedMethod() { } +//// static staticMethod() { } +//// getValue(): string | boolean { return "hello"; } +//// private privateMethod1() { } +//// protected protectedMethod1() { } +//// static staticMethod1() { } +//// getValue1(): string | boolean { return "hello"; } +////} +////interface I5 extends B0 { +//// methodOfInterface5(): number; +////} +////interface I6 extends B0 { +//// methodOfInterface6(): number; +//// staticMethod(): void; +////} +////interface I7 extends I { +//// methodOfInterface7(): number; +////} +////class B { +//// private privateMethod() { } +//// protected protectedMethod() { } +//// static staticMethod() { } +//// getValue(): string | boolean { return "hello"; } +////} +////class C0 implements I, I2 { +//// /*implementsIAndI2*/ +////} +////class C00 implements I, I2 { +//// static /*implementsIAndI2AndWritingStatic*/ +////} +////class C001 implements I, I2 { +//// methodOfInterface/*implementsIAndI2AndWritingMethodNameOfI*/ +////} +////class C extends B implements I, I2 { +//// /*extendsBAndImplementsIAndI2*/ +////} +////class C1 extends B implements I, I2 { +//// static /*extendsBAndImplementsIAndI2AndWritingStatic*/ +////} +////class D extends B implements I, I2 { +//// /*extendsBAndImplementsIAndI2WithMethodFromB*/ +//// protected protectedMethod() { +//// return "protected"; +//// } +////} +////class E extends B implements I, I2 { +//// /*extendsBAndImplementsIAndI2WithMethodFromI*/ +//// methodOfInterface() { +//// return 1; +//// } +////} +////class F extends B implements I, I2 { +//// /*extendsBAndImplementsIAndI2WithMethodFromBAndI*/ +//// protected protectedMethod() { +//// return "protected" +//// } +//// methodOfInterface() { +//// return 1; +//// } +////} +////class F2 extends B implements I, I2 { +//// protected protectedMethod() { +//// return "protected" +//// } +//// methodOfInterface() { +//// return 1; +//// } +//// static /*extendsBAndImplementsIAndI2WithMethodFromBAndIAndTypesStatic*/ +////} +////class G extends B implements I3 { +//// /*extendsBAndImplementsI3WithSameNameMembers*/ +////} +////class H extends B implements I3 { +//// /*extendsBAndImplementsI3WithSameNameMembersAndHasImplementedTheMember*/ +//// getValue() { +//// return "hello"; +//// } +////} +////class J extends B0 implements I4 { +//// /*extendsB0ThatExtendsAndImplementsI4WithStaticMethod*/ +////} +////class L extends B0 implements I4 { +//// /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedAnotherMethod*/ +//// staticMethod2() { +//// return "hello"; +//// } +////} +////class K extends B0 implements I4 { +//// /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethod*/ +//// staticMethod() { +//// return "hello"; +//// } +////} +////class M extends B0 implements I4 { +//// /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsStatic*/ +//// static staticMethod() { +//// return "hello"; +//// } +////} +////class N extends B0 implements I4 { +//// /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsBoth*/ +//// staticMethod() { +//// return "hello"; +//// } +//// static staticMethod() { +//// return "hello"; +//// } +////} +////class J1 extends B0 implements I4 { +//// static /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodWritingStatic*/ +////} +////class L1 extends B0 implements I4 { +//// staticMethod2() { +//// return "hello"; +//// } +//// static /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedAnotherMethodWritingStatic*/ +////} +////class K1 extends B0 implements I4 { +//// staticMethod() { +//// return "hello"; +//// } +//// static /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodWritingStatic*/ +////} +////class M1 extends B0 implements I4 { +//// static staticMethod() { +//// return "hello"; +//// } +//// static /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsStaticWritingStatic*/ +////} +////class N1 extends B0 implements I4 { +//// staticMethod() { +//// return "hello"; +//// } +//// static staticMethod() { +//// return "hello"; +//// } +//// static /*extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsBothWritingStatic*/ +////} +////class O implements I7 { +//// /*implementsI7whichExtendsI*/ +////} +////class P implements I7, I { +//// /*implementsI7whichExtendsIAndAlsoImplementsI*/ +////} +////class Q implements I, I7 { +//// /*implementsIAndAlsoImplementsI7whichExtendsI*/ +////} +////class R implements I5 { +//// /*implementsI5ThatExtendsB0*/ +////} +////class S implements I6 { +//// /*implementsI6ThatExtendsB0AndHasStaticMethodOfB0*/ +////} +////class T extends B0 implements I5 { +//// /*extendsB0AndImplementsI5ThatExtendsB0*/ +////} +////class U extends B0 implements I6 { +//// /*extendsB0AndImplementsI6ThatExtendsB0AndHasStaticMethodOfB0*/ +////} +////class R1 implements I5 { +//// static /*implementsI5ThatExtendsB0TypesStatic*/ +////} +////class S1 implements I6 { +//// static /*implementsI6ThatExtendsB0AndHasStaticMethodOfB0TypesStatic*/ +////} +////class T1 extends B0 implements I5 { +//// static /*extendsB0AndImplementsI5ThatExtendsB0TypesStatic*/ +////} +////class U1 extends B0 implements I6 { +//// static /*extendsB0AndImplementsI6ThatExtendsB0AndHasStaticMethodOfB0TypesStatic*/ +////} + +const allowedKeywordCount = verify.allowedClassElementKeywords.length; +type CompletionInfo = [string, string]; +type CompletionInfoVerifier = { validMembers: CompletionInfo[], invalidMembers: CompletionInfo[] }; + +function verifyClassElementLocations({ validMembers, invalidMembers }: CompletionInfoVerifier, classElementCompletionLocations: string[]) { + for (const marker of classElementCompletionLocations) { + goTo.marker(marker); + verifyCompletionInfo(validMembers, verify); + verifyCompletionInfo(invalidMembers, verify.not); + verify.completionListContainsClassElementKeywords(); + verify.completionListCount(allowedKeywordCount + validMembers.length); + } +} + +function verifyCompletionInfo(memberInfo: CompletionInfo[], verify: FourSlashInterface.verifyNegatable) { + for (const [symbol, text] of memberInfo) { + verify.completionListContains(symbol, text, /*documentation*/ undefined, "method"); + } +} + +const validInstanceMembersOfBaseClassB: CompletionInfo[] = [ + ["getValue", "(method) B.getValue(): string | boolean"], + ["protectedMethod", "(method) B.protectedMethod(): void"], +]; +const validStaticMembersOfBaseClassB: CompletionInfo[] = [ + ["staticMethod", "(method) B.staticMethod(): void"] +]; +const privateMembersOfBaseClassB: CompletionInfo[] = [ + ["privateMethod", "(method) B.privateMethod(): void"], +]; +const protectedPropertiesOfBaseClassB0: CompletionInfo[] = [ + ["protectedMethod", "(method) B0.protectedMethod(): void"], + ["protectedMethod1", "(method) B0.protectedMethod1(): void"], +]; +const publicPropertiesOfBaseClassB0: CompletionInfo[] = [ + ["getValue", "(method) B0.getValue(): string | boolean"], + ["getValue1", "(method) B0.getValue1(): string | boolean"], +]; +const validInstanceMembersOfBaseClassB0: CompletionInfo[] = protectedPropertiesOfBaseClassB0.concat(publicPropertiesOfBaseClassB0); +const validStaticMembersOfBaseClassB0: CompletionInfo[] = [ + ["staticMethod", "(method) B0.staticMethod(): void"], + ["staticMethod1", "(method) B0.staticMethod1(): void"] +]; +const privateMembersOfBaseClassB0: CompletionInfo[] = [ + ["privateMethod", "(method) B0.privateMethod(): void"], + ["privateMethod1", "(method) B0.privateMethod1(): void"], +]; +const membersOfI: CompletionInfo[] = [ + ["methodOfInterface", "(method) I.methodOfInterface(): number"], +]; +const membersOfI2: CompletionInfo[] = [ + ["methodOfInterface2", "(method) I2.methodOfInterface2(): number"], +]; +const membersOfI3: CompletionInfo[] = [ + ["getValue", "(method) I3.getValue(): string"], + ["method", "(method) I3.method(): string"], +]; +const membersOfI4: CompletionInfo[] = [ + ["staticMethod", "(method) I4.staticMethod(): void"], + ["method", "(method) I4.method(): string"], +]; +const membersOfI5: CompletionInfo[] = publicPropertiesOfBaseClassB0.concat([ + ["methodOfInterface5", "(method) I5.methodOfInterface5(): number"] +]); +const membersOfI6: CompletionInfo[] = publicPropertiesOfBaseClassB0.concat([ + ["staticMethod", "(method) I6.staticMethod(): void"], + ["methodOfInterface6", "(method) I6.methodOfInterface6(): number"] +]); +const membersOfI7: CompletionInfo[] = membersOfI.concat([ + ["methodOfInterface7", "(method) I7.methodOfInterface7(): number"] +]); + +function getCompletionInfoVerifier( + validMembers: CompletionInfo[], + invalidMembers: CompletionInfo[], + arrayToDistribute: CompletionInfo[], + isValidDistributionCriteria: (v: CompletionInfo) => boolean): CompletionInfoVerifier { + if (arrayToDistribute) { + validMembers = validMembers.concat(arrayToDistribute.filter(isValidDistributionCriteria)); + invalidMembers = invalidMembers.concat(arrayToDistribute.filter(v => !isValidDistributionCriteria(v))); + } + return { + validMembers, + invalidMembers + } +} + +const noMembers: CompletionInfo[] = []; +const membersOfIAndI2 = membersOfI.concat(membersOfI2); +const invalidMembersOfBAtInstanceLocation = privateMembersOfBaseClassB.concat(validStaticMembersOfBaseClassB); + +// members of I and I2 +verifyClassElementLocations({ validMembers: membersOfIAndI2, invalidMembers: noMembers }, [ + "implementsIAndI2", + "implementsIAndI2AndWritingMethodNameOfI" +]); + +// Static location so no members of I and I2 +verifyClassElementLocations({ validMembers: noMembers, invalidMembers: membersOfIAndI2 }, + ["implementsIAndI2AndWritingStatic"]); + +const allInstanceBAndIAndI2 = membersOfIAndI2.concat(validInstanceMembersOfBaseClassB); +// members of instance B, I and I2 +verifyClassElementLocations({ + validMembers: allInstanceBAndIAndI2, + invalidMembers: invalidMembersOfBAtInstanceLocation +}, ["extendsBAndImplementsIAndI2"]); + +// static location so only static members of B and no members of instance B, I and I2 +verifyClassElementLocations({ + validMembers: validStaticMembersOfBaseClassB, + invalidMembers: privateMembersOfBaseClassB.concat(allInstanceBAndIAndI2) +}, [ + "extendsBAndImplementsIAndI2AndWritingStatic", + "extendsBAndImplementsIAndI2WithMethodFromBAndIAndTypesStatic" + ]); + +// instance members of B without protectedMethod, I and I2 +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ membersOfIAndI2, + /*invalidMembers*/ invalidMembersOfBAtInstanceLocation, + /*arrayToDistribute*/ validInstanceMembersOfBaseClassB, + value => value[0] !== "protectedMethod"), + ["extendsBAndImplementsIAndI2WithMethodFromB"]); + +// instance members of B, members of T without methodOfInterface and I2 +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ membersOfI2.concat(validInstanceMembersOfBaseClassB), + /*invalidMembers*/ invalidMembersOfBAtInstanceLocation, + /*arrayToDistribute*/ membersOfI, + value => value[0] !== "methodOfInterface"), + ["extendsBAndImplementsIAndI2WithMethodFromI"]); + +// instance members of B without protectedMethod, members of T without methodOfInterface and I2 +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ membersOfI2, + /*invalidMembers*/ invalidMembersOfBAtInstanceLocation, + /*arrayToDistribute*/ membersOfI.concat(validInstanceMembersOfBaseClassB), + value => value[0] !== "methodOfInterface" && value[0] !== "protectedMethod"), + ["extendsBAndImplementsIAndI2WithMethodFromBAndI"]); + +// members of B and members of I3 that are not same as name of method in B +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ validInstanceMembersOfBaseClassB, + /*invalidMembers*/ invalidMembersOfBAtInstanceLocation, + /*arrayToDistribute*/ membersOfI3, + value => value[0] !== "getValue"), + ["extendsBAndImplementsI3WithSameNameMembers"]); + +// members of B (without getValue since its implemented) and members of I3 that are not same as name of method in B +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ noMembers, + /*invalidMembers*/ invalidMembersOfBAtInstanceLocation, + /*arrayToDistribute*/ membersOfI3.concat(validInstanceMembersOfBaseClassB), + value => value[0] !== "getValue"), + ["extendsBAndImplementsI3WithSameNameMembersAndHasImplementedTheMember"]); + +const invalidMembersOfB0AtInstanceSide = privateMembersOfBaseClassB0.concat(validStaticMembersOfBaseClassB0); +const invalidMembersOfB0AtStaticSide = privateMembersOfBaseClassB0.concat(validInstanceMembersOfBaseClassB0); +// members of B0 and members of I4 +verifyClassElementLocations({ + validMembers: validInstanceMembersOfBaseClassB0.concat(membersOfI4), + invalidMembers: invalidMembersOfB0AtInstanceSide +}, [ + "extendsB0ThatExtendsAndImplementsI4WithStaticMethod", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedAnotherMethod", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsStatic" + ]); + +// members of B0 and members of I4 that are not staticMethod +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ validInstanceMembersOfBaseClassB0, + /*invalidMembers*/ invalidMembersOfB0AtInstanceSide, + /*arrayToDistribute*/ membersOfI4, + value => value[0] !== "staticMethod" + ), [ + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethod", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsBoth" + ]); + +// static members of B0 +verifyClassElementLocations({ + validMembers: validStaticMembersOfBaseClassB0, + invalidMembers: invalidMembersOfB0AtStaticSide.concat(membersOfI4) +}, [ + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodWritingStatic", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedAnotherMethodWritingStatic", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodWritingStatic" + ]); + +// static members of B0 without staticMethod +verifyClassElementLocations( + getCompletionInfoVerifier( + /*validMembers*/ noMembers, + /*invalidMembers*/ invalidMembersOfB0AtStaticSide.concat(membersOfI4), + /*arrayToDistribute*/ validStaticMembersOfBaseClassB0, + value => value[0] !== "staticMethod" + ), [ + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsStaticWritingStatic", + "extendsB0ThatExtendsAndImplementsI4WithStaticMethodAndImplementedThatMethodAsBothWritingStatic" + ]); + +// members of I7 extends I +verifyClassElementLocations({ validMembers: membersOfI7, invalidMembers: noMembers }, [ + "implementsI7whichExtendsI", + "implementsI7whichExtendsIAndAlsoImplementsI", + "implementsIAndAlsoImplementsI7whichExtendsI" +]); + +const invalidMembersOfB0AtInstanceSideFromInterfaceExtendingB0 = invalidMembersOfB0AtInstanceSide + .concat(protectedPropertiesOfBaseClassB0); +// members of I5 extends B0 +verifyClassElementLocations({ + validMembers: membersOfI5, + invalidMembers: invalidMembersOfB0AtInstanceSideFromInterfaceExtendingB0 +}, [ + "implementsI5ThatExtendsB0", + ]); + +// members of I6 extends B0 +verifyClassElementLocations({ + validMembers: membersOfI6, + invalidMembers: invalidMembersOfB0AtInstanceSideFromInterfaceExtendingB0 +}, [ + "implementsI6ThatExtendsB0AndHasStaticMethodOfB0", + ]); + +// members of B0 and I5 that extends B0 +verifyClassElementLocations({ + validMembers: membersOfI5.concat(protectedPropertiesOfBaseClassB0), + invalidMembers: invalidMembersOfB0AtInstanceSide +}, [ + "extendsB0AndImplementsI5ThatExtendsB0" + ]); + +// members of B0 and I6 that extends B0 +verifyClassElementLocations({ + validMembers: membersOfI6.concat(protectedPropertiesOfBaseClassB0), + invalidMembers: invalidMembersOfB0AtInstanceSide +}, [ + "extendsB0AndImplementsI6ThatExtendsB0AndHasStaticMethodOfB0" + ]); + +// nothing on static side as these do not extend any other class +verifyClassElementLocations({ + validMembers: [], + invalidMembers: membersOfI5.concat(membersOfI6, invalidMembersOfB0AtStaticSide) +}, [ + "implementsI5ThatExtendsB0TypesStatic", + "implementsI6ThatExtendsB0AndHasStaticMethodOfB0TypesStatic" + ]); + +// statics of base B but nothing from instance side +verifyClassElementLocations({ + validMembers: validStaticMembersOfBaseClassB0, + invalidMembers: membersOfI5.concat(membersOfI6, invalidMembersOfB0AtStaticSide) +}, [ + "extendsB0AndImplementsI5ThatExtendsB0TypesStatic", + "extendsB0AndImplementsI6ThatExtendsB0AndHasStaticMethodOfB0TypesStatic" + ]); \ No newline at end of file From ca748d6c02fb7cbd208d6b2ad9aaa43e4b1b8c66 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Fri, 12 May 2017 14:48:13 -0700 Subject: [PATCH 30/34] Add insertSpaceAfterTypeAssertion to the server protocol --- src/server/protocol.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 428b69027c7..c4c9ecece2b 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2267,6 +2267,7 @@ namespace ts.server.protocol { insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean; insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + insertSpaceAfterTypeAssertion?: boolean; insertSpaceBeforeFunctionParenthesis?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; From 4240f6f5d9fedf3134f82ca23e1e9f7d74cc7422 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Fri, 12 May 2017 16:17:32 -0700 Subject: [PATCH 31/34] Extract diagnostics formatter used by --pretty This allows compilers other than tsc.js to display nice in-context diagnostics --- src/compiler/program.ts | 95 +++++++++++++++++++++++++++++++++++++++++ src/compiler/tsc.ts | 87 +------------------------------------ 2 files changed, 96 insertions(+), 86 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 7893fee651e..8233ab4802e 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -241,6 +241,101 @@ namespace ts { return output; } + const redForegroundEscapeSequence = "\u001b[91m"; + const yellowForegroundEscapeSequence = "\u001b[93m"; + const blueForegroundEscapeSequence = "\u001b[93m"; + const gutterStyleSequence = "\u001b[100;30m"; + const gutterSeparator = " "; + const resetEscapeSequence = "\u001b[0m"; + const ellipsis = "..."; + function getCategoryFormat(category: DiagnosticCategory): string { + switch (category) { + case DiagnosticCategory.Warning: return yellowForegroundEscapeSequence; + case DiagnosticCategory.Error: return redForegroundEscapeSequence; + case DiagnosticCategory.Message: return blueForegroundEscapeSequence; + } + } + + function formatAndReset(text: string, formatStyle: string) { + return formatStyle + text + resetEscapeSequence; + } + + function padLeft(s: string, length: number) { + while (s.length < length) { + s = " " + s; + } + return s; + } + + export function formatDiagnosticsWithColorAndContext(diagnostics: Diagnostic[], host: FormatDiagnosticsHost): string { + let output = ""; + for (const diagnostic of diagnostics) { + if (diagnostic.file) { + const { start, length, file } = diagnostic; + const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); + const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length); + const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line; + const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName; + + const hasMoreThanFiveLines = (lastLine - firstLine) >= 4; + let gutterWidth = (lastLine + 1 + "").length; + if (hasMoreThanFiveLines) { + gutterWidth = Math.max(ellipsis.length, gutterWidth); + } + + output += sys.newLine; + for (let i = firstLine; i <= lastLine; i++) { + // If the error spans over 5 lines, we'll only show the first 2 and last 2 lines, + // so we'll skip ahead to the second-to-last line. + if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) { + output += formatAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + sys.newLine; + i = lastLine - 1; + } + + const lineStart = getPositionOfLineAndCharacter(file, i, 0); + const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length; + let lineContent = file.text.slice(lineStart, lineEnd); + lineContent = lineContent.replace(/\s+$/g, ""); // trim from end + lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces + + // Output the gutter and the actual contents of the line. + output += formatAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator; + output += lineContent + sys.newLine; + + // Output the gutter and the error span for the line using tildes. + output += formatAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator; + output += redForegroundEscapeSequence; + if (i === firstLine) { + // If we're on the last line, then limit it to the last character of the last line. + // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position. + const lastCharForLine = i === lastLine ? lastLineChar : undefined; + + output += lineContent.slice(0, firstLineChar).replace(/\S/g, " "); + output += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~"); + } + else if (i === lastLine) { + output += lineContent.slice(0, lastLineChar).replace(/./g, "~"); + } + else { + // Squiggle the entire line. + output += lineContent.replace(/./g, "~"); + } + output += resetEscapeSequence; + + output += sys.newLine; + } + + output += sys.newLine; + output += `${ relativeFileName }(${ firstLine + 1 },${ firstLineChar + 1 }): `; + } + + const categoryColor = getCategoryFormat(diagnostic.category); + const category = DiagnosticCategory[diagnostic.category].toLowerCase(); + output += `${ formatAndReset(category, categoryColor) } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }`; + } + return output; + } + export function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string { if (typeof messageText === "string") { return messageText; diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 8981da65ba3..834c7326e22 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -60,93 +60,8 @@ namespace ts { sys.write(ts.formatDiagnostics([diagnostic], host)); } - const redForegroundEscapeSequence = "\u001b[91m"; - const yellowForegroundEscapeSequence = "\u001b[93m"; - const blueForegroundEscapeSequence = "\u001b[93m"; - const gutterStyleSequence = "\u001b[100;30m"; - const gutterSeparator = " "; - const resetEscapeSequence = "\u001b[0m"; - const ellipsis = "..."; - function getCategoryFormat(category: DiagnosticCategory): string { - switch (category) { - case DiagnosticCategory.Warning: return yellowForegroundEscapeSequence; - case DiagnosticCategory.Error: return redForegroundEscapeSequence; - case DiagnosticCategory.Message: return blueForegroundEscapeSequence; - } - } - - function formatAndReset(text: string, formatStyle: string) { - return formatStyle + text + resetEscapeSequence; - } - function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void { - let output = ""; - - if (diagnostic.file) { - const { start, length, file } = diagnostic; - const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); - const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length); - const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line; - const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName; - - const hasMoreThanFiveLines = (lastLine - firstLine) >= 4; - let gutterWidth = (lastLine + 1 + "").length; - if (hasMoreThanFiveLines) { - gutterWidth = Math.max(ellipsis.length, gutterWidth); - } - - output += sys.newLine; - for (let i = firstLine; i <= lastLine; i++) { - // If the error spans over 5 lines, we'll only show the first 2 and last 2 lines, - // so we'll skip ahead to the second-to-last line. - if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) { - output += formatAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + sys.newLine; - i = lastLine - 1; - } - - const lineStart = getPositionOfLineAndCharacter(file, i, 0); - const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length; - let lineContent = file.text.slice(lineStart, lineEnd); - lineContent = lineContent.replace(/\s+$/g, ""); // trim from end - lineContent = lineContent.replace("\t", " "); // convert tabs to single spaces - - // Output the gutter and the actual contents of the line. - output += formatAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator; - output += lineContent + sys.newLine; - - // Output the gutter and the error span for the line using tildes. - output += formatAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator; - output += redForegroundEscapeSequence; - if (i === firstLine) { - // If we're on the last line, then limit it to the last character of the last line. - // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position. - const lastCharForLine = i === lastLine ? lastLineChar : undefined; - - output += lineContent.slice(0, firstLineChar).replace(/\S/g, " "); - output += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~"); - } - else if (i === lastLine) { - output += lineContent.slice(0, lastLineChar).replace(/./g, "~"); - } - else { - // Squiggle the entire line. - output += lineContent.replace(/./g, "~"); - } - output += resetEscapeSequence; - - output += sys.newLine; - } - - output += sys.newLine; - output += `${ relativeFileName }(${ firstLine + 1 },${ firstLineChar + 1 }): `; - } - - const categoryColor = getCategoryFormat(diagnostic.category); - const category = DiagnosticCategory[diagnostic.category].toLowerCase(); - output += `${ formatAndReset(category, categoryColor) } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }`; - output += sys.newLine + sys.newLine; - - sys.write(output); + sys.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + sys.newLine + sys.newLine); } function reportWatchDiagnostic(diagnostic: Diagnostic) { From 8af5910555460f1b96e7a13d289df78f0808d09b Mon Sep 17 00:00:00 2001 From: Dick van den Brink Date: Mon, 15 May 2017 14:55:17 +0200 Subject: [PATCH 32/34] Only report used before declaration errors on regular enums const enums don't generate code and the values are inlined so now error is necessary --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 376bdc13c88..d6f56a69b9a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1280,7 +1280,7 @@ namespace ts { else if (result.flags & SymbolFlags.Class) { error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationNameToString(getNameOfDeclaration(declaration))); } - else if (result.flags & SymbolFlags.Enum) { + else if (result.flags & SymbolFlags.RegularEnum) { error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationNameToString(getNameOfDeclaration(declaration))); } } From 0cd2245c9ad125b0860c72c6058183ce84823ed0 Mon Sep 17 00:00:00 2001 From: Dick van den Brink Date: Mon, 15 May 2017 14:57:24 +0200 Subject: [PATCH 33/34] Accept baselines for const enum changes --- .../baselines/reference/enumUsedBeforeDeclaration.errors.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/baselines/reference/enumUsedBeforeDeclaration.errors.txt b/tests/baselines/reference/enumUsedBeforeDeclaration.errors.txt index aea5366389b..64e1830b521 100644 --- a/tests/baselines/reference/enumUsedBeforeDeclaration.errors.txt +++ b/tests/baselines/reference/enumUsedBeforeDeclaration.errors.txt @@ -1,14 +1,11 @@ tests/cases/compiler/enumUsedBeforeDeclaration.ts(1,18): error TS2450: Enum 'Color' used before its declaration. -tests/cases/compiler/enumUsedBeforeDeclaration.ts(2,24): error TS2450: Enum 'ConstColor' used before its declaration. -==== tests/cases/compiler/enumUsedBeforeDeclaration.ts (2 errors) ==== +==== tests/cases/compiler/enumUsedBeforeDeclaration.ts (1 errors) ==== const v: Color = Color.Green; ~~~~~ !!! error TS2450: Enum 'Color' used before its declaration. const v2: ConstColor = ConstColor.Green; - ~~~~~~~~~~ -!!! error TS2450: Enum 'ConstColor' used before its declaration. enum Color { Red, Green, Blue } const enum ConstColor { Red, Green, Blue } From 5eb2bd08acb4bf42e0aeb75f30dd90d83a4182fb Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 15 May 2017 08:24:29 -0700 Subject: [PATCH 34/34] findAllReferences: In `export default foo`, symbol name is `foo` --- src/compiler/utilities.ts | 4 ++++ src/services/importTracker.ts | 9 +++++---- .../fourslash/findAllRefsForDefaultExport04.ts | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 tests/cases/fourslash/findAllRefsForDefaultExport04.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 58a1822a24d..5046b44d094 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4032,6 +4032,10 @@ namespace ts { return node.kind === SyntaxKind.ExportSpecifier; } + export function isExportAssignment(node: Node): node is ExportAssignment { + return node.kind === SyntaxKind.ExportAssignment; + } + export function isModuleOrEnumDeclaration(node: Node): node is ModuleDeclaration | EnumDeclaration { return node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.EnumDeclaration; } diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index df50f3ec31c..c540c44620a 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -526,17 +526,18 @@ namespace ts.FindAllReferences { return isExternalModuleSymbol(exportingModuleSymbol) ? { exportingModuleSymbol, exportKind } : undefined; } - function symbolName(symbol: Symbol): string { + function symbolName(symbol: Symbol): string | undefined { if (symbol.name !== "default") { return symbol.name; } - const name = forEach(symbol.declarations, decl => { + return forEach(symbol.declarations, decl => { + if (isExportAssignment(decl)) { + return isIdentifier(decl.expression) ? decl.expression.text : undefined; + } const name = getNameOfDeclaration(decl); return name && name.kind === SyntaxKind.Identifier && name.text; }); - Debug.assert(!!name); - return name; } /** If at an export specifier, go to the symbol it refers to. */ diff --git a/tests/cases/fourslash/findAllRefsForDefaultExport04.ts b/tests/cases/fourslash/findAllRefsForDefaultExport04.ts new file mode 100644 index 00000000000..c8fdb0a6149 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsForDefaultExport04.ts @@ -0,0 +1,16 @@ +/// + +// @Filename: /a.ts +////const [|{| "isWriteAccess": true, "isDefinition": true |}a|] = 0; +////export default [|a|]; + +// @Filename: /b.ts +////import [|{| "isWriteAccess": true, "isDefinition": true |}a|] from "./a"; +////[|a|]; + +const [r0, r1, r2, r3] = test.ranges(); +verify.referenceGroups([r0, r1], [ + { definition: "const a: 0", ranges: [r0, r1] }, + { definition: "import a", ranges: [r2, r3] } +]); +verify.singleReferenceGroup("import a", [r2, r3]);