Skip to content

Commit cc39213

Browse files
author
Andy
authored
Support services settings (#22236)
* Support services settings * Code review * More review * Use different names for Options and GetCompletionsAtPositionOptions (todo: come up with better names) * More renames * More renaming * Support quote style in importFixes * Add `importModuleSpecifierPreference` option * Support quote style for `throw new Error('Method not implemented.')` (#18169) * options -> preferences
1 parent 9ee5167 commit cc39213

33 files changed

+485
-251
lines changed

Diff for: src/compiler/factory.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -70,20 +70,23 @@ namespace ts {
7070

7171
// Literals
7272

73+
/* @internal */ export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote: boolean): StringLiteral; // tslint:disable-line unified-signatures
7374
/** If a node is passed, creates a string literal whose source text is read from a source node during emit. */
7475
export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): StringLiteral;
7576
export function createLiteral(value: number): NumericLiteral;
7677
export function createLiteral(value: boolean): BooleanLiteral;
7778
export function createLiteral(value: string | number | boolean): PrimaryExpression;
78-
export function createLiteral(value: string | number | boolean | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): PrimaryExpression {
79+
export function createLiteral(value: string | number | boolean | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote?: boolean): PrimaryExpression {
7980
if (typeof value === "number") {
8081
return createNumericLiteral(value + "");
8182
}
8283
if (typeof value === "boolean") {
8384
return value ? createTrue() : createFalse();
8485
}
8586
if (isString(value)) {
86-
return createStringLiteral(value);
87+
const res = createStringLiteral(value);
88+
if (isSingleQuote) res.singleQuote = true;
89+
return res;
8790
}
8891
return createLiteralFromNode(value);
8992
}

Diff for: src/harness/fourslash.ts

+28-27
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,6 @@ namespace FourSlash {
365365

366366
function memoWrap(ls: ts.LanguageService, target: TestState): ts.LanguageService {
367367
const cacheableMembers: (keyof typeof ls)[] = [
368-
"getCompletionsAtPosition",
369368
"getCompletionEntryDetails",
370369
"getCompletionEntrySymbol",
371370
"getQuickInfoAtPosition",
@@ -1228,8 +1227,8 @@ Actual: ${stringify(fullActual)}`);
12281227
return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition, options);
12291228
}
12301229

1231-
private getCompletionEntryDetails(entryName: string, source?: string): ts.CompletionEntryDetails {
1232-
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings, source);
1230+
private getCompletionEntryDetails(entryName: string, source?: string, preferences?: ts.UserPreferences): ts.CompletionEntryDetails {
1231+
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings, source, preferences);
12331232
}
12341233

12351234
private getReferencesAtCaret() {
@@ -1728,8 +1727,8 @@ Actual: ${stringify(fullActual)}`);
17281727
Harness.IO.log(stringify(sigHelp));
17291728
}
17301729

1731-
public printCompletionListMembers(options: ts.GetCompletionsAtPositionOptions | undefined) {
1732-
const completions = this.getCompletionListAtCaret(options);
1730+
public printCompletionListMembers(preferences: ts.UserPreferences | undefined) {
1731+
const completions = this.getCompletionListAtCaret(preferences);
17331732
this.printMembersOrCompletions(completions);
17341733
}
17351734

@@ -1827,7 +1826,7 @@ Actual: ${stringify(fullActual)}`);
18271826
}
18281827
else if (prevChar === " " && /A-Za-z_/.test(ch)) {
18291828
/* Completions */
1830-
this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
1829+
this.languageService.getCompletionsAtPosition(this.activeFile.fileName, offset, ts.defaultPreferences);
18311830
}
18321831

18331832
if (i % checkCadence === 0) {
@@ -2402,14 +2401,14 @@ Actual: ${stringify(fullActual)}`);
24022401
public applyCodeActionFromCompletion(markerName: string, options: FourSlashInterface.VerifyCompletionActionOptions) {
24032402
this.goToMarker(markerName);
24042403

2405-
const actualCompletion = this.getCompletionListAtCaret({ includeExternalModuleExports: true, includeInsertTextCompletions: false }).entries.find(e =>
2404+
const actualCompletion = this.getCompletionListAtCaret({ ...ts.defaultPreferences, includeCompletionsForModuleExports: true }).entries.find(e =>
24062405
e.name === options.name && e.source === options.source);
24072406

24082407
if (!actualCompletion.hasAction) {
24092408
this.raiseError(`Completion for ${options.name} does not have an associated action.`);
24102409
}
24112410

2412-
const details = this.getCompletionEntryDetails(options.name, actualCompletion.source);
2411+
const details = this.getCompletionEntryDetails(options.name, actualCompletion.source, options.preferences);
24132412
if (details.codeActions.length !== 1) {
24142413
this.raiseError(`Expected one code action, got ${details.codeActions.length}`);
24152414
}
@@ -2454,7 +2453,7 @@ Actual: ${stringify(fullActual)}`);
24542453
const { fixId, newFileContent } = options;
24552454
const fixIds = ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => a.fixId);
24562455
ts.Debug.assert(ts.contains(fixIds, fixId), "No available code fix has that group id.", () => `Expected '${fixId}'. Available action ids: ${fixIds}`);
2457-
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings);
2456+
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings, ts.defaultPreferences);
24582457
assert.deepEqual(commands, options.commands);
24592458
assert(changes.every(c => c.fileName === this.activeFile.fileName), "TODO: support testing codefixes that touch multiple files");
24602459
this.applyChanges(changes);
@@ -2483,7 +2482,7 @@ Actual: ${stringify(fullActual)}`);
24832482

24842483
public verifyCodeFix(options: FourSlashInterface.VerifyCodeFixOptions) {
24852484
const fileName = this.activeFile.fileName;
2486-
const actions = this.getCodeFixes(fileName, options.errorCode);
2485+
const actions = this.getCodeFixes(fileName, options.errorCode, options.preferences);
24872486
let index = options.index;
24882487
if (index === undefined) {
24892488
if (!(actions && actions.length === 1)) {
@@ -2522,7 +2521,7 @@ Actual: ${stringify(fullActual)}`);
25222521
* Rerieves a codefix satisfying the parameters, or undefined if no such codefix is found.
25232522
* @param fileName Path to file where error should be retrieved from.
25242523
*/
2525-
private getCodeFixes(fileName: string, errorCode?: number): ts.CodeFixAction[] {
2524+
private getCodeFixes(fileName: string, errorCode?: number, preferences: ts.UserPreferences = ts.defaultPreferences): ts.CodeFixAction[] {
25262525
const diagnosticsForCodeFix = this.getDiagnostics(fileName, /*includeSuggestions*/ true).map(diagnostic => ({
25272526
start: diagnostic.start,
25282527
length: diagnostic.length,
@@ -2534,7 +2533,7 @@ Actual: ${stringify(fullActual)}`);
25342533
return;
25352534
}
25362535

2537-
return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.start + diagnostic.length, [diagnostic.code], this.formatCodeSettings);
2536+
return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.start + diagnostic.length, [diagnostic.code], this.formatCodeSettings, preferences);
25382537
});
25392538
}
25402539

