Skip to content

Commit c0e2d52

Browse files
author
Andy Hanson
committed
Support quote style in importFixes
1 parent 85b8abc commit c0e2d52

13 files changed

+92
-52
lines changed

src/harness/fourslash.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -1223,8 +1223,8 @@ Actual: ${stringify(fullActual)}`);
12231223
return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition, options);
12241224
}
12251225

1226-
private getCompletionEntryDetails(entryName: string, source?: string): ts.CompletionEntryDetails {
1227-
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings, source);
1226+
private getCompletionEntryDetails(entryName: string, source?: string, options?: ts.Options): ts.CompletionEntryDetails {
1227+
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings, source, options);
12281228
}
12291229

12301230
private getReferencesAtCaret() {
@@ -2399,7 +2399,7 @@ Actual: ${stringify(fullActual)}`);
23992399
this.raiseError(`Completion for ${options.name} does not have an associated action.`);
24002400
}
24012401

2402-
const details = this.getCompletionEntryDetails(options.name, actualCompletion.source);
2402+
const details = this.getCompletionEntryDetails(options.name, actualCompletion.source, options.options);
24032403
if (details.codeActions.length !== 1) {
24042404
this.raiseError(`Expected one code action, got ${details.codeActions.length}`);
24052405
}
@@ -2512,7 +2512,7 @@ Actual: ${stringify(fullActual)}`);
25122512
* Rerieves a codefix satisfying the parameters, or undefined if no such codefix is found.
25132513
* @param fileName Path to file where error should be retrieved from.
25142514
*/
2515-
private getCodeFixes(fileName: string, errorCode?: number): ts.CodeFixAction[] {
2515+
private getCodeFixes(fileName: string, errorCode?: number, options: ts.Options = ts.defaultOptions): ts.CodeFixAction[] {
25162516
const diagnosticsForCodeFix = this.getDiagnostics(fileName).map(diagnostic => ({
25172517
start: diagnostic.start,
25182518
length: diagnostic.length,
@@ -2524,7 +2524,7 @@ Actual: ${stringify(fullActual)}`);
25242524
return;
25252525
}
25262526

2527-
return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.start + diagnostic.length, [diagnostic.code], this.formatCodeSettings, ts.defaultOptions);
2527+
return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.start + diagnostic.length, [diagnostic.code], this.formatCodeSettings, options);
25282528
});
25292529
}
25302530

