Skip to content

Commit

Permalink
Support quote style in importFixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy Hanson committed Mar 12, 2018
1 parent 85b8abc commit b218afe
Show file tree
Hide file tree
Showing 13 changed files with 91 additions and 52 deletions.
19 changes: 10 additions & 9 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1223,8 +1223,8 @@ Actual: ${stringify(fullActual)}`);
return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition, options);
}

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

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

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

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

Expand All @@ -2550,15 +2550,15 @@ Actual: ${stringify(fullActual)}`);
}
}

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

const codeFixes = this.getCodeFixes(fileName, errorCode);
const codeFixes = this.getCodeFixes(fileName, errorCode, options);

if (codeFixes.length === 0) {
if (expectedTextArray.length !== 0) {
Expand Down Expand Up @@ -4215,8 +4215,8 @@ namespace FourSlashInterface {
this.state.applyCodeActionFromCompletion(markerName, options);
}

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

public navigationBar(json: any, options?: { checkSpans?: boolean }) {
Expand Down Expand Up @@ -4663,5 +4663,6 @@ namespace FourSlashInterface {
name: string;
source?: string;
description: string;
options?: ts.Options;
}
}
4 changes: 2 additions & 2 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,8 @@ namespace Harness.LanguageService {
getCompletionsAtPosition(fileName: string, position: number, options: ts.Options | undefined): ts.CompletionInfo {
return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, options));
}
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: ts.FormatCodeOptions | undefined, source: string | undefined): ts.CompletionEntryDetails {
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(options), source));
getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | undefined, source: string | undefined, options: ts.Options | undefined): ts.CompletionEntryDetails {
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, options));
}
getCompletionEntrySymbol(): ts.Symbol {
throw new Error("getCompletionEntrySymbol not implemented across the shim layer.");
Expand Down
2 changes: 1 addition & 1 deletion src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1266,7 +1266,7 @@ namespace ts.server {

const result = mapDefined(args.entryNames, entryName => {
const { name, source } = typeof entryName === "string" ? { name: entryName, source: undefined } : entryName;
return project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source);
return project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source, this.getOptions(file));
});
return simplifiedResult
? result.map(details => ({ ...details, codeActions: map(details.codeActions, action => this.mapCodeAction(project, action)) }))
Expand Down
24 changes: 17 additions & 7 deletions src/services/codefixes/importFixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ namespace ts.codefix {
compilerOptions: CompilerOptions;
getCanonicalFileName: GetCanonicalFileName;
cachedImportDeclarations?: ImportDeclarationMap;
options: Options;
}

function createCodeAction(descriptionDiagnostic: DiagnosticMessage, diagnosticArgs: string[], changes: FileTextChanges[]): CodeFixAction {
Expand All @@ -53,7 +54,8 @@ namespace ts.codefix {
cachedImportDeclarations: [],
getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames),
symbolName,
symbolToken
symbolToken,
options: context.options,
};
}

Expand Down Expand Up @@ -95,12 +97,13 @@ namespace ts.codefix {
formatContext: ts.formatting.FormatContext,
getCanonicalFileName: GetCanonicalFileName,
symbolToken: Node | undefined,
options: Options,
): { readonly moduleSpecifier: string, readonly codeAction: CodeAction } {
const exportInfos = getAllReExportingModules(exportedSymbol, checker, allSourceFiles);
Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol));
// We sort the best codefixes first, so taking `first` is best for completions.
const moduleSpecifier = first(getNewImportInfos(program, sourceFile, exportInfos, compilerOptions, getCanonicalFileName, host)).moduleSpecifier;
const ctx: ImportCodeFixContext = { host, program, checker, compilerOptions, sourceFile, formatContext, symbolName, getCanonicalFileName, symbolToken };
const ctx: ImportCodeFixContext = { host, program, checker, compilerOptions, sourceFile, formatContext, symbolName, getCanonicalFileName, symbolToken, options };
return { moduleSpecifier, codeAction: first(getCodeActionsForImport(exportInfos, ctx)) };
}
function getAllReExportingModules(exportedSymbol: Symbol, checker: TypeChecker, allSourceFiles: ReadonlyArray<SourceFile>): ReadonlyArray<SymbolExportInfo> {
Expand Down Expand Up @@ -181,12 +184,12 @@ namespace ts.codefix {
}
}