@@ -2560,15 +2559,15 @@ Actual: ${stringify(fullActual)}`);
25602559
}
25612560
}
25622561

2563-
public verifyImportFixAtPosition(expectedTextArray: string[], errorCode?: number) {
2562+
public verifyImportFixAtPosition(expectedTextArray: string[], errorCode: number | undefined, preferences: ts.UserPreferences | undefined) {
25642563
const { fileName } = this.activeFile;
25652564
const ranges = this.getRanges().filter(r => r.fileName === fileName);
25662565
if (ranges.length !== 1) {
25672566
this.raiseError("Exactly one range should be specified in the testfile.");
25682567
}
25692568
const range = ts.first(ranges);
25702569

2571-
const codeFixes = this.getCodeFixes(fileName, errorCode);
2570+
const codeFixes = this.getCodeFixes(fileName, errorCode, preferences);
25722571

25732572
if (codeFixes.length === 0) {
25742573
if (expectedTextArray.length !== 0) {
@@ -2938,7 +2937,7 @@ Actual: ${stringify(fullActual)}`);
29382937

29392938
public verifyApplicableRefactorAvailableAtMarker(negative: boolean, markerName: string) {
29402939
const marker = this.getMarkerByName(markerName);
2941-
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, marker.position);
2940+
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, marker.position, ts.defaultPreferences);
29422941
const isAvailable = applicableRefactors && applicableRefactors.length > 0;
29432942
if (negative && isAvailable) {
29442943
this.raiseError(`verifyApplicableRefactorAvailableAtMarker failed - expected no refactor at marker ${markerName} but found some.`);
@@ -2958,7 +2957,7 @@ Actual: ${stringify(fullActual)}`);
29582957
public verifyRefactorAvailable(negative: boolean, name: string, actionName?: string) {
29592958
const selection = this.getSelection();
29602959

2961-
let refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, selection) || [];
2960+
let refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, selection, ts.defaultPreferences) || [];
29622961
refactors = refactors.filter(r => r.name === name && (actionName === undefined || r.actions.some(a => a.name === actionName)));
29632962
const isAvailable = refactors.length > 0;
29642963

@@ -2980,7 +2979,7 @@ Actual: ${stringify(fullActual)}`);
29802979
public verifyRefactor({ name, actionName, refactors }: FourSlashInterface.VerifyRefactorOptions) {
29812980
const selection = this.getSelection();
29822981

2983-
const actualRefactors = (this.languageService.getApplicableRefactors(this.activeFile.fileName, selection) || ts.emptyArray)
2982+
const actualRefactors = (this.languageService.getApplicableRefactors(this.activeFile.fileName, selection, ts.defaultPreferences) || ts.emptyArray)
29842983
.filter(r => r.name === name && r.actions.some(a => a.name === actionName));
29852984
this.assertObjectsEqual(actualRefactors, refactors);
29862985
}
@@ -2991,7 +2990,7 @@ Actual: ${stringify(fullActual)}`);
29912990
throw new Error("Exactly one refactor range is allowed per test.");
29922991
}
29932992

2994-
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, { pos: ranges[0].pos, end: ranges[0].end });
2993+
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, ts.first(ranges), ts.defaultPreferences);
29952994
const isAvailable = applicableRefactors && applicableRefactors.length > 0;
29962995
if (negative && isAvailable) {
29972996
this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected no refactor but found some.`);
@@ -3003,7 +3002,7 @@ Actual: ${stringify(fullActual)}`);
30033002

30043003
public applyRefactor({ refactorName, actionName, actionDescription, newContent: newContentWithRenameMarker }: FourSlashInterface.ApplyRefactorOptions) {
30053004
const range = this.getSelection();
3006-
const refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, range);
3005+
const refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, range, ts.defaultPreferences);
30073006
const refactorsWithName = refactors.filter(r => r.name === refactorName);
30083007
if (refactorsWithName.length === 0) {
30093008
this.raiseError(`The expected refactor: ${refactorName} is not available at the marker location.\nAvailable refactors: ${refactors.map(r => r.name)}`);
@@ -3017,7 +3016,7 @@ Actual: ${stringify(fullActual)}`);
30173016
this.raiseError(`Expected action description to be ${JSON.stringify(actionDescription)}, got: ${JSON.stringify(action.description)}`);
30183017
}
30193018

