Add method signature completions (#46370)
* prototype creation for method override completion snippet
* WIP: start using codefix `addNewNodeForMemberSymbol` to create method decl node
* update type of addNewNodeForMemberSymbol
* add more tests and support more cases
* add more tests and fix some details
* wip: more fixes and tests
* expose check override modifier in checker
* fix test
* WIP: add snippet support
* WIP: snippet support on emitter, adding snippets in completions
* make add snippets work with overloads (not synced)
* fix snippet adding
* rebase
* WIP: try to add snippet escaping in emitter
* support escaping in snippets
* small fixes; fixed tests
* more tests and fixes
* fix new tests
* fix modifier inheritance for overloads
* merge conflict fixes; remove comments
* throw error if setOptions is called but not implemented
* fix newline handling
* fix weird stuff
* fix tests
* fix more tests
* Fix unbound host.getNewLine
* fix isParameterDeclaration changes
* rename diagnostic to status and remove snippets from public api
* rename emitter functions + fix indentation
* check completion kind before calling isclasslikemembercompletion
* fix missing type parameters
* Revert "fix missing type parameters"
This reverts commit 7bdeaa86da
.
* add isAmbient flag to addNewNodeForMemberSymbol
* add test for abstract overloads
* refactor snippet escaping support
* add user preference flag for enabling class member snippets
* update API baseline
* update tabstop order
Co-authored-by: Andrew Branch <andrewbranch@users.noreply.github.com>
This commit is contained in:
parent
566758d6cb
commit
fd620c93f6
|
@ -729,6 +729,7 @@ namespace ts {
|
|||
isDeclarationVisible,
|
||||
isPropertyAccessible,
|
||||
getTypeOnlyAliasDeclaration,
|
||||
getMemberOverrideModifierStatus,
|
||||
};
|
||||
|
||||
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined {
|
||||
|
@ -38469,7 +38470,7 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
checkMembersForMissingOverrideModifier(node, type, typeWithThis, staticType);
|
||||
checkMembersForOverrideModifier(node, type, typeWithThis, staticType);
|
||||
|
||||
const implementedTypeNodes = getEffectiveImplementsTypeNodes(node);
|
||||
if (implementedTypeNodes) {
|
||||
|
@ -38506,8 +38507,7 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
function checkMembersForMissingOverrideModifier(node: ClassLikeDeclaration, type: InterfaceType, typeWithThis: Type, staticType: ObjectType) {
|
||||
const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient);
|
||||
function checkMembersForOverrideModifier(node: ClassLikeDeclaration, type: InterfaceType, typeWithThis: Type, staticType: ObjectType) {
|
||||
const baseTypeNode = getEffectiveBaseTypeNode(node);
|
||||
const baseTypes = baseTypeNode && getBaseTypes(type);
|
||||
const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined;
|
||||
|
@ -38521,53 +38521,130 @@ namespace ts {
|
|||
if (isConstructorDeclaration(member)) {
|
||||
forEach(member.parameters, param => {
|
||||
if (isParameterPropertyDeclaration(param, member)) {
|
||||
checkClassMember(param, /*memberIsParameterProperty*/ true);
|
||||
checkExistingMemberForOverrideModifier(
|
||||
node,
|
||||
staticType,
|
||||
baseStaticType,
|
||||
baseWithThis,
|
||||
type,
|
||||
typeWithThis,
|
||||
param,
|
||||
/* memberIsParameterProperty */ true
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
checkClassMember(member);
|
||||
checkExistingMemberForOverrideModifier(
|
||||
node,
|
||||
staticType,
|
||||
baseStaticType,
|
||||
baseWithThis,
|
||||
type,
|
||||
typeWithThis,
|
||||
member,
|
||||
/* memberIsParameterProperty */ false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param member Existing member node to be checked.
|
||||
* Note: `member` cannot be a synthetic node.
|
||||
*/
|
||||
function checkExistingMemberForOverrideModifier(
|
||||
node: ClassLikeDeclaration,
|
||||
staticType: ObjectType,
|
||||
baseStaticType: Type,
|
||||
baseWithThis: Type | undefined,
|
||||
type: InterfaceType,
|
||||
typeWithThis: Type,
|
||||
member: ClassElement | ParameterPropertyDeclaration,
|
||||
memberIsParameterProperty: boolean,
|
||||
reportErrors = true,
|
||||
): MemberOverrideStatus {
|
||||
const declaredProp = member.name
|
||||
&& getSymbolAtLocation(member.name)
|
||||
|| getSymbolAtLocation(member);
|
||||
if (!declaredProp) {
|
||||
return MemberOverrideStatus.Ok;
|
||||
}
|
||||
|
||||
function checkClassMember(member: ClassElement | ParameterPropertyDeclaration, memberIsParameterProperty?: boolean) {
|
||||
const hasOverride = hasOverrideModifier(member);
|
||||
const hasStatic = isStatic(member);
|
||||
const isJs = isInJSFile(member);
|
||||
if (baseWithThis && (hasOverride || compilerOptions.noImplicitOverride)) {
|
||||
const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member);
|
||||
if (!declaredProp) {
|
||||
return;
|
||||
}
|
||||
return checkMemberForOverrideModifier(
|
||||
node,
|
||||
staticType,
|
||||
baseStaticType,
|
||||
baseWithThis,
|
||||
type,
|
||||
typeWithThis,
|
||||
hasOverrideModifier(member),
|
||||
hasAbstractModifier(member),
|
||||
isStatic(member),
|
||||
memberIsParameterProperty,
|
||||
symbolName(declaredProp),
|
||||
reportErrors ? member : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
const thisType = hasStatic ? staticType : typeWithThis;
|
||||
const baseType = hasStatic ? baseStaticType : baseWithThis;
|
||||
const prop = getPropertyOfType(thisType, declaredProp.escapedName);
|
||||
const baseProp = getPropertyOfType(baseType, declaredProp.escapedName);
|
||||
/**
|
||||
* Checks a class member declaration for either a missing or an invalid `override` modifier.
|
||||
* Note: this function can be used for speculative checking,
|
||||
* i.e. checking a member that does not yet exist in the program.
|
||||
* An example of that would be to call this function in a completions scenario,
|
||||
* when offering a method declaration as completion.
|
||||
* @param errorNode The node where we should report an error, or undefined if we should not report errors.
|
||||
*/
|
||||
function checkMemberForOverrideModifier(
|
||||
node: ClassLikeDeclaration,
|
||||
staticType: ObjectType,
|
||||
baseStaticType: Type,
|
||||
baseWithThis: Type | undefined,
|
||||
type: InterfaceType,
|
||||
typeWithThis: Type,
|
||||
memberHasOverrideModifier: boolean,
|
||||
memberHasAbstractModifier: boolean,
|
||||
memberIsStatic: boolean,
|
||||
memberIsParameterProperty: boolean,
|
||||
memberName: string,
|
||||
errorNode?: Node,
|
||||
): MemberOverrideStatus {
|
||||
const isJs = isInJSFile(node);
|
||||
const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient);
|
||||
if (baseWithThis && (memberHasOverrideModifier || compilerOptions.noImplicitOverride)) {
|
||||
const memberEscapedName = escapeLeadingUnderscores(memberName);
|
||||
const thisType = memberIsStatic ? staticType : typeWithThis;
|
||||
const baseType = memberIsStatic ? baseStaticType : baseWithThis;
|
||||
const prop = getPropertyOfType(thisType, memberEscapedName);
|
||||
const baseProp = getPropertyOfType(baseType, memberEscapedName);
|
||||
|
||||
const baseClassName = typeToString(baseWithThis);
|
||||
if (prop && !baseProp && hasOverride) {
|
||||
const suggestion = getSuggestedSymbolForNonexistentClassMember(symbolName(declaredProp), baseType);
|
||||
const baseClassName = typeToString(baseWithThis);
|
||||
if (prop && !baseProp && memberHasOverrideModifier) {
|
||||
if (errorNode) {
|
||||
const suggestion = getSuggestedSymbolForNonexistentClassMember(memberName, baseType); // Again, using symbol name: note that's different from `symbol.escapedName`
|
||||
suggestion ?
|
||||
error(
|
||||
member,
|
||||
errorNode,
|
||||
isJs ?
|
||||
Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1 :
|
||||
Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1,
|
||||
baseClassName,
|
||||
symbolToString(suggestion)) :
|
||||
error(
|
||||
member,
|
||||
errorNode,
|
||||
isJs ?
|
||||
Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0 :
|
||||
Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0,
|
||||
baseClassName);
|
||||
}
|
||||
else if (prop && baseProp?.declarations && compilerOptions.noImplicitOverride && !nodeInAmbientContext) {
|
||||
const baseHasAbstract = some(baseProp.declarations, hasAbstractModifier);
|
||||
if (hasOverride) {
|
||||
return;
|
||||
}
|
||||
return MemberOverrideStatus.HasInvalidOverride;
|
||||
}
|
||||
else if (prop && baseProp?.declarations && compilerOptions.noImplicitOverride && !nodeInAmbientContext) {
|
||||
const baseHasAbstract = some(baseProp.declarations, hasAbstractModifier);
|
||||
if (memberHasOverrideModifier) {
|
||||
return MemberOverrideStatus.Ok;
|
||||
}
|
||||
|
||||
if (!baseHasAbstract) {
|
||||
if (!baseHasAbstract) {
|
||||
if (errorNode) {
|
||||
const diag = memberIsParameterProperty ?
|
||||
isJs ?
|
||||
Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 :
|
||||
|
@ -38575,23 +38652,32 @@ namespace ts {
|
|||
isJs ?
|
||||
Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 :
|
||||
Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0;
|
||||
error(member, diag, baseClassName);
|
||||
error(errorNode, diag, baseClassName);
|
||||
}
|
||||
else if (hasAbstractModifier(member) && baseHasAbstract) {
|
||||
error(member, Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0, baseClassName);
|
||||
return MemberOverrideStatus.NeedsOverride;
|
||||
}
|
||||
else if (memberHasAbstractModifier && baseHasAbstract) {
|
||||
if (errorNode) {
|
||||
error(errorNode, Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0, baseClassName);
|
||||
}
|
||||
return MemberOverrideStatus.NeedsOverride;
|
||||
}
|
||||
}
|
||||
else if (hasOverride) {
|
||||
}
|
||||
else if (memberHasOverrideModifier) {
|
||||
if (errorNode) {
|
||||
const className = typeToString(type);
|
||||
error(
|
||||
member,
|
||||
errorNode,
|
||||
isJs ?
|
||||
Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class :
|
||||
Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class,
|
||||
className);
|
||||
}
|
||||
return MemberOverrideStatus.HasInvalidOverride;
|
||||
}
|
||||
|
||||
return MemberOverrideStatus.Ok;
|
||||
}
|
||||
|
||||
function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) {
|
||||
|
@ -38638,6 +38724,48 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a member declaration node to see if has a missing or invalid `override` modifier.
|
||||
* @param node Class-like node where the member is declared.
|
||||
* @param member Member declaration node.
|
||||
* Note: `member` can be a synthetic node without a parent.
|
||||
*/
|
||||
function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus {
|
||||
if (!member.name) {
|
||||
return MemberOverrideStatus.Ok;
|
||||
}
|
||||
|
||||
const symbol = getSymbolOfNode(node);
|
||||
const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType;
|
||||
const typeWithThis = getTypeWithThisArgument(type);
|
||||
const staticType = getTypeOfSymbol(symbol) as ObjectType;
|
||||
|
||||
const baseTypeNode = getEffectiveBaseTypeNode(node);
|
||||
const baseTypes = baseTypeNode && getBaseTypes(type);
|
||||
const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined;
|
||||
const baseStaticType = getBaseConstructorTypeOfClass(type);
|
||||
|
||||
const memberHasOverrideModifier = member.parent
|
||||
? hasOverrideModifier(member)
|
||||
: hasSyntacticModifier(member, ModifierFlags.Override);
|
||||
|
||||
const memberName = unescapeLeadingUnderscores(getTextOfPropertyName(member.name));
|
||||
|
||||
return checkMemberForOverrideModifier(
|
||||
node,
|
||||
staticType,
|
||||
baseStaticType,
|
||||
baseWithThis,
|
||||
type,
|
||||
typeWithThis,
|
||||
memberHasOverrideModifier,
|
||||
hasAbstractModifier(member),
|
||||
isStatic(member),
|
||||
/* memberIsParameterProperty */ false,
|
||||
memberName,
|
||||
);
|
||||
}
|
||||
|
||||
function getTargetSymbol(s: Symbol) {
|
||||
// if symbol is instantiated its flags are not copied from the 'target'
|
||||
// so we'll need to get back original 'target' symbol to work with correct set of flags
|
||||
|
|
|
@ -351,6 +351,10 @@ namespace ts {
|
|||
return formatEnum(kind, (ts as any).SyntaxKind, /*isFlags*/ false);
|
||||
}
|
||||
|
||||
export function formatSnippetKind(kind: SnippetKind | undefined): string {
|
||||
return formatEnum(kind, (ts as any).SnippetKind, /*isFlags*/ false);
|
||||
}
|
||||
|
||||
export function formatNodeFlags(flags: NodeFlags | undefined): string {
|
||||
return formatEnum(flags, (ts as any).NodeFlags, /*isFlags*/ true);
|
||||
}
|
||||
|
|
|
@ -1283,7 +1283,13 @@ namespace ts {
|
|||
currentParenthesizerRule = undefined;
|
||||
}
|
||||
|
||||
function pipelineEmitWithHintWorker(hint: EmitHint, node: Node): void {
|
||||
function pipelineEmitWithHintWorker(hint: EmitHint, node: Node, allowSnippets = true): void {
|
||||
if (allowSnippets) {
|
||||
const snippet = getSnippetElement(node);
|
||||
if (snippet) {
|
||||
return emitSnippetNode(hint, node, snippet);
|
||||
}
|
||||
}
|
||||
if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile));
|
||||
if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier));
|
||||
if (hint === EmitHint.JsxAttributeValue) return emitLiteral(cast(node, isStringLiteral), /*jsxAttributeEscape*/ true);
|
||||
|
@ -1924,6 +1930,32 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Snippet Elements
|
||||
//
|
||||
|
||||
function emitSnippetNode(hint: EmitHint, node: Node, snippet: SnippetElement) {
|
||||
switch (snippet.kind) {
|
||||
case SnippetKind.Placeholder:
|
||||
emitPlaceholder(hint, node, snippet);
|
||||
break;
|
||||
case SnippetKind.TabStop:
|
||||
emitTabStop(snippet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function emitPlaceholder(hint: EmitHint, node: Node, snippet: Placeholder) {
|
||||
nonEscapingWrite(`\$\{${snippet.order}:`); // `${2:`
|
||||
pipelineEmitWithHintWorker(hint, node, /*allowSnippets*/ false); // `...`
|
||||
nonEscapingWrite(`\}`); // `}`
|
||||
// `${2:...}`
|
||||
}
|
||||
|
||||
function emitTabStop(snippet: TabStop) {
|
||||
nonEscapingWrite(`\$${snippet.order}`);
|
||||
}
|
||||
|
||||
//
|
||||
// Identifiers
|
||||
//
|
||||
|
@ -4457,6 +4489,16 @@ namespace ts {
|
|||
writer.writeProperty(s);
|
||||
}
|
||||
|
||||
function nonEscapingWrite(s: string) {
|
||||
// This should be defined in a snippet-escaping text writer.
|
||||
if (writer.nonEscapingWrite) {
|
||||
writer.nonEscapingWrite(s);
|
||||
}
|
||||
else {
|
||||
writer.write(s);
|
||||
}
|
||||
}
|
||||
|
||||
function writeLine(count = 1) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
writer.writeLine(i > 0);
|
||||
|
|
|
@ -256,6 +256,24 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SnippetElement of a node.
|
||||
*/
|
||||
/* @internal */
|
||||
export function getSnippetElement(node: Node): SnippetElement | undefined {
|
||||
return node.emitNode?.snippetElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SnippetElement of a node.
|
||||
*/
|
||||
/* @internal */
|
||||
export function setSnippetElement<T extends Node>(node: T, snippet: SnippetElement): T {
|
||||
const emitNode = getOrCreateEmitNode(node);
|
||||
emitNode.snippetElement = snippet;
|
||||
return node;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function ignoreSourceNewlines<T extends Node>(node: T): T {
|
||||
getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines;
|
||||
|
|
|
@ -4414,6 +4414,14 @@ namespace ts {
|
|||
/* @internal */ isDeclarationVisible(node: Declaration | AnyImportSyntax): boolean;
|
||||
/* @internal */ isPropertyAccessible(node: Node, isSuper: boolean, isWrite: boolean, containingType: Type, property: Symbol): boolean;
|
||||
/* @internal */ getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined;
|
||||
/* @internal */ getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum MemberOverrideStatus {
|
||||
Ok,
|
||||
NeedsOverride,
|
||||
HasInvalidOverride
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
@ -6816,6 +6824,31 @@ namespace ts {
|
|||
externalHelpers?: boolean;
|
||||
helpers?: EmitHelper[]; // Emit helpers for the node
|
||||
startsOnNewLine?: boolean; // If the node should begin on a new line
|
||||
snippetElement?: SnippetElement; // Snippet element of the node
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export type SnippetElement = TabStop | Placeholder;
|
||||
|
||||
/* @internal */
|
||||
export interface TabStop {
|
||||
kind: SnippetKind.TabStop;
|
||||
order: number;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface Placeholder {
|
||||
kind: SnippetKind.Placeholder;
|
||||
order: number;
|
||||
}
|
||||
|
||||
// Reference: https://code.visualstudio.com/docs/editor/userdefinedsnippets#_snippet-syntax
|
||||
/* @internal */
|
||||
export const enum SnippetKind {
|
||||
TabStop, // `$1`, `$2`
|
||||
Placeholder, // `${1:foo}`
|
||||
Choice, // `${1|one,two,three|}`
|
||||
Variable, // `$name`, `${name:default}`
|
||||
}
|
||||
|
||||
export const enum EmitFlags {
|
||||
|
@ -8278,6 +8311,7 @@ namespace ts {
|
|||
hasTrailingComment(): boolean;
|
||||
hasTrailingWhitespace(): boolean;
|
||||
getTextPosWithWriteLine?(): number;
|
||||
nonEscapingWrite?(text: string): void;
|
||||
}
|
||||
|
||||
export interface GetEffectiveTypeRootsHost {
|
||||
|
@ -8623,6 +8657,7 @@ namespace ts {
|
|||
readonly includeCompletionsWithSnippetText?: boolean;
|
||||
readonly includeAutomaticOptionalChainCompletions?: boolean;
|
||||
readonly includeCompletionsWithInsertText?: boolean;
|
||||
readonly includeCompletionsWithClassMemberSnippets?: boolean;
|
||||
readonly allowIncompleteCompletions?: boolean;
|
||||
readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative";
|
||||
/** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */
|
||||
|
|
|
@ -3161,7 +3161,7 @@ namespace ts {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
export function isKeyword(token: SyntaxKind): boolean {
|
||||
export function isKeyword(token: SyntaxKind): token is KeywordSyntaxKind {
|
||||
return SyntaxKind.FirstKeyword <= token && token <= SyntaxKind.LastKeyword;
|
||||
}
|
||||
|
||||
|
@ -3346,7 +3346,7 @@ namespace ts {
|
|||
return node.escapedText === "push" || node.escapedText === "unshift";
|
||||
}
|
||||
|
||||
export function isParameterDeclaration(node: VariableLikeDeclaration) {
|
||||
export function isParameterDeclaration(node: VariableLikeDeclaration): boolean {
|
||||
const root = getRootDeclaration(node);
|
||||
return root.kind === SyntaxKind.Parameter;
|
||||
}
|
||||
|
@ -7425,4 +7425,8 @@ namespace ts {
|
|||
export function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression | ArrowFunction {
|
||||
return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction;
|
||||
}
|
||||
|
||||
export function escapeSnippetText(text: string): string {
|
||||
return text.replace(/\$/gm, "\\$");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2289,7 +2289,7 @@ namespace ts.server.protocol {
|
|||
/**
|
||||
* Human-readable description of the `source`.
|
||||
*/
|
||||
sourceDisplay?: SymbolDisplayPart[];
|
||||
sourceDisplay?: SymbolDisplayPart[];
|
||||
/**
|
||||
* If true, this completion should be highlighted as recommended. There will only be one of these.
|
||||
* This will be set when we know the user should write an expression with a certain type and that type is an enum or constructable class.
|
||||
|
@ -3381,6 +3381,13 @@ namespace ts.server.protocol {
|
|||
* values, with insertion text to replace preceding `.` tokens with `?.`.
|
||||
*/
|
||||
readonly includeAutomaticOptionalChainCompletions?: boolean;
|
||||
/**
|
||||
* If enabled, completions for class members (e.g. methods and properties) will include
|
||||
* a whole declaration for the member.
|
||||
* E.g., `class A { f| }` could be completed to `class A { foo(): number {} }`, instead of
|
||||
* `class A { foo }`.
|
||||
*/
|
||||
readonly includeCompletionsWithClassMemberSnippets?: boolean;
|
||||
readonly allowIncompleteCompletions?: boolean;
|
||||
readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative";
|
||||
/** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace ts.codefix {
|
|||
const abstractAndNonPrivateExtendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType).filter(symbolPointsToNonPrivateAndAbstractMember);
|
||||
|
||||
const importAdder = createImportAdder(sourceFile, context.program, preferences, context.host);
|
||||
createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, sourceFile, context, preferences, importAdder, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));
|
||||
createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, sourceFile, context, preferences, importAdder, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member as ClassElement));
|
||||
importAdder.writeFixes(changeTracker);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace ts.codefix {
|
|||
}
|
||||
|
||||
const importAdder = createImportAdder(sourceFile, context.program, preferences, context.host);
|
||||
createMissingMemberNodes(classDeclaration, nonPrivateAndNotExistedInHeritageClauseMembers, sourceFile, context, preferences, importAdder, member => insertInterfaceMemberNode(sourceFile, classDeclaration, member));
|
||||
createMissingMemberNodes(classDeclaration, nonPrivateAndNotExistedInHeritageClauseMembers, sourceFile, context, preferences, importAdder, member => insertInterfaceMemberNode(sourceFile, classDeclaration, member as ClassElement));
|
||||
importAdder.writeFixes(changeTracker);
|
||||
|
||||
function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void {
|
||||
|
|
|
@ -7,11 +7,18 @@ namespace ts.codefix {
|
|||
* @param importAdder If provided, type annotations will use identifier type references instead of ImportTypeNodes, and the missing imports will be added to the importAdder.
|
||||
* @returns Empty string iff there are no member insertions.
|
||||
*/
|
||||
export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: readonly Symbol[], sourceFile: SourceFile, context: TypeConstructionContext, preferences: UserPreferences, importAdder: ImportAdder | undefined, addClassElement: (node: ClassElement) => void): void {
|
||||
export function createMissingMemberNodes(
|
||||
classDeclaration: ClassLikeDeclaration,
|
||||
possiblyMissingSymbols: readonly Symbol[],
|
||||
sourceFile: SourceFile,
|
||||
context: TypeConstructionContext,
|
||||
preferences: UserPreferences,
|
||||
importAdder: ImportAdder | undefined,
|
||||
addClassElement: (node: AddNode) => void): void {
|
||||
const classMembers = classDeclaration.symbol.members!;
|
||||
for (const symbol of possiblyMissingSymbols) {
|
||||
if (!classMembers.has(symbol.escapedName)) {
|
||||
addNewNodeForMemberSymbol(symbol, classDeclaration, sourceFile, context, preferences, importAdder, addClassElement);
|
||||
addNewNodeForMemberSymbol(symbol, classDeclaration, sourceFile, context, preferences, importAdder, addClassElement, /* body */ undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +35,23 @@ namespace ts.codefix {
|
|||
host: LanguageServiceHost;
|
||||
}
|
||||
|
||||
type AddNode = PropertyDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction;
|
||||
|
||||
/**
|
||||
* @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`.
|
||||
* `addClassElement` will not be called if we can't figure out a representation for `symbol` in `enclosingDeclaration`.
|
||||
* @param body If defined, this will be the body of the member node passed to `addClassElement`. Otherwise, the body will default to a stub.
|
||||
*/
|
||||
function addNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, sourceFile: SourceFile, context: TypeConstructionContext, preferences: UserPreferences, importAdder: ImportAdder | undefined, addClassElement: (node: Node) => void): void {
|
||||
export function addNewNodeForMemberSymbol(
|
||||
symbol: Symbol,
|
||||
enclosingDeclaration: ClassLikeDeclaration,
|
||||
sourceFile: SourceFile,
|
||||
context: TypeConstructionContext,
|
||||
preferences: UserPreferences,
|
||||
importAdder: ImportAdder | undefined,
|
||||
addClassElement: (node: AddNode) => void,
|
||||
body: Block | undefined,
|
||||
isAmbient = false,
|
||||
): void {
|
||||
const declarations = symbol.getDeclarations();
|
||||
if (!(declarations && declarations.length)) {
|
||||
return undefined;
|
||||
|
@ -44,7 +64,7 @@ namespace ts.codefix {
|
|||
const modifiers = visibilityModifier ? factory.createNodeArray([visibilityModifier]) : undefined;
|
||||
const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration));
|
||||
const optional = !!(symbol.flags & SymbolFlags.Optional);
|
||||
const ambient = !!(enclosingDeclaration.flags & NodeFlags.Ambient);
|
||||
const ambient = !!(enclosingDeclaration.flags & NodeFlags.Ambient) || isAmbient;
|
||||
const quotePreference = getQuotePreference(sourceFile, preferences);
|
||||
|
||||
switch (declaration.kind) {
|
||||
|
@ -89,7 +109,7 @@ namespace ts.codefix {
|
|||
name,
|
||||
emptyArray,
|
||||
typeNode,
|
||||
ambient ? undefined : createStubbedMethodBody(quotePreference)));
|
||||
ambient ? undefined : body || createStubbedMethodBody(quotePreference)));
|
||||
}
|
||||
else {
|
||||
Debug.assertNode(accessor, isSetAccessorDeclaration, "The counterpart to a getter should be a setter");
|
||||
|
@ -100,7 +120,7 @@ namespace ts.codefix {
|
|||
modifiers,
|
||||
name,
|
||||
createDummyParameters(1, [parameterName], [typeNode], 1, /*inJs*/ false),
|
||||
ambient ? undefined : createStubbedMethodBody(quotePreference)));
|
||||
ambient ? undefined : body || createStubbedMethodBody(quotePreference)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -122,7 +142,7 @@ namespace ts.codefix {
|
|||
if (declarations.length === 1) {
|
||||
Debug.assert(signatures.length === 1, "One declaration implies one signature");
|
||||
const signature = signatures[0];
|
||||
outputMethod(quotePreference, signature, modifiers, name, ambient ? undefined : createStubbedMethodBody(quotePreference));
|
||||
outputMethod(quotePreference, signature, modifiers, name, ambient ? undefined : body || createStubbedMethodBody(quotePreference));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -134,11 +154,11 @@ namespace ts.codefix {
|
|||
if (!ambient) {
|
||||
if (declarations.length > signatures.length) {
|
||||
const signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration)!;
|
||||
outputMethod(quotePreference, signature, modifiers, name, createStubbedMethodBody(quotePreference));
|
||||
outputMethod(quotePreference, signature, modifiers, name, body || createStubbedMethodBody(quotePreference));
|
||||
}
|
||||
else {
|
||||
Debug.assert(declarations.length === signatures.length, "Declarations and signatures should match count");
|
||||
addClassElement(createMethodImplementingSignatures(checker, context, enclosingDeclaration, signatures, name, optional, modifiers, quotePreference));
|
||||
addClassElement(createMethodImplementingSignatures(checker, context, enclosingDeclaration, signatures, name, optional, modifiers, quotePreference, body));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -348,6 +368,7 @@ namespace ts.codefix {
|
|||
optional: boolean,
|
||||
modifiers: readonly Modifier[] | undefined,
|
||||
quotePreference: QuotePreference,
|
||||
body: Block | undefined,
|
||||
): MethodDeclaration {
|
||||
/** This is *a* signature with the maximal number of arguments,
|
||||
* such that if there is a "maximal" signature without rest arguments,
|
||||
|
@ -389,7 +410,8 @@ namespace ts.codefix {
|
|||
/*typeParameters*/ undefined,
|
||||
parameters,
|
||||
getReturnTypeFromSignatures(signatures, checker, context, enclosingDeclaration),
|
||||
quotePreference);
|
||||
quotePreference,
|
||||
body);
|
||||
}
|
||||
|
||||
function getReturnTypeFromSignatures(signatures: readonly Signature[], checker: TypeChecker, context: TypeConstructionContext, enclosingDeclaration: ClassLikeDeclaration): TypeNode | undefined {
|
||||
|
@ -406,7 +428,8 @@ namespace ts.codefix {
|
|||
typeParameters: readonly TypeParameterDeclaration[] | undefined,
|
||||
parameters: readonly ParameterDeclaration[],
|
||||
returnType: TypeNode | undefined,
|
||||
quotePreference: QuotePreference
|
||||
quotePreference: QuotePreference,
|
||||
body: Block | undefined
|
||||
): MethodDeclaration {
|
||||
return factory.createMethodDeclaration(
|
||||
/*decorators*/ undefined,
|
||||
|
@ -417,7 +440,7 @@ namespace ts.codefix {
|
|||
typeParameters,
|
||||
parameters,
|
||||
returnType,
|
||||
createStubbedMethodBody(quotePreference));
|
||||
body || createStubbedMethodBody(quotePreference));
|
||||
}
|
||||
|
||||
function createStubbedMethodBody(quotePreference: QuotePreference) {
|
||||
|
|
|
@ -244,7 +244,6 @@ namespace ts.Completions {
|
|||
|
||||
// If the request is a continuation of an earlier `isIncomplete` response,
|
||||
// we can continue it from the cached previous response.
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const incompleteCompletionsCache = preferences.allowIncompleteCompletions ? host.getIncompleteCompletionsCache?.() : undefined;
|
||||
if (incompleteCompletionsCache && completionKind === CompletionTriggerKind.TriggerForIncompleteCompletions && previousToken && isIdentifier(previousToken)) {
|
||||
|
@ -257,7 +256,7 @@ namespace ts.Completions {
|
|||
incompleteCompletionsCache?.clear();
|
||||
}
|
||||
|
||||
const stringCompletions = StringCompletions.getStringLiteralCompletions(sourceFile, position, previousToken, typeChecker, compilerOptions, host, log, preferences);
|
||||
const stringCompletions = StringCompletions.getStringLiteralCompletions(sourceFile, position, previousToken, compilerOptions, host, program, log, preferences);
|
||||
if (stringCompletions) {
|
||||
return stringCompletions;
|
||||
}
|
||||
|
@ -274,7 +273,7 @@ namespace ts.Completions {
|
|||
|
||||
switch (completionData.kind) {
|
||||
case CompletionDataKind.Data:
|
||||
const response = completionInfoFromData(sourceFile, typeChecker, compilerOptions, log, completionData, preferences);
|
||||
const response = completionInfoFromData(sourceFile, host, program, compilerOptions, log, completionData, preferences);
|
||||
if (response?.isIncomplete) {
|
||||
incompleteCompletionsCache?.set(response);
|
||||
}
|
||||
|
@ -403,7 +402,15 @@ namespace ts.Completions {
|
|||
return location?.kind === SyntaxKind.Identifier ? createTextSpanFromNode(location) : undefined;
|
||||
}
|
||||
|
||||
function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, preferences: UserPreferences): CompletionInfo | undefined {
|
||||
function completionInfoFromData(
|
||||
sourceFile: SourceFile,
|
||||
host: LanguageServiceHost,
|
||||
program: Program,
|
||||
compilerOptions: CompilerOptions,
|
||||
log: Log,
|
||||
completionData: CompletionData,
|
||||
preferences: UserPreferences,
|
||||
): CompletionInfo | undefined {
|
||||
const {
|
||||
symbols,
|
||||
contextToken,
|
||||
|
@ -443,7 +450,8 @@ namespace ts.Completions {
|
|||
contextToken,
|
||||
location,
|
||||
sourceFile,
|
||||
typeChecker,
|
||||
host,
|
||||
program,
|
||||
getEmitScriptTarget(compilerOptions),
|
||||
log,
|
||||
completionKind,
|
||||
|
@ -472,7 +480,8 @@ namespace ts.Completions {
|
|||
contextToken,
|
||||
location,
|
||||
sourceFile,
|
||||
typeChecker,
|
||||
host,
|
||||
program,
|
||||
getEmitScriptTarget(compilerOptions),
|
||||
log,
|
||||
completionKind,
|
||||
|
@ -614,7 +623,8 @@ namespace ts.Completions {
|
|||
contextToken: Node | undefined,
|
||||
location: Node,
|
||||
sourceFile: SourceFile,
|
||||
typeChecker: TypeChecker,
|
||||
host: LanguageServiceHost,
|
||||
program: Program,
|
||||
name: string,
|
||||
needsConvertPropertyAccess: boolean,
|
||||
origin: SymbolOriginInfo | undefined,
|
||||
|
@ -625,6 +635,7 @@ namespace ts.Completions {
|
|||
useSemicolons: boolean,
|
||||
options: CompilerOptions,
|
||||
preferences: UserPreferences,
|
||||
completionKind: CompletionKind,
|
||||
): CompletionEntry | undefined {
|
||||
let insertText: string | undefined;
|
||||
let replacementSpan = getReplacementSpanForContextToken(replacementToken);
|
||||
|
@ -633,6 +644,7 @@ namespace ts.Completions {
|
|||
let sourceDisplay;
|
||||
let hasAction;
|
||||
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const insertQuestionDot = origin && originIsNullableMember(origin);
|
||||
const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess;
|
||||
if (origin && originIsThisType(origin)) {
|
||||
|
@ -686,13 +698,11 @@ namespace ts.Completions {
|
|||
}
|
||||
}
|
||||
|
||||
if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (originIsExport(origin) || originIsResolvedExport(origin)) {
|
||||
data = originToCompletionEntryData(origin);
|
||||
hasAction = !importCompletionNode;
|
||||
if (preferences.includeCompletionsWithClassMemberSnippets &&
|
||||
preferences.includeCompletionsWithInsertText &&
|
||||
completionKind === CompletionKind.MemberLike &&
|
||||
isClassLikeMemberCompletion(symbol, location)) {
|
||||
({ insertText, isSnippet } = getEntryForMemberCompletion(host, program, options, preferences, name, symbol, location, contextToken));
|
||||
}
|
||||
|
||||
const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location);
|
||||
|
@ -726,6 +736,15 @@ namespace ts.Completions {
|
|||
}
|
||||
}
|
||||
|
||||
if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (originIsExport(origin) || originIsResolvedExport(origin)) {
|
||||
data = originToCompletionEntryData(origin);
|
||||
hasAction = !importCompletionNode;
|
||||
}
|
||||
|
||||
// TODO(drosen): Right now we just permit *all* semantic meanings when calling
|
||||
// 'getSymbolKind' which is permissible given that it is backwards compatible; but
|
||||
// really we should consider passing the meaning for the node so that we don't report
|
||||
|
@ -752,8 +771,261 @@ namespace ts.Completions {
|
|||
};
|
||||
}
|
||||
|
||||
function escapeSnippetText(text: string): string {
|
||||
return text.replace(/\$/gm, "\\$");
|
||||
function isClassLikeMemberCompletion(symbol: Symbol, location: Node): boolean {
|
||||
// TODO: support JS files.
|
||||
if (isInJSFile(location)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Completion symbol must be for a class member.
|
||||
const memberFlags =
|
||||
SymbolFlags.ClassMember
|
||||
& SymbolFlags.EnumMemberExcludes;
|
||||
/* In
|
||||
`class C {
|
||||
|
|
||||
}`
|
||||
`location` is a class-like declaration.
|
||||
In
|
||||
`class C {
|
||||
m|
|
||||
}`
|
||||
`location` is an identifier,
|
||||
`location.parent` is a class element declaration,
|
||||
and `location.parent.parent` is a class-like declaration.
|
||||
In
|
||||
`abstract class C {
|
||||
abstract
|
||||
abstract m|
|
||||
}`
|
||||
`location` is a syntax list (with modifiers as children),
|
||||
and `location.parent` is a class-like declaration.
|
||||
*/
|
||||
return !!(symbol.flags & memberFlags) &&
|
||||
(
|
||||
isClassLike(location) ||
|
||||
(
|
||||
location.parent &&
|
||||
location.parent.parent &&
|
||||
isClassElement(location.parent) &&
|
||||
location === location.parent.name &&
|
||||
isClassLike(location.parent.parent)
|
||||
) ||
|
||||
(
|
||||
location.parent &&
|
||||
isSyntaxList(location) &&
|
||||
isClassLike(location.parent)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function getEntryForMemberCompletion(
|
||||
host: LanguageServiceHost,
|
||||
program: Program,
|
||||
options: CompilerOptions,
|
||||
preferences: UserPreferences,
|
||||
name: string,
|
||||
symbol: Symbol,
|
||||
location: Node,
|
||||
contextToken: Node | undefined,
|
||||
): { insertText: string, isSnippet?: true } {
|
||||
const classLikeDeclaration = findAncestor(location, isClassLike);
|
||||
if (!classLikeDeclaration) {
|
||||
return { insertText: name };
|
||||
}
|
||||
|
||||
let isSnippet: true | undefined;
|
||||
let insertText: string = name;
|
||||
|
||||
const checker = program.getTypeChecker();
|
||||
const sourceFile = location.getSourceFile();
|
||||
const printer = createSnippetPrinter({
|
||||
removeComments: true,
|
||||
module: options.module,
|
||||
target: options.target,
|
||||
omitTrailingSemicolon: true,
|
||||
newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))),
|
||||
});
|
||||
const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host);
|
||||
|
||||
let body;
|
||||
let tabstopStart = 1;
|
||||
if (preferences.includeCompletionsWithSnippetText) {
|
||||
isSnippet = true;
|
||||
// We are adding a final tabstop (i.e. $0) in the body of the suggested member, if it has one.
|
||||
// Note: this assumes we won't have more than one body in the completion nodes, which should be the case.
|
||||
const emptyStatement1 = factory.createExpressionStatement(factory.createIdentifier(""));
|
||||
setSnippetElement(emptyStatement1, { kind: SnippetKind.TabStop, order: 1 });
|
||||
tabstopStart = 2;
|
||||
body = factory.createBlock([emptyStatement1], /* multiline */ true);
|
||||
}
|
||||
else {
|
||||
body = factory.createBlock([], /* multiline */ true);
|
||||
}
|
||||
|
||||
let modifiers = ModifierFlags.None;
|
||||
// Whether the suggested member should be abstract.
|
||||
// e.g. in `abstract class C { abstract | }`, we should offer abstract method signatures at position `|`.
|
||||
// Note: We are relying on checking if the context token is `abstract`,
|
||||
// since other visibility modifiers (e.g. `protected`) should come *before* `abstract`.
|
||||
// However, that is not true for the e.g. `override` modifier, so this check has its limitations.
|
||||
const isAbstract = contextToken && isModifierLike(contextToken) === SyntaxKind.AbstractKeyword;
|
||||
const completionNodes: Node[] = [];
|
||||
codefix.addNewNodeForMemberSymbol(
|
||||
symbol,
|
||||
classLikeDeclaration,
|
||||
sourceFile,
|
||||
{ program, host },
|
||||
preferences,
|
||||
importAdder,
|
||||
// `addNewNodeForMemberSymbol` calls this callback function for each new member node
|
||||
// it adds for the given member symbol.
|
||||
// We store these member nodes in the `completionNodes` array.
|
||||
// Note: there might be:
|
||||
// - No nodes if `addNewNodeForMemberSymbol` cannot figure out a node for the member;
|
||||
// - One node;
|
||||
// - More than one node if the member is overloaded (e.g. a method with overload signatures).
|
||||
node => {
|
||||
let requiredModifiers = ModifierFlags.None;
|
||||
if (isAbstract) {
|
||||
requiredModifiers |= ModifierFlags.Abstract;
|
||||
}
|
||||
if (isClassElement(node)
|
||||
&& checker.getMemberOverrideModifierStatus(classLikeDeclaration, node) === MemberOverrideStatus.NeedsOverride) {
|
||||
requiredModifiers |= ModifierFlags.Override;
|
||||
}
|
||||
|
||||
let presentModifiers = ModifierFlags.None;
|
||||
if (!completionNodes.length) {
|
||||
// Omit already present modifiers from the first completion node/signature.
|
||||
if (contextToken) {
|
||||
presentModifiers = getPresentModifiers(contextToken);
|
||||
}
|
||||
// Keep track of added missing required modifiers and modifiers already present.
|
||||
// This is needed when we have overloaded signatures,
|
||||
// so this callback will be called for multiple nodes/signatures,
|
||||
// and we need to make sure the modifiers are uniform for all nodes/signatures.
|
||||
modifiers = node.modifierFlagsCache | requiredModifiers | presentModifiers;
|
||||
}
|
||||
node = factory.updateModifiers(node, modifiers & (~presentModifiers));
|
||||
|
||||
completionNodes.push(node);
|
||||
},
|
||||
body,
|
||||
isAbstract);
|
||||
|
||||
if (completionNodes.length) {
|
||||
if (preferences.includeCompletionsWithSnippetText) {
|
||||
addSnippets(completionNodes, tabstopStart);
|
||||
}
|
||||
insertText = printer.printSnippetList(ListFormat.MultiLine, factory.createNodeArray(completionNodes), sourceFile);
|
||||
}
|
||||
|
||||
return { insertText, isSnippet };
|
||||
}
|
||||
|
||||
function getPresentModifiers(contextToken: Node): ModifierFlags {
|
||||
let modifiers = ModifierFlags.None;
|
||||
let contextMod;
|
||||
/*
|
||||
Cases supported:
|
||||
In
|
||||
`class C {
|
||||
public abstract |
|
||||
}`
|
||||
`contextToken` is ``abstract`` (as an identifier),
|
||||
`contextToken.parent` is property declaration,
|
||||
`location` is class declaration ``class C { ... }``.
|
||||
In
|
||||
`class C {
|
||||
protected override m|
|
||||
}`
|
||||
`contextToken` is ``override`` (as a keyword),
|
||||
`contextToken.parent` is property declaration,
|
||||
`location` is identifier ``m``,
|
||||
`location.parent` is property declaration ``protected override m``,
|
||||
`location.parent.parent` is class declaration ``class C { ... }``.
|
||||
*/
|
||||
if (contextMod = isModifierLike(contextToken)) {
|
||||
modifiers |= modifierToFlag(contextMod);
|
||||
}
|
||||
if (isPropertyDeclaration(contextToken.parent)) {
|
||||
modifiers |= modifiersToFlags(contextToken.parent.modifiers);
|
||||
}
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
function isModifierLike(node: Node): ModifierSyntaxKind | undefined {
|
||||
if (isModifier(node)) {
|
||||
return node.kind;
|
||||
}
|
||||
if (isIdentifier(node) && node.originalKeywordKind && isModifierKind(node.originalKeywordKind)) {
|
||||
return node.originalKeywordKind;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function addSnippets(nodes: Node[], orderStart: number): void {
|
||||
let order = orderStart;
|
||||
for (const node of nodes) {
|
||||
addSnippetsWorker(node, /*parent*/ undefined);
|
||||
}
|
||||
|
||||
function addSnippetsWorker(node: Node, parent: Node | undefined) {
|
||||
if (isVariableLike(node) && node.kind === SyntaxKind.Parameter) {
|
||||
// Placeholder
|
||||
setSnippetElement(node.name, { kind: SnippetKind.Placeholder, order });
|
||||
order += 1;
|
||||
if (node.type) {
|
||||
setSnippetElement(node.type, { kind: SnippetKind.Placeholder, order });
|
||||
order += 1;
|
||||
}
|
||||
}
|
||||
else if (isTypeNode(node) && parent && isFunctionLikeDeclaration(parent)) {
|
||||
setSnippetElement(node, { kind: SnippetKind.Placeholder, order });
|
||||
order += 1;
|
||||
}
|
||||
else if (isTypeParameterDeclaration(node) && parent && isFunctionLikeDeclaration(parent)) {
|
||||
setSnippetElement(node, { kind: SnippetKind.Placeholder, order });
|
||||
order += 1;
|
||||
}
|
||||
|
||||
forEachChild(node, child => addSnippetsWorker(child, node));
|
||||
}
|
||||
}
|
||||
|
||||
function createSnippetPrinter(
|
||||
printerOptions: PrinterOptions,
|
||||
) {
|
||||
const printer = createPrinter(printerOptions);
|
||||
const baseWriter = createTextWriter(getNewLineCharacter(printerOptions));
|
||||
const writer: EmitTextWriter = {
|
||||
...baseWriter,
|
||||
write: s => baseWriter.write(escapeSnippetText(s)),
|
||||
nonEscapingWrite: baseWriter.write,
|
||||
writeLiteral: s => baseWriter.writeLiteral(escapeSnippetText(s)),
|
||||
writeStringLiteral: s => baseWriter.writeStringLiteral(escapeSnippetText(s)),
|
||||
writeSymbol: (s, symbol) => baseWriter.writeSymbol(escapeSnippetText(s), symbol),
|
||||
writeParameter: s => baseWriter.writeParameter(escapeSnippetText(s)),
|
||||
writeComment: s => baseWriter.writeComment(escapeSnippetText(s)),
|
||||
writeProperty: s => baseWriter.writeProperty(escapeSnippetText(s)),
|
||||
};
|
||||
|
||||
return {
|
||||
printSnippetList,
|
||||
};
|
||||
|
||||
|
||||
/* Snippet-escaping version of `printer.printList`. */
|
||||
function printSnippetList(
|
||||
format: ListFormat,
|
||||
list: NodeArray<Node>,
|
||||
sourceFile: SourceFile | undefined,
|
||||
): string {
|
||||
writer.clear();
|
||||
printer.writeList(format, list, sourceFile, writer);
|
||||
return writer.getText();
|
||||
}
|
||||
}
|
||||
|
||||
function originToCompletionEntryData(origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport): CompletionEntryData | undefined {
|
||||
|
@ -863,7 +1135,8 @@ namespace ts.Completions {
|
|||
contextToken: Node | undefined,
|
||||
location: Node,
|
||||
sourceFile: SourceFile,
|
||||
typeChecker: TypeChecker,
|
||||
host: LanguageServiceHost,
|
||||
program: Program,
|
||||
target: ScriptTarget,
|
||||
log: Log,
|
||||
kind: CompletionKind,
|
||||
|
@ -881,6 +1154,7 @@ namespace ts.Completions {
|
|||
const start = timestamp();
|
||||
const variableDeclaration = getVariableDeclaration(location);
|
||||
const useSemicolons = probablyUsesSemicolons(sourceFile);
|
||||
const typeChecker = program.getTypeChecker();
|
||||
// Tracks unique names.
|
||||
// Value is set to false for global variables or completions from external module exports, because we can have multiple of those;
|
||||
// true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports.
|
||||
|
@ -904,7 +1178,8 @@ namespace ts.Completions {
|
|||
contextToken,
|
||||
location,
|
||||
sourceFile,
|
||||
typeChecker,
|
||||
host,
|
||||
program,
|
||||
name,
|
||||
needsConvertPropertyAccess,
|
||||
origin,
|
||||
|
@ -914,7 +1189,8 @@ namespace ts.Completions {
|
|||
importCompletionNode,
|
||||
useSemicolons,
|
||||
compilerOptions,
|
||||
preferences
|
||||
preferences,
|
||||
kind,
|
||||
);
|
||||
if (!entry) {
|
||||
continue;
|
||||
|
|
|
@ -1,18 +1,35 @@
|
|||
/* @internal */
|
||||
namespace ts.Completions.StringCompletions {
|
||||
export function getStringLiteralCompletions(sourceFile: SourceFile, position: number, contextToken: Node | undefined, checker: TypeChecker, options: CompilerOptions, host: LanguageServiceHost, log: Log, preferences: UserPreferences): CompletionInfo | undefined {
|
||||
export function getStringLiteralCompletions(
|
||||
sourceFile: SourceFile,
|
||||
position: number,
|
||||
contextToken: Node | undefined,
|
||||
options: CompilerOptions,
|
||||
host: LanguageServiceHost,
|
||||
program: Program,
|
||||
log: Log,
|
||||
preferences: UserPreferences): CompletionInfo | undefined {
|
||||
if (isInReferenceComment(sourceFile, position)) {
|
||||
const entries = getTripleSlashReferenceCompletion(sourceFile, position, options, host);
|
||||
return entries && convertPathCompletions(entries);
|
||||
}
|
||||
if (isInString(sourceFile, position, contextToken)) {
|
||||
if (!contextToken || !isStringLiteralLike(contextToken)) return undefined;
|
||||
const entries = getStringLiteralCompletionEntries(sourceFile, contextToken, position, checker, options, host, preferences);
|
||||
return convertStringLiteralCompletions(entries, contextToken, sourceFile, checker, log, options, preferences);
|
||||
const entries = getStringLiteralCompletionEntries(sourceFile, contextToken, position, program.getTypeChecker(), options, host, preferences);
|
||||
return convertStringLiteralCompletions(entries, contextToken, sourceFile, host, program, log, options, preferences);
|
||||
}
|
||||
}
|
||||
|
||||
function convertStringLiteralCompletions(completion: StringLiteralCompletion | undefined, contextToken: StringLiteralLike, sourceFile: SourceFile, checker: TypeChecker, log: Log, options: CompilerOptions, preferences: UserPreferences): CompletionInfo | undefined {
|
||||
function convertStringLiteralCompletions(
|
||||
completion: StringLiteralCompletion | undefined,
|
||||
contextToken: StringLiteralLike,
|
||||
sourceFile: SourceFile,
|
||||
host: LanguageServiceHost,
|
||||
program: Program,
|
||||
log: Log,
|
||||
options: CompilerOptions,
|
||||
preferences: UserPreferences,
|
||||
): CompletionInfo | undefined {
|
||||
if (completion === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -30,7 +47,8 @@ namespace ts.Completions.StringCompletions {
|
|||
contextToken,
|
||||
sourceFile,
|
||||
sourceFile,
|
||||
checker,
|
||||
host,
|
||||
program,
|
||||
ScriptTarget.ESNext,
|
||||
log,
|
||||
CompletionKind.String,
|
||||
|
|
|
@ -1037,7 +1037,7 @@ namespace ts.textChanges {
|
|||
/** Note: output node may be mutated input node. */
|
||||
export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
|
||||
const writer = createWriter(newLineCharacter);
|
||||
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
|
||||
const newLine = getNewLineKind(newLineCharacter);
|
||||
createPrinter({
|
||||
newLine,
|
||||
neverAsciiEscape: true,
|
||||
|
|
|
@ -3279,5 +3279,9 @@ namespace ts {
|
|||
return decisionFromFile ?? program.usesUriStyleNodeCoreModules;
|
||||
}
|
||||
|
||||
export function getNewLineKind(newLineCharacter: string): NewLineKind {
|
||||
return newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
||||
|
|
|
@ -4042,6 +4042,7 @@ declare namespace ts {
|
|||
readonly includeCompletionsWithSnippetText?: boolean;
|
||||
readonly includeAutomaticOptionalChainCompletions?: boolean;
|
||||
readonly includeCompletionsWithInsertText?: boolean;
|
||||
readonly includeCompletionsWithClassMemberSnippets?: boolean;
|
||||
readonly allowIncompleteCompletions?: boolean;
|
||||
readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative";
|
||||
/** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */
|
||||
|
@ -9537,6 +9538,13 @@ declare namespace ts.server.protocol {
|
|||
* values, with insertion text to replace preceding `.` tokens with `?.`.
|
||||
*/
|
||||
readonly includeAutomaticOptionalChainCompletions?: boolean;
|
||||
/**
|
||||
* If enabled, completions for class members (e.g. methods and properties) will include
|
||||
* a whole declaration for the member.
|
||||
* E.g., `class A { f| }` could be completed to `class A { foo(): number {} }`, instead of
|
||||
* `class A { foo }`.
|
||||
*/
|
||||
readonly includeCompletionsWithClassMemberSnippets?: boolean;
|
||||
readonly allowIncompleteCompletions?: boolean;
|
||||
readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative";
|
||||
/** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */
|
||||
|
|
|
@ -4042,6 +4042,7 @@ declare namespace ts {
|
|||
readonly includeCompletionsWithSnippetText?: boolean;
|
||||
readonly includeAutomaticOptionalChainCompletions?: boolean;
|
||||
readonly includeCompletionsWithInsertText?: boolean;
|
||||
readonly includeCompletionsWithClassMemberSnippets?: boolean;
|
||||
readonly allowIncompleteCompletions?: boolean;
|
||||
readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative";
|
||||
/** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */
|
||||
|
|
343
tests/cases/fourslash/completionsOverridingMethod.ts
Normal file
343
tests/cases/fourslash/completionsOverridingMethod.ts
Normal file
|
@ -0,0 +1,343 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @newline: LF
|
||||
// @Filename: a.ts
|
||||
// Case: Concrete class implements abstract method
|
||||
////abstract class ABase {
|
||||
//// abstract foo(param1: string, param2: boolean): Promise<void>;
|
||||
////}
|
||||
////
|
||||
////class ASub extends ABase {
|
||||
//// f/*a*/
|
||||
////}
|
||||
|
||||
// @Filename: b.ts
|
||||
// Case: Concrete class overrides concrete method
|
||||
////class BBase {
|
||||
//// foo(a: string, b: string): string {
|
||||
//// return a + b;
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
////class BSub extends BBase {
|
||||
//// f/*b*/
|
||||
////}
|
||||
|
||||
// @Filename: c.ts
|
||||
// Case: Multiple overrides, concrete class overrides concrete method
|
||||
////class CBase {
|
||||
//// foo(a: string | number): string {
|
||||
//// return a + "";
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
////class CSub extends CBase {
|
||||
//// foo(a: string): string {
|
||||
//// return add;
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
////class CSub2 extends CSub {
|
||||
//// f/*c*/
|
||||
////}
|
||||
|
||||
// @Filename: d.ts
|
||||
// Case: Abstract class extends abstract class
|
||||
////abstract class DBase {
|
||||
//// abstract foo(a: string): string;
|
||||
////}
|
||||
////
|
||||
////abstract class DSub extends DBase {
|
||||
//// f/*d*/
|
||||
////}
|
||||
|
||||
// @Filename: e.ts
|
||||
// Case: Class implements interface
|
||||
////interface EBase {
|
||||
//// foo(a: string): string;
|
||||
////}
|
||||
////
|
||||
////class ESub implements EBase {
|
||||
//// f/*e*/
|
||||
////}
|
||||
|
||||
// @Filename: f.ts
|
||||
// Case: Abstract class implements interface
|
||||
////interface FBase {
|
||||
//// foo(a: string): string;
|
||||
////}
|
||||
////
|
||||
////abstract class FSub implements FBase {
|
||||
//// f/*f*/
|
||||
////}
|
||||
|
||||
// @Filename: g.ts
|
||||
// Case: Method has overloads
|
||||
////interface GBase {
|
||||
//// foo(a: string): string;
|
||||
//// foo(a: undefined, b: number): string;
|
||||
////}
|
||||
////
|
||||
////class GSub implements GBase {
|
||||
//// f/*g*/
|
||||
////}
|
||||
|
||||
// @Filename: h.ts
|
||||
// Case: Static method
|
||||
// Note: static methods are only suggested for completions after the `static` keyword
|
||||
////class HBase {
|
||||
//// static met(n: number): number {
|
||||
//// return n;
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
////class HSub extends HBase {
|
||||
//// /*h1*/
|
||||
//// static /*h2*/
|
||||
////}
|
||||
|
||||
// @Filename: i.ts
|
||||
// Case: Generic method
|
||||
////class IBase {
|
||||
//// met<T>(t: T): T {
|
||||
//// return t;
|
||||
//// }
|
||||
//// metcons<T extends string | number>(t: T): T {
|
||||
//// return t;
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
////class ISub extends IBase {
|
||||
//// /*i*/
|
||||
////}
|
||||
|
||||
|
||||
verify.completions({
|
||||
marker: "a",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "foo",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"foo(param1: string, param2: boolean): Promise<void> {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "b",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "foo",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"foo(a: string, b: string): string {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "c",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "foo",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"foo(a: string): string {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "d",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "foo",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"foo(a: string): string {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "e",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "foo",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"foo(a: string): string {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "f",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "foo",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"foo(a: string): string {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "g",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "foo",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"foo(a: string): string;\n\
|
||||
foo(a: undefined, b: number): string;\n\
|
||||
foo(a: any, b?: any): string {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "h1",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
excludes: "met",
|
||||
});
|
||||
verify.completions({
|
||||
marker: "h2",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "met",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"met(n: number): number {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "i",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "met",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"met<T>(t: T): T {\n}\n",
|
||||
},
|
||||
{
|
||||
name: "metcons",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"metcons<T extends string | number>(t: T): T {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
37
tests/cases/fourslash/completionsOverridingMethod1.ts
Normal file
37
tests/cases/fourslash/completionsOverridingMethod1.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @newline: LF
|
||||
// @Filename: h.ts
|
||||
// @noImplicitOverride: true
|
||||
// Case: Suggested method needs `override` modifier
|
||||
////class HBase {
|
||||
//// foo(a: string): void {}
|
||||
////}
|
||||
////
|
||||
////class HSub extends HBase {
|
||||
//// f/*h*/
|
||||
////}
|
||||
|
||||
|
||||
verify.completions({
|
||||
marker: "h",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "foo",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"override foo(a: string): void {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
36
tests/cases/fourslash/completionsOverridingMethod2.ts
Normal file
36
tests/cases/fourslash/completionsOverridingMethod2.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @newline: LF
|
||||
// @Filename: a.ts
|
||||
// Case: Snippet text needs escaping
|
||||
////interface DollarSign {
|
||||
//// "$usd"(a: number): number;
|
||||
////}
|
||||
////class USD implements DollarSign {
|
||||
//// /*a*/
|
||||
////}
|
||||
|
||||
|
||||
verify.completions({
|
||||
marker: "a",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: true,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "$usd",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
isSnippet: true,
|
||||
insertText:
|
||||
"\"\\$usd\"(${2:a}: ${3:number}): ${4:number} {\n $1\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
36
tests/cases/fourslash/completionsOverridingMethod3.ts
Normal file
36
tests/cases/fourslash/completionsOverridingMethod3.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @newline: LF
|
||||
// @Filename: boo.d.ts
|
||||
// Case: Declaration files
|
||||
////interface Ghost {
|
||||
//// boo(): string;
|
||||
////}
|
||||
////
|
||||
////declare class Poltergeist implements Ghost {
|
||||
//// /*b*/
|
||||
////}
|
||||
|
||||
|
||||
verify.completions({
|
||||
marker: "b",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "boo",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"boo(): string;\n",
|
||||
}
|
||||
],
|
||||
});
|
64
tests/cases/fourslash/completionsOverridingMethod4.ts
Normal file
64
tests/cases/fourslash/completionsOverridingMethod4.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @newline: LF
|
||||
// @Filename: secret.ts
|
||||
// Case: accessibility modifier inheritance
|
||||
////class Secret {
|
||||
//// #secret(): string {
|
||||
//// return "secret";
|
||||
//// }
|
||||
////
|
||||
//// private tell(): string {
|
||||
//// return this.#secret();
|
||||
//// }
|
||||
////
|
||||
//// protected hint(): string {
|
||||
//// return "hint";
|
||||
//// }
|
||||
////
|
||||
//// public refuse(): string {
|
||||
//// return "no comments";
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
////class Gossip extends Secret {
|
||||
//// /* no telling secrets */
|
||||
//// /*a*/
|
||||
////}
|
||||
|
||||
|
||||
verify.completions({
|
||||
marker: "a",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
excludes: [
|
||||
"tell",
|
||||
"#secret",
|
||||
],
|
||||
includes: [
|
||||
{
|
||||
name: "hint",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText: "protected hint(): string {\n}\n",
|
||||
},
|
||||
{
|
||||
name: "refuse",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText: "public refuse(): string {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
116
tests/cases/fourslash/completionsOverridingMethod5.ts
Normal file
116
tests/cases/fourslash/completionsOverridingMethod5.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @newline: LF
|
||||
// @Filename: a.ts
|
||||
// Case: abstract methods
|
||||
////abstract class Ab {
|
||||
//// abstract met(n: string): void;
|
||||
//// met2(n: number): void {
|
||||
////
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
////abstract class Abc extends Ab {
|
||||
//// /*a*/
|
||||
//// abstract /*b*/
|
||||
//// abstract m/*c*/
|
||||
////}
|
||||
|
||||
|
||||
verify.completions({
|
||||
marker: "a",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "met",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText: "met(n: string): void {\n}\n",
|
||||
},
|
||||
{
|
||||
name: "met2",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText: "met2(n: number): void {\n}\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "b",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "met",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText: "met(n: string): void;\n",
|
||||
},
|
||||
{
|
||||
name: "met2",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText: "met2(n: number): void;\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "c",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "met",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText: "met(n: string): void;\n",
|
||||
},
|
||||
{
|
||||
name: "met2",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText: "met2(n: number): void;\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
|
96
tests/cases/fourslash/completionsOverridingMethod6.ts
Normal file
96
tests/cases/fourslash/completionsOverridingMethod6.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: a.ts
|
||||
// @newline: LF
|
||||
// Case: modifier inheritance/deduplication
|
||||
////class A {
|
||||
//// public method(): number {
|
||||
//// return 0;
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
////abstract class B extends A {
|
||||
//// public abstract /*b*/
|
||||
////}
|
||||
////
|
||||
////class C extends A {
|
||||
//// public override m/*a*/
|
||||
////}
|
||||
////
|
||||
////interface D {
|
||||
//// fun(a: number): number;
|
||||
//// fun(a: undefined, b: string): number;
|
||||
////}
|
||||
////
|
||||
////class E implements D {
|
||||
//// public f/*c*/
|
||||
////}
|
||||
|
||||
verify.completions({
|
||||
marker: "a",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "method",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText: "method(): number {\n}\n",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "b",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "method",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText: "method(): number;\n",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "c",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "fun",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"fun(a: number): number;\n\
|
||||
public fun(a: undefined, b: string): number;\n\
|
||||
public fun(a: any, b?: any): number {\n}\n",
|
||||
},
|
||||
],
|
||||
});
|
38
tests/cases/fourslash/completionsOverridingMethod7.ts
Normal file
38
tests/cases/fourslash/completionsOverridingMethod7.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: a.ts
|
||||
// @newline: LF
|
||||
// Case: abstract overloads
|
||||
////abstract class Base {
|
||||
//// abstract M<T>(t: T): void;
|
||||
//// abstract M<T>(t: T, x: number): void;
|
||||
////}
|
||||
////
|
||||
////abstract class Derived extends Base {
|
||||
//// abstract /*a*/
|
||||
////}
|
||||
|
||||
verify.completions({
|
||||
marker: "a",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "M",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
`M<T>(t: T): void;
|
||||
abstract M<T>(t: T, x: number): void;
|
||||
`,
|
||||
},
|
||||
],
|
||||
});
|
39
tests/cases/fourslash/completionsOverridingProperties.ts
Normal file
39
tests/cases/fourslash/completionsOverridingProperties.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @newline: LF
|
||||
// @Filename: a.ts
|
||||
// Case: Properties
|
||||
////class Base {
|
||||
//// protected foo: string = "bar";
|
||||
////
|
||||
////}
|
||||
////
|
||||
////class Sub extends Base {
|
||||
//// /*a*/
|
||||
////}
|
||||
|
||||
|
||||
|
||||
|
||||
verify.completions({
|
||||
marker: "a",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: false,
|
||||
includeCompletionsWithClassMemberSnippets: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "foo",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
insertText:
|
||||
"protected foo: string;\n",
|
||||
}
|
||||
],
|
||||
});
|
|
@ -646,6 +646,7 @@ declare namespace FourSlashInterface {
|
|||
readonly includeCompletionsForImportStatements?: boolean;
|
||||
readonly includeCompletionsWithSnippetText?: boolean;
|
||||
readonly includeCompletionsWithInsertText?: boolean;
|
||||
readonly includeCompletionsWithClassMemberSnippets?: boolean;
|
||||
readonly allowIncompleteCompletions?: boolean;
|
||||
/** @deprecated use `includeCompletionsWithInsertText` */
|
||||
readonly includeInsertTextCompletions?: boolean;
|
||||
|
|
|
@ -84,6 +84,7 @@ verify.completions({
|
|||
],
|
||||
preferences: {
|
||||
jsxAttributeCompletionStyle: "auto",
|
||||
includeCompletionsWithSnippetText: true
|
||||
includeCompletionsWithSnippetText: true,
|
||||
includeCompletionsWithInsertText: true,
|
||||
}
|
||||
});
|
|
@ -90,6 +90,7 @@ verify.completions({
|
|||
],
|
||||
preferences: {
|
||||
jsxAttributeCompletionStyle: "braces",
|
||||
includeCompletionsWithSnippetText: true
|
||||
includeCompletionsWithSnippetText: true,
|
||||
includeCompletionsWithInsertText: true,
|
||||
}
|
||||
});
|
Loading…
Reference in a new issue