@@ -2550,15 +2550,15 @@ Actual: ${stringify(fullActual)}`);
25502550
}
25512551
}
25522552

2553-
public verifyImportFixAtPosition(expectedTextArray: string[], errorCode?: number) {
2553+
public verifyImportFixAtPosition(expectedTextArray: string[], errorCode: number | undefined, options: ts.Options | undefined) {
25542554
const { fileName } = this.activeFile;
25552555
const ranges = this.getRanges().filter(r => r.fileName === fileName);
25562556
if (ranges.length !== 1) {
25572557
this.raiseError("Exactly one range should be specified in the testfile.");
25582558
}
25592559
const range = ts.first(ranges);
25602560

2561-
const codeFixes = this.getCodeFixes(fileName, errorCode);
2561+
const codeFixes = this.getCodeFixes(fileName, errorCode, options);
25622562

25632563
if (codeFixes.length === 0) {
25642564
if (expectedTextArray.length !== 0) {
@@ -4215,8 +4215,8 @@ namespace FourSlashInterface {
42154215
this.state.applyCodeActionFromCompletion(markerName, options);
42164216
}
42174217

4218-
public importFixAtPosition(expectedTextArray: string[], errorCode?: number): void {
4219-
this.state.verifyImportFixAtPosition(expectedTextArray, errorCode);
4218+
public importFixAtPosition(expectedTextArray: string[], errorCode?: number, options?: ts.Options): void {
4219+
this.state.verifyImportFixAtPosition(expectedTextArray, errorCode, options);
42204220
}
42214221

42224222
public navigationBar(json: any, options?: { checkSpans?: boolean }) {
@@ -4663,5 +4663,6 @@ namespace FourSlashInterface {
46634663
name: string;
46644664
source?: string;
46654665
description: string;
4666+
options?: ts.Options;
46664667
}
46674668
}

src/harness/harnessLanguageService.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,8 @@ namespace Harness.LanguageService {
420420
getCompletionsAtPosition(fileName: string, position: number, options: ts.Options | undefined): ts.CompletionInfo {
421421
return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, options));
422422
}
423-
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: ts.FormatCodeOptions | undefined, source: string | undefined): ts.CompletionEntryDetails {
424-
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(options), source));
423+
getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | undefined, source: string | undefined, options: ts.Options | undefined): ts.CompletionEntryDetails {
424+
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, options));
425425
}
426426
getCompletionEntrySymbol(): ts.Symbol {
427427
throw new Error("getCompletionEntrySymbol not implemented across the shim layer.");

src/server/session.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1266,7 +1266,7 @@ namespace ts.server {
12661266

12671267
const result = mapDefined(args.entryNames, entryName => {
12681268
const { name, source } = typeof entryName === "string" ? { name: entryName, source: undefined } : entryName;
1269-
return project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source);
1269+
return project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source, this.getOptions(file));
12701270
});
12711271
return simplifiedResult
12721272
? result.map(details => ({ ...details, codeActions: map(details.codeActions, action => this.mapCodeAction(project, action)) }))

src/services/codefixes/importFixes.ts

+18-7
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ namespace ts.codefix {
3030
compilerOptions: CompilerOptions;
3131
getCanonicalFileName: GetCanonicalFileName;
3232
cachedImportDeclarations?: ImportDeclarationMap;
33+
options: Options;
3334
}
3435

3536
function createCodeAction(descriptionDiagnostic: DiagnosticMessage, diagnosticArgs: string[], changes: FileTextChanges[]): CodeFixAction {
@@ -53,7 +54,8 @@ namespace ts.codefix {
5354
cachedImportDeclarations: [],
5455
getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames),
5556
symbolName,
56-
symbolToken
57+
symbolToken,
58+
options: context.options,
5759
};
5860
}
5961

@@ -95,12 +97,13 @@ namespace ts.codefix {
9597
formatContext: ts.formatting.FormatContext,
9698
getCanonicalFileName: GetCanonicalFileName,
9799
symbolToken: Node | undefined,
100+
options: Options,
98101
): { readonly moduleSpecifier: string, readonly codeAction: CodeAction } {
99102
const exportInfos = getAllReExportingModules(exportedSymbol, checker, allSourceFiles);
100103
Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol));
101104
// We sort the best codefixes first, so taking `first` is best for completions.
102105
const moduleSpecifier = first(getNewImportInfos(program, sourceFile, exportInfos, compilerOptions, getCanonicalFileName, host)).moduleSpecifier;
103-
const ctx: ImportCodeFixContext = { host, program, checker, compilerOptions, sourceFile, formatContext, symbolName, getCanonicalFileName, symbolToken };
106+
const ctx: ImportCodeFixContext = { host, program, checker, compilerOptions, sourceFile, formatContext, symbolName, getCanonicalFileName, symbolToken, options };
104107
return { moduleSpecifier, codeAction: first(getCodeActionsForImport(exportInfos, ctx)) };
105108
}
106109
function getAllReExportingModules(exportedSymbol: Symbol, checker: TypeChecker, allSourceFiles: ReadonlyArray<SourceFile>): ReadonlyArray<SymbolExportInfo> {
@@ -181,12 +184,12 @@ namespace ts.codefix {
181184
}
182185
}
183186

184-
function getCodeActionForNewImport(context: SymbolContext, { moduleSpecifier, importKind }: NewImportInfo): CodeFixAction {
187+
function getCodeActionForNewImport(context: SymbolContext & { options: Options }, { moduleSpecifier, importKind }: NewImportInfo): CodeFixAction {
185188
const { sourceFile, symbolName } = context;
186189
const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax);
187190

188191
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
189-
const quotedModuleSpecifier = createStringLiteralWithQuoteStyle(sourceFile, moduleSpecifierWithoutQuotes);
192+
const quotedModuleSpecifier = createStringLiteralWithQuoteStyle(sourceFile, moduleSpecifierWithoutQuotes, context.options);
190193
const importDecl = importKind !== ImportKind.Equals
191194
? createImportDeclaration(
192195
/*decorators*/ undefined,
@@ -214,12 +217,20 @@ namespace ts.codefix {
214217
return createCodeAction(Diagnostics.Import_0_from_module_1, [symbolName, moduleSpecifierWithoutQuotes], changes);
215218
}
216219

217-
function createStringLiteralWithQuoteStyle(sourceFile: SourceFile, text: string): StringLiteral {
220+
function createStringLiteralWithQuoteStyle(sourceFile: SourceFile, text: string, options: Options): StringLiteral {
218221
const literal = createLiteral(text);
219-
const firstModuleSpecifier = firstOrUndefined(sourceFile.imports);
220-
literal.singleQuote = !!firstModuleSpecifier && !isStringDoubleQuoted(firstModuleSpecifier, sourceFile);
222+
literal.singleQuote = shouldUseSingleQuote(sourceFile, options);
221223
return literal;
222224
}
225+
function shouldUseSingleQuote(sourceFile: SourceFile, options: Options): boolean {
226+
if (options.quote) {
227+
return options.quote === "single";
228+
}
229+
else {
230+
const firstModuleSpecifier = firstOrUndefined(sourceFile.imports);
231+
return !!firstModuleSpecifier && !isStringDoubleQuoted(firstModuleSpecifier, sourceFile);
232+
}
233+
}
223234

224235
function usesJsExtensionOnImports(sourceFile: SourceFile): boolean {
225236
return firstDefined(sourceFile.imports, ({ text }) => pathIsRelative(text) ? fileExtensionIs(text, Extension.Js) : undefined) || false;

src/services/completions.ts

+8-19
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ namespace ts.Completions {
524524
host: LanguageServiceHost,
525525
formatContext: formatting.FormatContext,
526526
getCanonicalFileName: GetCanonicalFileName,
527+
options: Options,
527528
): CompletionEntryDetails {
528529
const typeChecker = program.getTypeChecker();
529530
const { name } = entryId;
@@ -545,7 +546,7 @@ namespace ts.Completions {
545546
}
546547
case "symbol": {
547548
const { symbol, location, symbolToOriginInfoMap, previousToken } = symbolCompletion;
548-
const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles);
549+
const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles, options);
549550
const kindModifiers = SymbolDisplay.getSymbolModifiers(symbol);
550551
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
551552
return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions, source: sourceDisplay };
@@ -585,26 +586,13 @@ namespace ts.Completions {
585586
formatContext: formatting.FormatContext,
586587
getCanonicalFileName: GetCanonicalFileName,
587588
allSourceFiles: ReadonlyArray<SourceFile>,
589+
options: Options,
588590
): CodeActionsAndSourceDisplay {
589591
const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)];
590-
return symbolOriginInfo && symbolOriginInfo.type === "export"
591-
? getCodeActionsAndSourceDisplayForImport(symbolOriginInfo, symbol, program, checker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles)
592-
: { codeActions: undefined, sourceDisplay: undefined };
593-
}
592+
if (!symbolOriginInfo || symbolOriginInfo.type !== "export") {
593+
return { codeActions: undefined, sourceDisplay: undefined };
594+
}
594595

595-
function getCodeActionsAndSourceDisplayForImport(
596-
symbolOriginInfo: SymbolOriginInfoExport,
597-
symbol: Symbol,
598-
program: Program,
599-
checker: TypeChecker,
600-
host: LanguageServiceHost,
601-
compilerOptions: CompilerOptions,
602-
sourceFile: SourceFile,
603-
previousToken: Node,
604-
formatContext: formatting.FormatContext,
605-
getCanonicalFileName: GetCanonicalFileName,
606-
allSourceFiles: ReadonlyArray<SourceFile>
607-
): CodeActionsAndSourceDisplay {
608596
const { moduleSymbol } = symbolOriginInfo;
609597
const exportedSymbol = skipAlias(symbol.exportSymbol || symbol, checker);
610598
const { moduleSpecifier, codeAction } = codefix.getImportCompletionAction(
@@ -619,7 +607,8 @@ namespace ts.Completions {
619607
allSourceFiles,
620608
formatContext,
621609
getCanonicalFileName,
622-
previousToken);
610+
previousToken,
611+
options);
623612
return { sourceDisplay: [textPart(moduleSpecifier)], codeActions: [codeAction] };
624613
}
625614

src/services/services.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1443,7 +1443,7 @@ namespace ts {
14431443
fullOptions);
14441444
}
14451445

1446-
function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions?: FormatCodeSettings, source?: string): CompletionEntryDetails {
1446+
function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, options: Options = defaultOptions): CompletionEntryDetails {
14471447
synchronizeHostData();
14481448
return Completions.getCompletionEntryDetails(
14491449
program,
@@ -1455,7 +1455,8 @@ namespace ts {
14551455
program.getSourceFiles(),
14561456
host,
14571457
formattingOptions && formatting.getFormatContext(formattingOptions),
1458-
getCanonicalFileName);
1458+
getCanonicalFileName,
1459+
options);
14591460
}
14601461

14611462
function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol {

src/services/shims.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ namespace ts {
151151
getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string;
152152
getEncodedSemanticClassifications(fileName: string, start: number, length: number): string;
153153

154-
getCompletionsAtPosition(fileName: string, position: number, settings: Options | undefined): string;
155-
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined): string;
154+
getCompletionsAtPosition(fileName: string, position: number, options: Options | undefined): string;
155+
getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, options: Options | undefined): string;
156156

157157
getQuickInfoAtPosition(fileName: string, position: number): string;
158158

@@ -909,20 +909,20 @@ namespace ts {
909909
* to provide at the given source position and providing a member completion
910910
* list if requested.
911911
*/
912-
public getCompletionsAtPosition(fileName: string, position: number, settings: Options | undefined) {
912+
public getCompletionsAtPosition(fileName: string, position: number, options: Options | undefined) {
913913
return this.forwardJSONCall(
914-
`getCompletionsAtPosition('${fileName}', ${position}, ${settings})`,
915-
() => this.languageService.getCompletionsAtPosition(fileName, position, settings)
914+
`getCompletionsAtPosition('${fileName}', ${position}, ${options})`,
915+
() => this.languageService.getCompletionsAtPosition(fileName, position, options)
916916
);
917917
}
918918

919919
/** Get a string based representation of a completion list entry details */
920-
public getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined) {
920+
public getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, options: Options | undefined) {
921921
return this.forwardJSONCall(
922922
`getCompletionEntryDetails('${fileName}', ${position}, '${entryName}')`,
923923
() => {
924-
const localOptions: ts.FormatCodeOptions = options === undefined ? undefined : JSON.parse(options);
925-
return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source);
924+
const localOptions: ts.FormatCodeOptions = formatOptions === undefined ? undefined : JSON.parse(formatOptions);
925+
return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source, options);
926926
}
927927
);
928928
}

src/services/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,9 @@ namespace ts {
256256
fileName: string,
257257
position: number,
258258
name: string,
259-
options: FormatCodeOptions | FormatCodeSettings | undefined,
259+
formatOptions: FormatCodeOptions | FormatCodeSettings | undefined,
260260
source: string | undefined,
261+
options: Options | undefined,
261262
): CompletionEntryDetails;
262263
getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): Symbol;
263264

tests/baselines/reference/api/tsserverlibrary.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4071,7 +4071,7 @@ declare namespace ts {
40714071
getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications;
40724072
getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications;
40734073
getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): CompletionInfo;
4074-
getCompletionEntryDetails(fileName: string, position: number, name: string, options: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined): CompletionEntryDetails;
4074+
getCompletionEntryDetails(fileName: string, position: number, name: string, formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined, options: Options | undefined): CompletionEntryDetails;
40754075
getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): Symbol;
40764076
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
40774077
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan;

0 commit comments

Comments
 (0)