3020-
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName);
3019+
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName, ts.defaultPreferences);
30213020
for (const edit of editInfo.edits) {
30223021
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
30233022
}
@@ -3062,14 +3061,14 @@ Actual: ${stringify(fullActual)}`);
30623061
formattingOptions = formattingOptions || this.formatCodeSettings;
30633062
const markerPos = this.getMarkerByName(markerName).position;
30643063

3065-
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, markerPos);
3064+
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, markerPos, ts.defaultPreferences);
30663065
const applicableRefactorToApply = ts.find(applicableRefactors, refactor => refactor.name === refactorNameToApply);
30673066

30683067
if (!applicableRefactorToApply) {
30693068
this.raiseError(`The expected refactor: ${refactorNameToApply} is not available at the marker location.`);
30703069
}
30713070

3072-
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, formattingOptions, markerPos, refactorNameToApply, actionName);
3071+
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, formattingOptions, markerPos, refactorNameToApply, actionName, ts.defaultPreferences);
30733072

30743073
for (const edit of editInfo.edits) {
30753074
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
@@ -4217,8 +4216,8 @@ namespace FourSlashInterface {
42174216
this.state.applyCodeActionFromCompletion(markerName, options);
42184217
}
42194218

4220-
public importFixAtPosition(expectedTextArray: string[], errorCode?: number): void {
4221-
this.state.verifyImportFixAtPosition(expectedTextArray, errorCode);
4219+
public importFixAtPosition(expectedTextArray: string[], errorCode?: number, preferences?: ts.UserPreferences): void {
4220+
this.state.verifyImportFixAtPosition(expectedTextArray, errorCode, preferences);
42224221
}
42234222

42244223
public navigationBar(json: any, options?: { checkSpans?: boolean }) {
@@ -4424,7 +4423,7 @@ namespace FourSlashInterface {
44244423
this.state.printCurrentSignatureHelp();
44254424
}
44264425

4427-
public printCompletionListMembers(options: ts.GetCompletionsAtPositionOptions | undefined) {
4426+
public printCompletionListMembers(options: ts.UserPreferences | undefined) {
44284427
this.state.printCompletionListMembers(options);
44294428
}
44304429

@@ -4621,11 +4620,11 @@ namespace FourSlashInterface {
46214620
}
46224621

46234622
export type ExpectedCompletionEntry = string | { name: string, insertText?: string, replacementSpan?: FourSlash.Range };
4624-
export interface CompletionsAtOptions extends ts.GetCompletionsAtPositionOptions {
4623+
export interface CompletionsAtOptions extends Partial<ts.UserPreferences> {
46254624
isNewIdentifierLocation?: boolean;
46264625
}
46274626

4628-
export interface VerifyCompletionListContainsOptions extends ts.GetCompletionsAtPositionOptions {
4627+
export interface VerifyCompletionListContainsOptions extends ts.UserPreferences {
46294628
sourceDisplay: string;
46304629
isRecommended?: true;
46314630
insertText?: string;
@@ -4646,6 +4645,7 @@ namespace FourSlashInterface {
46464645
description: string;
46474646
errorCode?: number;
46484647
index?: number;
4648+
preferences?: ts.UserPreferences;
46494649
}
46504650

46514651
export interface VerifyCodeFixAvailableOptions {
@@ -4669,6 +4669,7 @@ namespace FourSlashInterface {
46694669
name: string;
46704670
source?: string;
46714671
description: string;
4672+
preferences?: ts.UserPreferences;
46724673
}
46734674

46744675
export interface Diagnostic {

Diff for: src/harness/harnessLanguageService.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -420,11 +420,11 @@ namespace Harness.LanguageService {
420420
getEncodedSemanticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications {
421421
return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length));
422422
}
423-
getCompletionsAtPosition(fileName: string, position: number, options: ts.GetCompletionsAtPositionOptions | undefined): ts.CompletionInfo {
424-
return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, options));
423+
getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined): ts.CompletionInfo {
424+
return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences));
425425
}
426-
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: ts.FormatCodeOptions | undefined, source: string | undefined): ts.CompletionEntryDetails {
427-
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(options), source));
426+
getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | undefined, source: string | undefined, preferences: ts.UserPreferences | undefined): ts.CompletionEntryDetails {
427+
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, preferences));
428428
}
429429
getCompletionEntrySymbol(): ts.Symbol {
430430
throw new Error("getCompletionEntrySymbol not implemented across the shim layer.");

Diff for: src/harness/unittests/extractTestHelpers.ts

+2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ namespace ts {
127127
endPosition: selectionRange.end,
128128
host: notImplementedHost,
129129
formatContext: formatting.getFormatContext(testFormatOptions),
130+
preferences: defaultPreferences,
130131
};
131132
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange));
132133
assert.equal(rangeToExtract.errors, undefined, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText);
@@ -190,6 +191,7 @@ namespace ts {
190191
endPosition: selectionRange.end,
191192
host: notImplementedHost,
192193
formatContext: formatting.getFormatContext(testFormatOptions),
194+
preferences: defaultPreferences,
193195
};
194196
const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange));
195197
assert.isUndefined(rangeToExtract.errors, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText);

0 commit comments

Comments
 (0)