function getCodeActionForNewImport(context: SymbolContext, { moduleSpecifier, importKind }: NewImportInfo): CodeFixAction {
function getCodeActionForNewImport(context: SymbolContext & { options: Options }, { moduleSpecifier, importKind }: NewImportInfo): CodeFixAction {
const { sourceFile, symbolName } = context;
const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax);

const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
const quotedModuleSpecifier = createStringLiteralWithQuoteStyle(sourceFile, moduleSpecifierWithoutQuotes);
const quotedModuleSpecifier = createStringLiteralWithQuoteStyle(sourceFile, moduleSpecifierWithoutQuotes, context.options);
const importDecl = importKind !== ImportKind.Equals
? createImportDeclaration(
/*decorators*/ undefined,
Expand Down Expand Up @@ -214,12 +217,19 @@ namespace ts.codefix {
return createCodeAction(Diagnostics.Import_0_from_module_1, [symbolName, moduleSpecifierWithoutQuotes], changes);
}

function createStringLiteralWithQuoteStyle(sourceFile: SourceFile, text: string): StringLiteral {
function createStringLiteralWithQuoteStyle(sourceFile: SourceFile, text: string, options: Options): StringLiteral {
const literal = createLiteral(text);
const firstModuleSpecifier = firstOrUndefined(sourceFile.imports);
literal.singleQuote = !!firstModuleSpecifier && !isStringDoubleQuoted(firstModuleSpecifier, sourceFile);
literal.singleQuote = shouldUseSingleQuote(sourceFile, options);
return literal;
}
function shouldUseSingleQuote(sourceFile: SourceFile, options: Options): boolean {
if (options.quote) {
return options.quote === "single";
} else {
const firstModuleSpecifier = firstOrUndefined(sourceFile.imports);
return !!firstModuleSpecifier && !isStringDoubleQuoted(firstModuleSpecifier, sourceFile);
}
}

function usesJsExtensionOnImports(sourceFile: SourceFile): boolean {
return firstDefined(sourceFile.imports, ({ text }) => pathIsRelative(text) ? fileExtensionIs(text, Extension.Js) : undefined) || false;
Expand Down
27 changes: 8 additions & 19 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ namespace ts.Completions {
host: LanguageServiceHost,
formatContext: formatting.FormatContext,
getCanonicalFileName: GetCanonicalFileName,
options: Options,
): CompletionEntryDetails {
const typeChecker = program.getTypeChecker();
const { name } = entryId;
Expand All @@ -545,7 +546,7 @@ namespace ts.Completions {
}
case "symbol": {
const { symbol, location, symbolToOriginInfoMap, previousToken } = symbolCompletion;
const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles);
const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles, options);
const kindModifiers = SymbolDisplay.getSymbolModifiers(symbol);
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions, source: sourceDisplay };
Expand Down Expand Up @@ -585,26 +586,13 @@ namespace ts.Completions {
formatContext: formatting.FormatContext,
getCanonicalFileName: GetCanonicalFileName,
allSourceFiles: ReadonlyArray<SourceFile>,
options: Options,
): CodeActionsAndSourceDisplay {
const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)];
return symbolOriginInfo && symbolOriginInfo.type === "export"
? getCodeActionsAndSourceDisplayForImport(symbolOriginInfo, symbol, program, checker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles)
: { codeActions: undefined, sourceDisplay: undefined };
}
if (!symbolOriginInfo || symbolOriginInfo.type !== "export") {
return { codeActions: undefined, sourceDisplay: undefined };
}

function getCodeActionsAndSourceDisplayForImport(
symbolOriginInfo: SymbolOriginInfoExport,
symbol: Symbol,
program: Program,
checker: TypeChecker,
host: LanguageServiceHost,
compilerOptions: CompilerOptions,
sourceFile: SourceFile,
previousToken: Node,
formatContext: formatting.FormatContext,
getCanonicalFileName: GetCanonicalFileName,
allSourceFiles: ReadonlyArray<SourceFile>
): CodeActionsAndSourceDisplay {
const { moduleSymbol } = symbolOriginInfo;
const exportedSymbol = skipAlias(symbol.exportSymbol || symbol, checker);
const { moduleSpecifier, codeAction } = codefix.getImportCompletionAction(
Expand All @@ -619,7 +607,8 @@ namespace ts.Completions {
allSourceFiles,
formatContext,
getCanonicalFileName,
previousToken);
previousToken,
options);
return { sourceDisplay: [textPart(moduleSpecifier)], codeActions: [codeAction] };
}

Expand Down
5 changes: 3 additions & 2 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1443,7 +1443,7 @@ namespace ts {
fullOptions);
}

function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions?: FormatCodeSettings, source?: string): CompletionEntryDetails {
function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, options: Options = defaultOptions): CompletionEntryDetails {
synchronizeHostData();
return Completions.getCompletionEntryDetails(
program,
Expand All @@ -1455,7 +1455,8 @@ namespace ts {
program.getSourceFiles(),
host,
formattingOptions && formatting.getFormatContext(formattingOptions),
getCanonicalFileName);
getCanonicalFileName,
options);
}

function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol {
Expand Down
16 changes: 8 additions & 8 deletions src/services/shims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ namespace ts {
getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string;
getEncodedSemanticClassifications(fileName: string, start: number, length: number): string;

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

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

Expand Down Expand Up @@ -909,20 +909,20 @@ namespace ts {
* to provide at the given source position and providing a member completion
* list if requested.
*/
public getCompletionsAtPosition(fileName: string, position: number, settings: Options | undefined) {
public getCompletionsAtPosition(fileName: string, position: number, options: Options | undefined) {
return this.forwardJSONCall(
`getCompletionsAtPosition('${fileName}', ${position}, ${settings})`,
() => this.languageService.getCompletionsAtPosition(fileName, position, settings)
`getCompletionsAtPosition('${fileName}', ${position}, ${options})`,
() => this.languageService.getCompletionsAtPosition(fileName, position, options)
);
}

/** Get a string based representation of a completion list entry details */
public getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined) {
public getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, options: Options | undefined) {
return this.forwardJSONCall(
`getCompletionEntryDetails('${fileName}', ${position}, '${entryName}')`,
() => {
const localOptions: ts.FormatCodeOptions = options === undefined ? undefined : JSON.parse(options);
return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source);
const localOptions: ts.FormatCodeOptions = formatOptions === undefined ? undefined : JSON.parse(formatOptions);
return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source, options);
}
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,9 @@ namespace ts {
fileName: string,
position: number,
name: string,
options: FormatCodeOptions | FormatCodeSettings | undefined,
formatOptions: FormatCodeOptions | FormatCodeSettings | undefined,
source: string | undefined,
options: Options | undefined,
): CompletionEntryDetails;
getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): Symbol;

Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4071,7 +4071,7 @@ declare namespace ts {
getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications;
getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications;
getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined): CompletionInfo;
getCompletionEntryDetails(fileName: string, position: number, name: string, options: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined): CompletionEntryDetails;
getCompletionEntryDetails(fileName: string, position: number, name: string, formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined, options: Options | undefined): CompletionEntryDetails;
getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): Symbol;
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan;
Expand Down
Loading

0 comments on commit b218afe

Please sign in to comment.