Compare commits

...

2 commits

Author SHA1 Message Date
Gabriela Araujo Britto 64e0bd4263 fixes 2021-11-16 16:41:22 -08:00
Gabriela Araujo Britto 3e59096506 call formatter in completions 2021-11-16 16:41:21 -08:00
10 changed files with 102 additions and 45 deletions

View file

@ -1940,7 +1940,7 @@ namespace ts {
emitPlaceholder(hint, node, snippet); emitPlaceholder(hint, node, snippet);
break; break;
case SnippetKind.TabStop: case SnippetKind.TabStop:
emitTabStop(snippet); emitTabStop(hint, node, snippet);
break; break;
} }
} }
@ -1952,7 +1952,12 @@ namespace ts {
// `${2:...}` // `${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}`); nonEscapingWrite(`\$${snippet.order}`);
} }
@ -4109,9 +4114,13 @@ namespace ts {
} }
function emitModifiers(node: Node, modifiers: NodeArray<Modifier> | undefined) { function emitModifiers(node: Node, modifiers: NodeArray<Modifier> | undefined) {
if (modifiers && modifiers.length) { if (modifiers) {
emitList(node, modifiers, ListFormat.Modifiers); onBeforeEmitNodeArray?.(modifiers);
writeSpace(); if (modifiers.length) {
emitList(node, modifiers, ListFormat.Modifiers);
writeSpace();
}
onAfterEmitNodeArray?.(modifiers);
} }
} }

View file

@ -472,8 +472,8 @@ namespace Harness.LanguageService {
const responseFormat = format || ts.SemanticClassificationFormat.Original; const responseFormat = format || ts.SemanticClassificationFormat.Original;
return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length, responseFormat)); return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length, responseFormat));
} }
getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined): ts.CompletionInfo { getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined, formattingSettings: ts.FormatCodeSettings | undefined): ts.CompletionInfo {
return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences)); 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 { 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)); return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, preferences, data));

View file

