diff --git a/src/services/completions.ts b/src/services/completions.ts index cf707c642ed..f3b26665c7c 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1617,7 +1617,7 @@ namespace ts.Completions { * Relevant symbols are stored in the captured 'symbols' variable. */ function tryGetClassLikeCompletionSymbols(): GlobalsSearch { - const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location); + const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location, position); if (!decl) return GlobalsSearch.Continue; // We're looking up possible property names from parent type. @@ -2234,7 +2234,7 @@ namespace ts.Completions { * 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 tryGetObjectTypeDeclarationCompletionContainer(sourceFile: SourceFile, contextToken: Node | undefined, location: Node): ObjectTypeDeclaration | undefined { + function tryGetObjectTypeDeclarationCompletionContainer(sourceFile: SourceFile, contextToken: Node | undefined, location: Node, position: number): ObjectTypeDeclaration | undefined { // class c { method() { } | method2() { } } switch (location.kind) { case SyntaxKind.SyntaxList: @@ -2244,9 +2244,15 @@ namespace ts.Completions { if (cls && !findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)) { return cls; } + break; + case SyntaxKind.Identifier: // class c extends React.Component { a: () => 1\n compon| } + if (isFromObjectTypeDeclaration(location)) { + return findAncestor(location, isObjectTypeDeclaration); + } } if (!contextToken) return undefined; + switch (contextToken.kind) { case SyntaxKind.SemicolonToken: // class c {getValue(): number; | } case SyntaxKind.CloseBraceToken: // class c { method() { } | } @@ -2258,7 +2264,13 @@ namespace ts.Completions { case SyntaxKind.CommaToken: // class c {getValue(): number, | } return tryCast(contextToken.parent, isObjectTypeDeclaration); default: - if (!isFromObjectTypeDeclaration(contextToken)) return undefined; + if (!isFromObjectTypeDeclaration(contextToken)) { + // class c extends React.Component { a: () => 1\n| } + if (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line && isObjectTypeDeclaration(location)) { + return location; + } + return undefined; + } const isValidKeyword = isClassLike(contextToken.parent.parent) ? isClassMemberCompletionKeyword : isInterfaceOrTypeLiteralCompletionKeyword; return (isValidKeyword(contextToken.kind) || contextToken.kind === SyntaxKind.AsteriskToken || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text)!)) // TODO: GH#18217 ? contextToken.parent.parent as ObjectTypeDeclaration : undefined; diff --git a/src/services/types.ts b/src/services/types.ts index b97125734f7..099263063a8 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -892,7 +892,7 @@ namespace ts { } export interface CompletionInfo { - /** Not true for all glboal completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ + /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ isGlobalCompletion: boolean; isMemberCompletion: boolean; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index fb39db1d3e0..1810cb020e3 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5404,7 +5404,7 @@ declare namespace ts { argumentCount: number; } interface CompletionInfo { - /** Not true for all glboal completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ + /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ isGlobalCompletion: boolean; isMemberCompletion: boolean; /** diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c9d6a220e6f..f74b4b14683 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5404,7 +5404,7 @@ declare namespace ts { argumentCount: number; } interface CompletionInfo { - /** Not true for all glboal completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ + /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ isGlobalCompletion: boolean; isMemberCompletion: boolean; /** diff --git a/tests/cases/fourslash/completionEntryAfterASIExpressionInClass.ts b/tests/cases/fourslash/completionEntryAfterASIExpressionInClass.ts new file mode 100644 index 00000000000..61271140ad0 --- /dev/null +++ b/tests/cases/fourslash/completionEntryAfterASIExpressionInClass.ts @@ -0,0 +1,21 @@ +/// + +//// class Parent { +//// protected shouldWork() { +//// console.log(); +//// } +//// } +//// +//// class Child extends Parent { +//// // this assumes ASI, but on next line wants to +//// x = () => 1 +//// shoul/*insideid*/ +//// } +//// +//// class ChildTwo extends Parent { +//// // this assumes ASI, but on next line wants to +//// x = () => 1 +//// /*root*/ //nothing +//// } + +verify.completions({ marker: ["insideid", "root"], includes: "shouldWork", isNewIdentifierLocation: true });