Skip to content

Commit ca0beaf

Browse files
author
Andy Hanson
committed
Support quote style for throw new Error('Method not implemented.') (#18169)
1 parent 940979a commit ca0beaf

9 files changed

+93
-39
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

+2-1
Original file line numberDiff line numberDiff line change
@@ -2482,7 +2482,7 @@ Actual: ${stringify(fullActual)}`);
24822482

24832483
public verifyCodeFix(options: FourSlashInterface.VerifyCodeFixOptions) {
24842484
const fileName = this.activeFile.fileName;
2485-
const actions = this.getCodeFixes(fileName, options.errorCode);
2485+
const actions = this.getCodeFixes(fileName, options.errorCode, options.options);
24862486
let index = options.index;
24872487
if (index === undefined) {
24882488
if (!(actions && actions.length === 1)) {
@@ -4662,6 +4662,7 @@ namespace FourSlashInterface {
46624662
description: string;
46634663
errorCode?: number;
46644664
index?: number;
4665+
options?: ts.Options;
46654666
}
46664667

46674668
export interface VerifyCodeFixAvailableOptions {

Diff for: src/services/codefixes/fixAddMissingMember.ts

+25-7
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace ts.codefix {
1111
const info = getInfo(context.sourceFile, context.span.start, context.program.getTypeChecker());
1212
if (!info) return undefined;
1313
const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
14-
const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs);
14+
const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs, context.options);
1515
const addMember = inJs ?
1616
singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, classDeclarationSourceFile, classDeclaration, token.text, makeStatic)) :
1717
getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, classDeclaration, token, makeStatic);
@@ -21,7 +21,7 @@ namespace ts.codefix {
2121
getAllCodeActions: context => {
2222
const seenNames = createMap<true>();
2323
return codeFixAll(context, errorCodes, (changes, diag) => {
24-
const { program } = context;
24+
const { program, options } = context;
2525
const info = getInfo(diag.file!, diag.start!, program.getTypeChecker());
2626
if (!info) return;
2727
const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
@@ -31,7 +31,7 @@ namespace ts.codefix {
3131

3232
// Always prefer to add a method declaration if possible.
3333
if (call) {
34-
addMethodDeclaration(changes, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs);
34+
addMethodDeclaration(changes, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs, options);
3535
}
3636
else {
3737
if (inJs) {
@@ -181,14 +181,32 @@ namespace ts.codefix {
181181
return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, fixId: undefined };
182182
}
183183

184-
function getActionForMethodDeclaration(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFixAction | undefined {
184+
function getActionForMethodDeclaration(
185+
context: CodeFixContext,
186+
classDeclarationSourceFile: SourceFile,
187+
classDeclaration: ClassLikeDeclaration,
188+
token: Identifier,
189+
callExpression: CallExpression,
190+
makeStatic: boolean,
191+
inJs: boolean,
192+
options: Options,
193+
): CodeFixAction | undefined {
185194
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]);
186-
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs));
195+
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs, options));
187196
return { description, changes, fixId };
188197
}
189198

190-
function addMethodDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean) {
191-
const methodDeclaration = createMethodFromCallExpression(callExpression, token.text, inJs, makeStatic);
199+
function addMethodDeclaration(
200+
changeTracker: textChanges.ChangeTracker,
201+
classDeclarationSourceFile: SourceFile,
202+
classDeclaration: ClassLikeDeclaration,
203+
token: Identifier,
204+
callExpression: CallExpression,
205+
makeStatic: boolean,
206+
inJs: boolean,
207+
options: Options,
208+
): void {
209+
const methodDeclaration = createMethodFromCallExpression(callExpression, token.text, inJs, makeStatic, options);
192210
changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration);
193211
}
194212
}

Diff for: src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace ts.codefix {
1010
getCodeActions(context) {
1111
const { program, sourceFile, span } = context;
1212
const changes = textChanges.ChangeTracker.with(context, t =>
13-
addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), t));
13+
addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), t, context.options));
1414
return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, fixId }];
1515
},
1616
fixIds: [fixId],
@@ -19,7 +19,7 @@ namespace ts.codefix {
1919
return codeFixAll(context, errorCodes, (changes, diag) => {
2020
const classDeclaration = getClass(diag.file!, diag.start!);
2121
if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) {
22-
addMissingMembers(classDeclaration, context.sourceFile, context.program.getTypeChecker(), changes);
22+
addMissingMembers(classDeclaration, context.sourceFile, context.program.getTypeChecker(), changes, context.options);
2323
}
2424
});
2525
},
@@ -32,15 +32,15 @@ namespace ts.codefix {
3232
return cast(token.parent, isClassLike);
3333
}
3434

35-
function addMissingMembers(classDeclaration: ClassLikeDeclaration, sourceFile: SourceFile, checker: TypeChecker, changeTracker: textChanges.ChangeTracker): void {
35+
function addMissingMembers(classDeclaration: ClassLikeDeclaration, sourceFile: SourceFile, checker: TypeChecker, changeTracker: textChanges.ChangeTracker, options: Options): void {
3636
const extendsNode = getClassExtendsHeritageClauseElement(classDeclaration);
3737
const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode);
3838

3939
// Note that this is ultimately derived from a map indexed by symbol names,
4040
// so duplicates cannot occur.
4141
const abstractAndNonPrivateExtendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType).filter(symbolPointsToNonPrivateAndAbstractMember);
4242

43-
createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));
43+
createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker, options, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));
4444
}
4545

4646
function symbolPointsToNonPrivateAndAbstractMember(symbol: Symbol): boolean {

Diff for: src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace ts.codefix {
1010
const classDeclaration = getClass(sourceFile, span.start);
1111
const checker = program.getTypeChecker();
1212
return mapDefined<ExpressionWithTypeArguments, CodeFixAction>(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => {
13-
const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t));
13+
const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t, context.options));
1414
if (changes.length === 0) return undefined;
1515
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]);
1616
return { description, changes, fixId };
@@ -23,7 +23,7 @@ namespace ts.codefix {
2323
const classDeclaration = getClass(diag.file!, diag.start!);
2424
if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) {
2525
for (const implementedTypeNode of getClassImplementsHeritageClauseElements(classDeclaration)) {
26-
addMissingDeclarations(context.program.getTypeChecker(), implementedTypeNode, diag.file!, classDeclaration, changes);
26+
addMissingDeclarations(context.program.getTypeChecker(), implementedTypeNode, diag.file!, classDeclaration, changes, context.options);
2727
}
2828
}
2929
});
@@ -39,7 +39,8 @@ namespace ts.codefix {
3939
implementedTypeNode: ExpressionWithTypeArguments,
4040
sourceFile: SourceFile,
4141
classDeclaration: ClassLikeDeclaration,
42-
changeTracker: textChanges.ChangeTracker
42+
changeTracker: textChanges.ChangeTracker,
43+
options: Options,
4344
): void {
4445
// Note that this is ultimately derived from a map indexed by symbol names,
4546
// so duplicates cannot occur.
@@ -56,7 +57,7 @@ namespace ts.codefix {
5657
createMissingIndexSignatureDeclaration(implementedType, IndexKind.String);
5758
}
5859

59-
createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));
60+
createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker, options, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));
6061

6162
function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void {
6263
const indexInfoOfKind = checker.getIndexInfoOfType(type, kind);

Diff for: src/services/codefixes/helpers.ts

+29-14
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ namespace ts.codefix {
66
* @param possiblyMissingSymbols The collection of symbols to filter and then get insertions for.
77
* @returns Empty string iff there are no member insertions.
88
*/
9-
export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: ReadonlyArray<Symbol>, checker: TypeChecker, out: (node: ClassElement) => void): void {
9+
export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: ReadonlyArray<Symbol>, checker: TypeChecker, options: Options, out: (node: ClassElement) => void): void {
1010
const classMembers = classDeclaration.symbol.members;
1111
for (const symbol of possiblyMissingSymbols) {
1212
if (!classMembers.has(symbol.escapedName)) {
13-
addNewNodeForMemberSymbol(symbol, classDeclaration, checker, out);
13+
addNewNodeForMemberSymbol(symbol, classDeclaration, checker, options, out);
1414
}
1515
}
1616
}
1717

1818
/**
1919
* @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`.
2020
*/
21-
function addNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker, out: (node: Node) => void): void {
21+
function addNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker, options: Options, out: (node: Node) => void): void {
2222
const declarations = symbol.getDeclarations();
2323
if (!(declarations && declarations.length)) {
2424
return undefined;
@@ -63,7 +63,7 @@ namespace ts.codefix {
6363
if (declarations.length === 1) {
6464
Debug.assert(signatures.length === 1);
6565
const signature = signatures[0];
66-
outputMethod(signature, modifiers, name, createStubbedMethodBody());
66+
outputMethod(signature, modifiers, name, createStubbedMethodBody(options));
6767
break;
6868
}
6969

@@ -74,11 +74,11 @@ namespace ts.codefix {
7474

7575
if (declarations.length > signatures.length) {
7676
const signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration);
77-
outputMethod(signature, modifiers, name, createStubbedMethodBody());
77+
outputMethod(signature, modifiers, name, createStubbedMethodBody(options));
7878
}
7979
else {
8080
Debug.assert(declarations.length === signatures.length);
81-
out(createMethodImplementingSignatures(signatures, name, optional, modifiers));
81+
out(createMethodImplementingSignatures(signatures, name, optional, modifiers, options));
8282
}
8383
break;
8484
}
@@ -107,7 +107,13 @@ namespace ts.codefix {
107107
return nodes && createNodeArray(nodes.map(getSynthesizedDeepClone));
108108
}
109109

110-
export function createMethodFromCallExpression({ typeArguments, arguments: args }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean): MethodDeclaration {
110+
export function createMethodFromCallExpression(
111+
{ typeArguments, arguments: args }: CallExpression,
112+
methodName: string,
113+
inJs: boolean,
114+
makeStatic: boolean,
115+
options: Options,
116+
): MethodDeclaration {
111117
return createMethod(
112118
/*decorators*/ undefined,
113119
/*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
@@ -118,7 +124,7 @@ namespace ts.codefix {
118124
createTypeParameterDeclaration(CharacterCodes.T + typeArguments.length - 1 <= CharacterCodes.Z ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`)),
119125
/*parameters*/ createDummyParameters(args.length, /*names*/ undefined, /*minArgumentCount*/ undefined, inJs),
120126
/*type*/ inJs ? undefined : createKeywordTypeNode(SyntaxKind.AnyKeyword),
121-
createStubbedMethodBody());
127+
createStubbedMethodBody(options));
122128
}
123129

124130
function createDummyParameters(argCount: number, names: string[] | undefined, minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] {
@@ -137,7 +143,13 @@ namespace ts.codefix {
137143
return parameters;
138144
}
139145

140-
function createMethodImplementingSignatures(signatures: ReadonlyArray<Signature>, name: PropertyName, optional: boolean, modifiers: ReadonlyArray<Modifier> | undefined): MethodDeclaration {
146+
function createMethodImplementingSignatures(
147+
signatures: ReadonlyArray<Signature>,
148+
name: PropertyName,
149+
optional: boolean,
150+
modifiers: ReadonlyArray<Modifier> | undefined,
151+
options: Options,
152+
): MethodDeclaration {
141153
/** This is *a* signature with the maximal number of arguments,
142154
* such that if there is a "maximal" signature without rest arguments,
143155
* this is one of them.
@@ -178,7 +190,8 @@ namespace ts.codefix {
178190
optional,
179191
/*typeParameters*/ undefined,
180192
parameters,
181-
/*returnType*/ undefined);
193+
/*returnType*/ undefined,
194+
options);
182195
}
183196

184197
function createStubbedMethod(
@@ -187,7 +200,9 @@ namespace ts.codefix {
187200
optional: boolean,
188201
typeParameters: ReadonlyArray<TypeParameterDeclaration> | undefined,
189202
parameters: ReadonlyArray<ParameterDeclaration>,
190-
returnType: TypeNode | undefined) {
203+
returnType: TypeNode | undefined,
204+
options: Options
205+
): MethodDeclaration {
191206
return createMethod(
192207
/*decorators*/ undefined,
193208
modifiers,
@@ -197,16 +212,16 @@ namespace ts.codefix {
197212
typeParameters,
198213
parameters,
199214
returnType,
200-
createStubbedMethodBody());
215+
createStubbedMethodBody(options));
201216
}
202217

203-
function createStubbedMethodBody() {
218+
function createStubbedMethodBody(options: Options): Block {
204219
return createBlock(
205220
[createThrow(
206221
createNew(
207222
createIdentifier("Error"),
208223
/*typeArguments*/ undefined,
209-
[createLiteral("Method not implemented.")]))],
224+
[createLiteral("Method not implemented.", /*isSingleQuote*/ options.quote === "single")]))],
210225
/*multiline*/ true);
211226
}
212227

Diff for: src/services/codefixes/importFixes.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,11 @@ namespace ts.codefix {
185185
}
186186

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

191191
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
192-
const quotedModuleSpecifier = createStringLiteralWithQuoteStyle(sourceFile, moduleSpecifierWithoutQuotes, context.options);
192+
const quotedModuleSpecifier = createLiteral(moduleSpecifierWithoutQuotes, shouldUseSingleQuote(sourceFile, options));
193193
const importDecl = importKind !== ImportKind.Equals
194194
? createImportDeclaration(
195195
/*decorators*/ undefined,
@@ -217,11 +217,6 @@ namespace ts.codefix {
217217
return createCodeAction(Diagnostics.Import_0_from_module_1, [symbolName, moduleSpecifierWithoutQuotes], changes);
218218
}
219219

220-
function createStringLiteralWithQuoteStyle(sourceFile: SourceFile, text: string, options: Options): StringLiteral {
221-
const literal = createLiteral(text);
222-
literal.singleQuote = shouldUseSingleQuote(sourceFile, options);
223-
return literal;
224-
}
225220
function shouldUseSingleQuote(sourceFile: SourceFile, options: Options): boolean {
226221
if (options.quote) {
227222
return options.quote === "single";

0 commit comments

Comments
 (0)