diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 0df29c10ad657..445537d3de873 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1940,7 +1940,7 @@ namespace ts { emitPlaceholder(hint, node, snippet); break; case SnippetKind.TabStop: - emitTabStop(snippet); + emitTabStop(hint, node, snippet); break; } } @@ -1952,7 +1952,12 @@ namespace ts { // `${2:...}` } - function emitTabStop(snippet: TabStop) { + function emitTabStop(hint: EmitHint, node: Node, snippet: TabStop) { + // A tab stop should only be attached to an empty node, i.e. a node that doesn't emit any text. + Debug.assert(node.kind === SyntaxKind.EmptyStatement, + `A tab stop cannot be attached to a node of kind ${Debug.formatSyntaxKind(node.kind)}.`); + Debug.assert(hint !== EmitHint.EmbeddedStatement, + `A tab stop cannot be attached to an embedded statement.`); nonEscapingWrite(`\$${snippet.order}`); } diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 34fd2365365fe..77a3da2d3cceb 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -1075,7 +1075,7 @@ namespace ts { if (flags & ModifierFlags.Override) result.push(createModifier(SyntaxKind.OverrideKeyword)); if (flags & ModifierFlags.Readonly) result.push(createModifier(SyntaxKind.ReadonlyKeyword)); if (flags & ModifierFlags.Async) result.push(createModifier(SyntaxKind.AsyncKeyword)); - return result; + return result.length ? result : undefined; } // @@ -6080,32 +6080,36 @@ namespace ts { function updateModifiers(node: T, modifiers: readonly Modifier[] | ModifierFlags): T; function updateModifiers(node: HasModifiers, modifiers: readonly Modifier[] | ModifierFlags) { + let modifierArray; if (typeof modifiers === "number") { - modifiers = createModifiersFromModifierFlags(modifiers); + modifierArray = createModifiersFromModifierFlags(modifiers); } - return isParameter(node) ? updateParameterDeclaration(node, node.decorators, modifiers, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer) : - isPropertySignature(node) ? updatePropertySignature(node, modifiers, node.name, node.questionToken, node.type) : - isPropertyDeclaration(node) ? updatePropertyDeclaration(node, node.decorators, modifiers, node.name, node.questionToken ?? node.exclamationToken, node.type, node.initializer) : - isMethodSignature(node) ? updateMethodSignature(node, modifiers, node.name, node.questionToken, node.typeParameters, node.parameters, node.type) : - isMethodDeclaration(node) ? updateMethodDeclaration(node, node.decorators, modifiers, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body) : - isConstructorDeclaration(node) ? updateConstructorDeclaration(node, node.decorators, modifiers, node.parameters, node.body) : - isGetAccessorDeclaration(node) ? updateGetAccessorDeclaration(node, node.decorators, modifiers, node.name, node.parameters, node.type, node.body) : - isSetAccessorDeclaration(node) ? updateSetAccessorDeclaration(node, node.decorators, modifiers, node.name, node.parameters, node.body) : - isIndexSignatureDeclaration(node) ? updateIndexSignature(node, node.decorators, modifiers, node.parameters, node.type) : - isFunctionExpression(node) ? updateFunctionExpression(node, modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : - isArrowFunction(node) ? updateArrowFunction(node, modifiers, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, node.body) : - isClassExpression(node) ? updateClassExpression(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : - isVariableStatement(node) ? updateVariableStatement(node, modifiers, node.declarationList) : - isFunctionDeclaration(node) ? updateFunctionDeclaration(node, node.decorators, modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : - isClassDeclaration(node) ? updateClassDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : - isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : - isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.type) : - isEnumDeclaration(node) ? updateEnumDeclaration(node, node.decorators, modifiers, node.name, node.members) : - isModuleDeclaration(node) ? updateModuleDeclaration(node, node.decorators, modifiers, node.name, node.body) : - isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, node.decorators, modifiers, node.isTypeOnly, node.name, node.moduleReference) : - isImportDeclaration(node) ? updateImportDeclaration(node, node.decorators, modifiers, node.importClause, node.moduleSpecifier, node.assertClause) : - isExportAssignment(node) ? updateExportAssignment(node, node.decorators, modifiers, node.expression) : - isExportDeclaration(node) ? updateExportDeclaration(node, node.decorators, modifiers, node.isTypeOnly, node.exportClause, node.moduleSpecifier, node.assertClause) : + else { + modifierArray = modifiers; + } + return isParameter(node) ? updateParameterDeclaration(node, node.decorators, modifierArray, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer) : + isPropertySignature(node) ? updatePropertySignature(node, modifierArray, node.name, node.questionToken, node.type) : + isPropertyDeclaration(node) ? updatePropertyDeclaration(node, node.decorators, modifierArray, node.name, node.questionToken ?? node.exclamationToken, node.type, node.initializer) : + isMethodSignature(node) ? updateMethodSignature(node, modifierArray, node.name, node.questionToken, node.typeParameters, node.parameters, node.type) : + isMethodDeclaration(node) ? updateMethodDeclaration(node, node.decorators, modifierArray, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body) : + isConstructorDeclaration(node) ? updateConstructorDeclaration(node, node.decorators, modifierArray, node.parameters, node.body) : + isGetAccessorDeclaration(node) ? updateGetAccessorDeclaration(node, node.decorators, modifierArray, node.name, node.parameters, node.type, node.body) : + isSetAccessorDeclaration(node) ? updateSetAccessorDeclaration(node, node.decorators, modifierArray, node.name, node.parameters, node.body) : + isIndexSignatureDeclaration(node) ? updateIndexSignature(node, node.decorators, modifierArray, node.parameters, node.type) : + isFunctionExpression(node) ? updateFunctionExpression(node, modifierArray, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : + isArrowFunction(node) ? updateArrowFunction(node, modifierArray, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, node.body) : + isClassExpression(node) ? updateClassExpression(node, node.decorators, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : + isVariableStatement(node) ? updateVariableStatement(node, modifierArray, node.declarationList) : + isFunctionDeclaration(node) ? updateFunctionDeclaration(node, node.decorators, modifierArray, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : + isClassDeclaration(node) ? updateClassDeclaration(node, node.decorators, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : + isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, node.decorators, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : + isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, node.decorators, modifierArray, node.name, node.typeParameters, node.type) : + isEnumDeclaration(node) ? updateEnumDeclaration(node, node.decorators, modifierArray, node.name, node.members) : + isModuleDeclaration(node) ? updateModuleDeclaration(node, node.decorators, modifierArray, node.name, node.body) : + isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, node.decorators, modifierArray, node.isTypeOnly, node.name, node.moduleReference) : + isImportDeclaration(node) ? updateImportDeclaration(node, node.decorators, modifierArray, node.importClause, node.moduleSpecifier, node.assertClause) : + isExportAssignment(node) ? updateExportAssignment(node, node.decorators, modifierArray, node.expression) : + isExportDeclaration(node) ? updateExportDeclaration(node, node.decorators, modifierArray, node.isTypeOnly, node.exportClause, node.moduleSpecifier, node.assertClause) : Debug.assertNever(node); } diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 6cea3b85055ee..ab0294f9b44ce 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -1610,7 +1610,7 @@ namespace ts { } // Elide "public" modifier, as it is the default - function maskModifiers(node: Node, modifierMask?: ModifierFlags, modifierAdditions?: ModifierFlags): Modifier[] { + function maskModifiers(node: Node, modifierMask?: ModifierFlags, modifierAdditions?: ModifierFlags): Modifier[] | undefined { return factory.createModifiersFromModifierFlags(maskModifierFlags(node, modifierMask, modifierAdditions)); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e7e60dc025210..130a156c33b54 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -7169,7 +7169,7 @@ namespace ts { // createModifier(kind: T): ModifierToken; - createModifiersFromModifierFlags(flags: ModifierFlags): Modifier[]; + createModifiersFromModifierFlags(flags: ModifierFlags): Modifier[] | undefined; // // Names @@ -7841,7 +7841,7 @@ namespace ts { * - *DO NOT USE THIS* if a more appropriate function is available. */ /* @internal */ cloneNode(node: T): T; - /* @internal */ updateModifiers(node: T, modifiers: readonly Modifier[] | ModifierFlags): T; + /* @internal */ updateModifiers(node: T, modifiers: readonly Modifier[] | ModifierFlags | undefined): T; } /* @internal */ diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index b5cb4b362d3ab..19b82ac6542a0 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -1348,7 +1348,11 @@ namespace FourSlash { if (options) { this.configure(options); } - return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition, options); + return this.languageService.getCompletionsAtPosition( + this.activeFile.fileName, + this.currentCaretPosition, + options, + this.formatCodeSettings); } private getCompletionEntryDetails(entryName: string, source: string | undefined, data: ts.CompletionEntryData | undefined, preferences?: ts.UserPreferences): ts.CompletionEntryDetails | undefined { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 6f7245b01a9ff..22e88498203d1 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -472,8 +472,8 @@ namespace Harness.LanguageService { const responseFormat = format || ts.SemanticClassificationFormat.Original; return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length, responseFormat)); } - getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined): ts.CompletionInfo { - return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences)); + getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined, formattingSettings: ts.FormatCodeSettings | undefined): ts.CompletionInfo { + return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences, formattingSettings)); } getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | undefined, source: string | undefined, preferences: ts.UserPreferences | undefined, data: ts.CompletionEntryData | undefined): ts.CompletionEntryDetails { return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, preferences, data)); diff --git a/src/server/session.ts b/src/server/session.ts index 3110c411f7e35..03f57a218386f 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1842,13 +1842,18 @@ namespace ts.server { const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; const position = this.getPosition(args, scriptInfo); - const completions = project.getLanguageService().getCompletionsAtPosition(file, position, { - ...convertUserPreferences(this.getPreferences(file)), - triggerCharacter: args.triggerCharacter, - triggerKind: args.triggerKind as CompletionTriggerKind | undefined, - includeExternalModuleExports: args.includeExternalModuleExports, - includeInsertTextCompletions: args.includeInsertTextCompletions - }); + const completions = project.getLanguageService().getCompletionsAtPosition( + file, + position, + { + ...convertUserPreferences(this.getPreferences(file)), + triggerCharacter: args.triggerCharacter, + triggerKind: args.triggerKind as CompletionTriggerKind | undefined, + includeExternalModuleExports: args.includeExternalModuleExports, + includeInsertTextCompletions: args.includeInsertTextCompletions, + }, + project.projectService.getFormatCodeOptions(file), + ); if (completions === undefined) return undefined; if (kind === protocol.CommandTypes.CompletionsFull) return completions; diff --git a/src/services/completions.ts b/src/services/completions.ts index ca265f268f035..bb19bf2798087 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -229,6 +229,7 @@ namespace ts.Completions { triggerCharacter: CompletionsTriggerCharacter | undefined, completionKind: CompletionTriggerKind | undefined, cancellationToken: CancellationToken, + formatContext?: formatting.FormatContext, ): CompletionInfo | undefined { const { previousToken } = getRelevantTokens(position, sourceFile); if (triggerCharacter && !isInString(sourceFile, position, previousToken) && !isValidTrigger(sourceFile, triggerCharacter, previousToken, position)) { @@ -275,7 +276,7 @@ namespace ts.Completions { switch (completionData.kind) { case CompletionDataKind.Data: - const response = completionInfoFromData(sourceFile, host, program, compilerOptions, log, completionData, preferences); + const response = completionInfoFromData(sourceFile, host, program, compilerOptions, log, completionData, preferences, formatContext); if (response?.isIncomplete) { incompleteCompletionsCache?.set(response); } @@ -438,6 +439,7 @@ namespace ts.Completions { log: Log, completionData: CompletionData, preferences: UserPreferences, + formatContext: formatting.FormatContext | undefined, ): CompletionInfo | undefined { const { symbols, @@ -485,6 +487,7 @@ namespace ts.Completions { completionKind, preferences, compilerOptions, + formatContext, isTypeOnlyLocation, propertyAccessToConvert, isJsxIdentifierExpected, @@ -515,6 +518,7 @@ namespace ts.Completions { completionKind, preferences, compilerOptions, + formatContext, isTypeOnlyLocation, propertyAccessToConvert, isJsxIdentifierExpected, @@ -664,6 +668,7 @@ namespace ts.Completions { options: CompilerOptions, preferences: UserPreferences, completionKind: CompletionKind, + formatContext: formatting.FormatContext | undefined, ): CompletionEntry | undefined { let insertText: string | undefined; let replacementSpan = getReplacementSpanForContextToken(replacementToken); @@ -732,7 +737,7 @@ namespace ts.Completions { completionKind === CompletionKind.MemberLike && isClassLikeMemberCompletion(symbol, location)) { let importAdder; - ({ insertText, isSnippet, importAdder } = getEntryForMemberCompletion(host, program, options, preferences, name, symbol, location, contextToken)); + ({ insertText, isSnippet, importAdder } = getEntryForMemberCompletion(host, program, options, preferences, name, symbol, location, contextToken, formatContext)); if (importAdder?.hasFixes()) { hasAction = true; source = CompletionSource.ClassMemberSnippet; @@ -858,6 +863,7 @@ namespace ts.Completions { symbol: Symbol, location: Node, contextToken: Node | undefined, + formatContext: formatting.FormatContext | undefined, ): { insertText: string, isSnippet?: true, importAdder?: codefix.ImportAdder } { const classLikeDeclaration = findAncestor(location, isClassLike); if (!classLikeDeclaration) { @@ -878,15 +884,16 @@ namespace ts.Completions { }); const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); + // Create empty body for possible method implementation. let body; if (preferences.includeCompletionsWithSnippetText) { isSnippet = true; // We are adding a tabstop (i.e. `$0`) in the body of the suggested member, // if it has one, so that the cursor ends up in the body once the completion is inserted. // Note: this assumes we won't have more than one body in the completion nodes, which should be the case. - const emptyStatement = factory.createExpressionStatement(factory.createIdentifier("")); - setSnippetElement(emptyStatement, { kind: SnippetKind.TabStop, order: 0 }); - body = factory.createBlock([emptyStatement], /* multiline */ true); + const emptyStmt = factory.createEmptyStatement(); + body = factory.createBlock([emptyStmt], /* multiline */ true); + setSnippetElement(emptyStmt, { kind: SnippetKind.TabStop, order: 0 }); } else { body = factory.createBlock([], /* multiline */ true); @@ -937,7 +944,6 @@ namespace ts.Completions { modifiers = node.modifierFlagsCache | requiredModifiers | presentModifiers; } node = factory.updateModifiers(node, modifiers & (~presentModifiers)); - completionNodes.push(node); }, body, @@ -945,10 +951,38 @@ namespace ts.Completions { isAbstract); if (completionNodes.length) { - insertText = printer.printSnippetList( - ListFormat.MultiLine | ListFormat.NoTrailingNewLine, - factory.createNodeArray(completionNodes), - sourceFile); + // If we have access to formatting settings, we print the nodes using the emitter, + // and then format the printed text. + if (formatContext) { + const syntheticFile = { + text: printer.printSnippetList( + ListFormat.MultiLine | ListFormat.NoTrailingNewLine, + factory.createNodeArray(completionNodes), + sourceFile), + getLineAndCharacterOfPosition(pos: number) { + return getLineAndCharacterOfPosition(this, pos); + }, + }; + + const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); + const changes = flatMap(completionNodes, node => { + const nodeWithPos = textChanges.assignPositionsToNode(node); + return formatting.formatNodeGivenIndentation( + nodeWithPos, + syntheticFile, + sourceFile.languageVariant, + /* indentation */ 0, + /* delta */ 0, + { ...formatContext, options: formatOptions }); + }); + insertText = textChanges.applyChanges(syntheticFile.text, changes); + } + else { // Otherwise, just use emitter to print the new nodes. + insertText = printer.printSnippetList( + ListFormat.MultiLine | ListFormat.NoTrailingNewLine, + factory.createNodeArray(completionNodes), + sourceFile); + } } return { insertText, isSnippet, importAdder }; @@ -998,8 +1032,8 @@ namespace ts.Completions { function createSnippetPrinter( printerOptions: PrinterOptions, ) { - const printer = createPrinter(printerOptions); - const baseWriter = createTextWriter(getNewLineCharacter(printerOptions)); + const baseWriter = textChanges.createWriter(getNewLineCharacter(printerOptions)); + const printer = createPrinter(printerOptions, baseWriter); const writer: EmitTextWriter = { ...baseWriter, write: s => baseWriter.write(escapeSnippetText(s)), @@ -1143,6 +1177,7 @@ namespace ts.Completions { kind: CompletionKind, preferences: UserPreferences, compilerOptions: CompilerOptions, + formatContext: formatting.FormatContext | undefined, isTypeOnlyLocation?: boolean, propertyAccessToConvert?: PropertyAccessExpression, jsxIdentifierExpected?: boolean, @@ -1192,6 +1227,7 @@ namespace ts.Completions { compilerOptions, preferences, kind, + formatContext, ); if (!entry) { continue; @@ -1470,7 +1506,8 @@ namespace ts.Completions { name, symbol, location, - contextToken); + contextToken, + formatContext); if (importAdder) { const changes = textChanges.ChangeTracker.with( { host, formatContext, preferences }, diff --git a/src/services/services.ts b/src/services/services.ts index c65f52216869a..c3b2056d09efe 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1606,7 +1606,7 @@ namespace ts { return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)]; } - function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = emptyOptions): CompletionInfo | undefined { + function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = emptyOptions, formattingSettings?: FormatCodeSettings): CompletionInfo | undefined { // Convert from deprecated options names to new names const fullPreferences: UserPreferences = { ...identity(options), // avoid excess property check @@ -1623,7 +1623,8 @@ namespace ts { fullPreferences, options.triggerCharacter, options.triggerKind, - cancellationToken); + cancellationToken, + formattingSettings && formatting.getFormatContext(formattingSettings, host)); } function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions, data?: CompletionEntryData): CompletionEntryDetails | undefined { diff --git a/src/services/shims.ts b/src/services/shims.ts index 942e943f68f2c..a6cb6eefc3c1a 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -151,7 +151,7 @@ namespace ts { getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string; getEncodedSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string; - getCompletionsAtPosition(fileName: string, position: number, preferences: UserPreferences | undefined): string; + getCompletionsAtPosition(fileName: string, position: number, preferences: UserPreferences | undefined, formattingSettings: FormatCodeSettings | undefined): string; getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined): string; getQuickInfoAtPosition(fileName: string, position: number): string; @@ -956,10 +956,10 @@ namespace ts { * to provide at the given source position and providing a member completion * list if requested. */ - public getCompletionsAtPosition(fileName: string, position: number, preferences: GetCompletionsAtPositionOptions | undefined) { + public getCompletionsAtPosition(fileName: string, position: number, preferences: GetCompletionsAtPositionOptions | undefined, formattingSettings: FormatCodeSettings | undefined) { return this.forwardJSONCall( - `getCompletionsAtPosition('${fileName}', ${position}, ${preferences})`, - () => this.languageService.getCompletionsAtPosition(fileName, position, preferences) + `getCompletionsAtPosition('${fileName}', ${position}, ${preferences}, ${formattingSettings})`, + () => this.languageService.getCompletionsAtPosition(fileName, position, preferences, formattingSettings) ); } diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 287251ffaa051..e453443526cc7 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -54,6 +54,7 @@ namespace ts.Completions.StringCompletions { CompletionKind.String, preferences, options, + /*formatContext*/ undefined, ); // Target will not be used, so arbitrary return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, optionalReplacementSpan, entries }; } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 44ac36ab2046d..bb1b21aa20198 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -1052,15 +1052,6 @@ namespace ts.textChanges { ? "" : options.suffix); } - function getFormatCodeSettingsForWriting({ options }: formatting.FormatContext, sourceFile: SourceFile): FormatCodeSettings { - const shouldAutoDetectSemicolonPreference = !options.semicolons || options.semicolons === SemicolonPreference.Ignore; - const shouldRemoveSemicolons = options.semicolons === SemicolonPreference.Remove || shouldAutoDetectSemicolonPreference && !probablyUsesSemicolons(sourceFile); - return { - ...options, - semicolons: shouldRemoveSemicolons ? SemicolonPreference.Remove : SemicolonPreference.Ignore, - }; - } - /** Note: this may mutate `nodeIn`. */ function getFormattedTextOfNode(nodeIn: Node, sourceFile: SourceFile, pos: number, { indentation, prefix, delta }: InsertNodeOptions, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string { const { node, text } = getNonformattedText(nodeIn, sourceFile, newLineCharacter); @@ -1110,7 +1101,7 @@ namespace ts.textChanges { return skipTrivia(s, 0) === s.length; } - function assignPositionsToNode(node: Node): Node { + export function assignPositionsToNode(node: Node): Node { const visited = visitEachChild(node, assignPositionsToNode, nullTransformationContext, assignPositionsToNodeArray, assignPositionsToNode); // create proxy node for non synthesized nodes const newNode = nodeIsSynthesized(visited) ? visited : Object.create(visited) as Node; @@ -1131,7 +1122,7 @@ namespace ts.textChanges { interface TextChangesWriter extends EmitTextWriter, PrintHandlers {} - function createWriter(newLine: string): TextChangesWriter { + export function createWriter(newLine: string): TextChangesWriter { let lastNonTriviaPosition = 0; const writer = createTextWriter(newLine); diff --git a/src/services/types.ts b/src/services/types.ts index b9bd9f2177d5b..bc5e0d86d87fa 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -416,8 +416,9 @@ namespace ts { * @param position A zero-based index of the character where you want the entries * @param options An object describing how the request was triggered and what kinds * of code actions can be returned with the completions. + * @param formattingSettings settings needed for calling formatting functions. */ - getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): WithMetadata | undefined; + getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined, formattingSettings?: FormatCodeSettings): WithMetadata | undefined; /** * Gets the extended details for a completion entry retrieved from `getCompletionsAtPosition`. diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 593cbc73913f7..a45fd3f441ecf 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -3299,5 +3299,17 @@ namespace ts { : getLocaleSpecificMessage(diag); } + /** + * Get format code settings for a code writing context (e.g. when formatting text changes or completions code). + */ + export function getFormatCodeSettingsForWriting({ options }: formatting.FormatContext, sourceFile: SourceFile): FormatCodeSettings { + const shouldAutoDetectSemicolonPreference = !options.semicolons || options.semicolons === SemicolonPreference.Ignore; + const shouldRemoveSemicolons = options.semicolons === SemicolonPreference.Remove || shouldAutoDetectSemicolonPreference && !probablyUsesSemicolons(sourceFile); + return { + ...options, + semicolons: shouldRemoveSemicolons ? SemicolonPreference.Remove : SemicolonPreference.Ignore, + }; + } + // #endregion } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 4df9e6328ecbd..355037ba05531 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3344,7 +3344,7 @@ declare namespace ts { createTrue(): TrueLiteral; createFalse(): FalseLiteral; createModifier(kind: T): ModifierToken; - createModifiersFromModifierFlags(flags: ModifierFlags): Modifier[]; + createModifiersFromModifierFlags(flags: ModifierFlags): Modifier[] | undefined; createQualifiedName(left: EntityName, right: string | Identifier): QualifiedName; updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier): QualifiedName; createComputedPropertyName(expression: Expression): ComputedPropertyName; @@ -5760,8 +5760,9 @@ declare namespace ts { * @param position A zero-based index of the character where you want the entries * @param options An object describing how the request was triggered and what kinds * of code actions can be returned with the completions. + * @param formattingSettings settings needed for calling formatting functions. */ - getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): WithMetadata | undefined; + getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined, formattingSettings?: FormatCodeSettings): WithMetadata | undefined; /** * Gets the extended details for a completion entry retrieved from `getCompletionsAtPosition`. * @@ -10671,7 +10672,7 @@ declare namespace ts { /** @deprecated Use `factory.createModifier` or the factory supplied by your transformation context instead. */ const createModifier: (kind: T) => ModifierToken; /** @deprecated Use `factory.createModifiersFromModifierFlags` or the factory supplied by your transformation context instead. */ - const createModifiersFromModifierFlags: (flags: ModifierFlags) => Modifier[]; + const createModifiersFromModifierFlags: (flags: ModifierFlags) => Modifier[] | undefined; /** @deprecated Use `factory.createQualifiedName` or the factory supplied by your transformation context instead. */ const createQualifiedName: (left: EntityName, right: string | Identifier) => QualifiedName; /** @deprecated Use `factory.updateQualifiedName` or the factory supplied by your transformation context instead. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index fdd55dab1f2d9..0137fbeb78a75 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3344,7 +3344,7 @@ declare namespace ts { createTrue(): TrueLiteral; createFalse(): FalseLiteral; createModifier(kind: T): ModifierToken; - createModifiersFromModifierFlags(flags: ModifierFlags): Modifier[]; + createModifiersFromModifierFlags(flags: ModifierFlags): Modifier[] | undefined; createQualifiedName(left: EntityName, right: string | Identifier): QualifiedName; updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier): QualifiedName; createComputedPropertyName(expression: Expression): ComputedPropertyName; @@ -5760,8 +5760,9 @@ declare namespace ts { * @param position A zero-based index of the character where you want the entries * @param options An object describing how the request was triggered and what kinds * of code actions can be returned with the completions. + * @param formattingSettings settings needed for calling formatting functions. */ - getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): WithMetadata | undefined; + getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined, formattingSettings?: FormatCodeSettings): WithMetadata | undefined; /** * Gets the extended details for a completion entry retrieved from `getCompletionsAtPosition`. * @@ -6862,7 +6863,7 @@ declare namespace ts { /** @deprecated Use `factory.createModifier` or the factory supplied by your transformation context instead. */ const createModifier: (kind: T) => ModifierToken; /** @deprecated Use `factory.createModifiersFromModifierFlags` or the factory supplied by your transformation context instead. */ - const createModifiersFromModifierFlags: (flags: ModifierFlags) => Modifier[]; + const createModifiersFromModifierFlags: (flags: ModifierFlags) => Modifier[] | undefined; /** @deprecated Use `factory.createQualifiedName` or the factory supplied by your transformation context instead. */ const createQualifiedName: (left: EntityName, right: string | Identifier) => QualifiedName; /** @deprecated Use `factory.updateQualifiedName` or the factory supplied by your transformation context instead. */ diff --git a/tests/cases/fourslash/completionsOverridingMethod11.ts b/tests/cases/fourslash/completionsOverridingMethod11.ts new file mode 100644 index 0000000000000..e406bd16a496b --- /dev/null +++ b/tests/cases/fourslash/completionsOverridingMethod11.ts @@ -0,0 +1,69 @@ +/// + +// @Filename: a.ts +// @newline: LF +// Case: formatting: no semicolons +////function foo() { +//// const a = 1 +//// const b = 2 +//// foo() +//// return a + b +////} +//// +////interface Base { +//// a: string +//// b(a: string): void +//// c(a: string): string +//// c(a: number): number +////} +////class Sub implements Base { +//// /*a*/ +////} + +verify.completions({ + marker: "a", + isNewIdentifierLocation: true, + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + includes: [ + { + name: "a", + sortText: completion.SortText.LocationPriority, + replacementSpan: { + fileName: "", + pos: 0, + end: 0, + }, + insertText: "a: string", + }, + { + name: "b", + sortText: completion.SortText.LocationPriority, + replacementSpan: { + fileName: "", + pos: 0, + end: 0, + }, + insertText: +`b(a: string): void { +}`, + }, + { + name: "c", + sortText: completion.SortText.LocationPriority, + replacementSpan: { + fileName: "", + pos: 0, + end: 0, + }, + insertText: +`c(a: string): string +c(a: number): number +c(a: any): string | number { +}`, + }, + ], +}); \ No newline at end of file