@ -1842,13 +1842,18 @@ namespace ts.server {
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
const position = this.getPosition(args, scriptInfo); const position = this.getPosition(args, scriptInfo);
const completions = project.getLanguageService().getCompletionsAtPosition(file, position, { const completions = project.getLanguageService().getCompletionsAtPosition(
...convertUserPreferences(this.getPreferences(file)), file,
triggerCharacter: args.triggerCharacter, position,
triggerKind: args.triggerKind as CompletionTriggerKind | undefined, {
includeExternalModuleExports: args.includeExternalModuleExports, ...convertUserPreferences(this.getPreferences(file)),
includeInsertTextCompletions: args.includeInsertTextCompletions 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 (completions === undefined) return undefined;
if (kind === protocol.CommandTypes.CompletionsFull) return completions; if (kind === protocol.CommandTypes.CompletionsFull) return completions;

View file

@ -229,6 +229,7 @@ namespace ts.Completions {
triggerCharacter: CompletionsTriggerCharacter | undefined, triggerCharacter: CompletionsTriggerCharacter | undefined,
completionKind: CompletionTriggerKind | undefined, completionKind: CompletionTriggerKind | undefined,
cancellationToken: CancellationToken, cancellationToken: CancellationToken,
formatContext?: formatting.FormatContext,
): CompletionInfo | undefined { ): CompletionInfo | undefined {
const { previousToken } = getRelevantTokens(position, sourceFile); const { previousToken } = getRelevantTokens(position, sourceFile);
if (triggerCharacter && !isInString(sourceFile, position, previousToken) && !isValidTrigger(sourceFile, triggerCharacter, previousToken, position)) { if (triggerCharacter && !isInString(sourceFile, position, previousToken) && !isValidTrigger(sourceFile, triggerCharacter, previousToken, position)) {
@ -275,7 +276,7 @@ namespace ts.Completions {
switch (completionData.kind) { switch (completionData.kind) {
case CompletionDataKind.Data: 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) { if (response?.isIncomplete) {
incompleteCompletionsCache?.set(response); incompleteCompletionsCache?.set(response);
} }
@ -412,6 +413,7 @@ namespace ts.Completions {
log: Log, log: Log,
completionData: CompletionData, completionData: CompletionData,
preferences: UserPreferences, preferences: UserPreferences,
formatContext: formatting.FormatContext | undefined,
): CompletionInfo | undefined { ): CompletionInfo | undefined {
const { const {
symbols, symbols,
@ -459,6 +461,7 @@ namespace ts.Completions {
completionKind, completionKind,
preferences, preferences,
compilerOptions, compilerOptions,
formatContext,
isTypeOnlyLocation, isTypeOnlyLocation,
propertyAccessToConvert, propertyAccessToConvert,
isJsxIdentifierExpected, isJsxIdentifierExpected,
@ -489,6 +492,7 @@ namespace ts.Completions {
completionKind, completionKind,
preferences, preferences,
compilerOptions, compilerOptions,
formatContext,
isTypeOnlyLocation, isTypeOnlyLocation,
propertyAccessToConvert, propertyAccessToConvert,
isJsxIdentifierExpected, isJsxIdentifierExpected,
@ -638,6 +642,7 @@ namespace ts.Completions {
options: CompilerOptions, options: CompilerOptions,
preferences: UserPreferences, preferences: UserPreferences,
completionKind: CompletionKind, completionKind: CompletionKind,
formatContext: formatting.FormatContext | undefined,
): CompletionEntry | undefined { ): CompletionEntry | undefined {
let insertText: string | undefined; let insertText: string | undefined;
let replacementSpan = getReplacementSpanForContextToken(replacementToken); let replacementSpan = getReplacementSpanForContextToken(replacementToken);
@ -706,7 +711,7 @@ namespace ts.Completions {
completionKind === CompletionKind.MemberLike && completionKind === CompletionKind.MemberLike &&
isClassLikeMemberCompletion(symbol, location)) { isClassLikeMemberCompletion(symbol, location)) {
let importAdder; 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()) { if (importAdder?.hasFixes()) {
hasAction = true; hasAction = true;
source = CompletionSource.ClassMemberSnippet; source = CompletionSource.ClassMemberSnippet;
@ -832,6 +837,7 @@ namespace ts.Completions {
symbol: Symbol, symbol: Symbol,
location: Node, location: Node,
contextToken: Node | undefined, contextToken: Node | undefined,
formatContext: formatting.FormatContext | undefined,
): { insertText: string, isSnippet?: true, importAdder?: codefix.ImportAdder } { ): { insertText: string, isSnippet?: true, importAdder?: codefix.ImportAdder } {
const classLikeDeclaration = findAncestor(location, isClassLike); const classLikeDeclaration = findAncestor(location, isClassLike);
if (!classLikeDeclaration) { if (!classLikeDeclaration) {
@ -852,15 +858,16 @@ namespace ts.Completions {
}); });
const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host);
// Create empty body for possible method implementation.
let body; let body;
if (preferences.includeCompletionsWithSnippetText) { if (preferences.includeCompletionsWithSnippetText) {
isSnippet = true; isSnippet = true;
// We are adding a tabstop (i.e. `$0`) in the body of the suggested member, // 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. // 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. // 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("")); const emptyStmt = factory.createEmptyStatement();
setSnippetElement(emptyStatement, { kind: SnippetKind.TabStop, order: 0 }); body = factory.createBlock([emptyStmt], /* multiline */ true);
body = factory.createBlock([emptyStatement], /* multiline */ true); setSnippetElement(emptyStmt, { kind: SnippetKind.TabStop, order: 0 });
} }
else { else {
body = factory.createBlock([], /* multiline */ true); body = factory.createBlock([], /* multiline */ true);
@ -911,7 +918,6 @@ namespace ts.Completions {
modifiers = node.modifierFlagsCache | requiredModifiers | presentModifiers; modifiers = node.modifierFlagsCache | requiredModifiers | presentModifiers;
} }
node = factory.updateModifiers(node, modifiers & (~presentModifiers)); node = factory.updateModifiers(node, modifiers & (~presentModifiers));
completionNodes.push(node); completionNodes.push(node);
}, },
body, body,
@ -919,10 +925,38 @@ namespace ts.Completions {
isAbstract); isAbstract);
if (completionNodes.length) { if (completionNodes.length) {
insertText = printer.printSnippetList( // If we have access to formatting settings, we print the nodes using the emitter,
ListFormat.MultiLine | ListFormat.NoTrailingNewLine, // and then format the printed text.
factory.createNodeArray(completionNodes), if (formatContext) {
sourceFile); 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 }; return { insertText, isSnippet, importAdder };
@ -972,8 +1006,8 @@ namespace ts.Completions {
function createSnippetPrinter( function createSnippetPrinter(
printerOptions: PrinterOptions, printerOptions: PrinterOptions,
) { ) {
const printer = createPrinter(printerOptions); const baseWriter = textChanges.createWriter(getNewLineCharacter(printerOptions));
const baseWriter = createTextWriter(getNewLineCharacter(printerOptions)); const printer = createPrinter(printerOptions, baseWriter);
const writer: EmitTextWriter = { const writer: EmitTextWriter = {
...baseWriter, ...baseWriter,
write: s => baseWriter.write(escapeSnippetText(s)), write: s => baseWriter.write(escapeSnippetText(s)),
@ -1117,6 +1151,7 @@ namespace ts.Completions {
kind: CompletionKind, kind: CompletionKind,
preferences: UserPreferences, preferences: UserPreferences,
compilerOptions: CompilerOptions, compilerOptions: CompilerOptions,
formatContext: formatting.FormatContext | undefined,
isTypeOnlyLocation?: boolean, isTypeOnlyLocation?: boolean,
propertyAccessToConvert?: PropertyAccessExpression, propertyAccessToConvert?: PropertyAccessExpression,
jsxIdentifierExpected?: boolean, jsxIdentifierExpected?: boolean,
@ -1166,6 +1201,7 @@ namespace ts.Completions {
compilerOptions, compilerOptions,
preferences, preferences,
kind, kind,
formatContext,
); );
if (!entry) { if (!entry) {
continue; continue;
@ -1444,7 +1480,8 @@ namespace ts.Completions {
name, name,
symbol, symbol,
location, location,
contextToken); contextToken,
formatContext);
if (importAdder) { if (importAdder) {
const changes = textChanges.ChangeTracker.with( const changes = textChanges.ChangeTracker.with(
{ host, formatContext, preferences }, { host, formatContext, preferences },

View file

@ -1593,7 +1593,7 @@ namespace ts {
return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)]; 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 // Convert from deprecated options names to new names
const fullPreferences: UserPreferences = { const fullPreferences: UserPreferences = {
...identity<UserPreferences>(options), // avoid excess property check ...identity<UserPreferences>(options), // avoid excess property check
@ -1610,7 +1610,8 @@ namespace ts {
fullPreferences, fullPreferences,
options.triggerCharacter, options.triggerCharacter,
options.triggerKind, 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 { function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions, data?: CompletionEntryData): CompletionEntryDetails | undefined {

View file

@ -151,7 +151,7 @@ namespace ts {
getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string; getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string;
getEncodedSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): 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; 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; getQuickInfoAtPosition(fileName: string, position: number): string;
@ -956,10 +956,10 @@ namespace ts {
* to provide at the given source position and providing a member completion * to provide at the given source position and providing a member completion
* list if requested. * 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( return this.forwardJSONCall(
`getCompletionsAtPosition('${fileName}', ${position}, ${preferences})`, `getCompletionsAtPosition('${fileName}', ${position}, ${preferences}, ${formattingSettings})`,
() => this.languageService.getCompletionsAtPosition(fileName, position, preferences) () => this.languageService.getCompletionsAtPosition(fileName, position, preferences, formattingSettings)
); );
} }

View file

@ -54,6 +54,7 @@ namespace ts.Completions.StringCompletions {
CompletionKind.String, CompletionKind.String,
preferences, preferences,
options, options,
/*formatContext*/ undefined,
); // Target will not be used, so arbitrary ); // Target will not be used, so arbitrary
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, optionalReplacementSpan, entries }; return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, optionalReplacementSpan, entries };
} }

View file

@ -1052,15 +1052,6 @@ namespace ts.textChanges {
? "" : options.suffix); ? "" : 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`. */ /** 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 { 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); const { node, text } = getNonformattedText(nodeIn, sourceFile, newLineCharacter);
@ -1110,7 +1101,7 @@ namespace ts.textChanges {
return skipTrivia(s, 0) === s.length; 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); const visited = visitEachChild(node, assignPositionsToNode, nullTransformationContext, assignPositionsToNodeArray, assignPositionsToNode);
// create proxy node for non synthesized nodes // create proxy node for non synthesized nodes
const newNode = nodeIsSynthesized(visited) ? visited : Object.create(visited) as Node; const newNode = nodeIsSynthesized(visited) ? visited : Object.create(visited) as Node;
@ -1131,7 +1122,7 @@ namespace ts.textChanges {
interface TextChangesWriter extends EmitTextWriter, PrintHandlers {} interface TextChangesWriter extends EmitTextWriter, PrintHandlers {}
function createWriter(newLine: string): TextChangesWriter { export function createWriter(newLine: string): TextChangesWriter {
let lastNonTriviaPosition = 0; let lastNonTriviaPosition = 0;
const writer = createTextWriter(newLine); const writer = createTextWriter(newLine);

View file

@ -414,8 +414,9 @@ namespace ts {
* @param position A zero-based index of the character where you want the entries * @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 * @param options An object describing how the request was triggered and what kinds
* of code actions can be returned with the completions. * 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<CompletionInfo> | undefined; getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined, formattingSettings?: FormatCodeSettings): WithMetadata<CompletionInfo> | undefined;
/** /**
* Gets the extended details for a completion entry retrieved from `getCompletionsAtPosition`. * Gets the extended details for a completion entry retrieved from `getCompletionsAtPosition`.

View file

@ -3290,5 +3290,17 @@ namespace ts {
: getLocaleSpecificMessage(diag); : 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 // #endregion
} }