Skip to content

Commit

Permalink
Support quote style for throw new Error('Method not implemented.') (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy Hanson committed Mar 13, 2018
1 parent 940979a commit ca0beaf
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 39 deletions.
7 changes: 5 additions & 2 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,23 @@ namespace ts {

// Literals

/* @internal */ export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote: boolean): StringLiteral; // tslint:disable-line unified-signatures
/** If a node is passed, creates a string literal whose source text is read from a source node during emit. */
export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): StringLiteral;
export function createLiteral(value: number): NumericLiteral;
export function createLiteral(value: boolean): BooleanLiteral;
export function createLiteral(value: string | number | boolean): PrimaryExpression;
export function createLiteral(value: string | number | boolean | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): PrimaryExpression {
export function createLiteral(value: string | number | boolean | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote?: boolean): PrimaryExpression {
if (typeof value === "number") {
return createNumericLiteral(value + "");
}
if (typeof value === "boolean") {
return value ? createTrue() : createFalse();
}
if (isString(value)) {
return createStringLiteral(value);
const res = createStringLiteral(value);
if (isSingleQuote) res.singleQuote = true;
return res;
}
return createLiteralFromNode(value);
}
Expand Down
3 changes: 2 additions & 1 deletion src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2482,7 +2482,7 @@ Actual: ${stringify(fullActual)}`);

public verifyCodeFix(options: FourSlashInterface.VerifyCodeFixOptions) {
const fileName = this.activeFile.fileName;
const actions = this.getCodeFixes(fileName, options.errorCode);
const actions = this.getCodeFixes(fileName, options.errorCode, options.options);
let index = options.index;
if (index === undefined) {
if (!(actions && actions.length === 1)) {
Expand Down Expand Up @@ -4662,6 +4662,7 @@ namespace FourSlashInterface {
description: string;
errorCode?: number;
index?: number;
options?: ts.Options;
}

export interface VerifyCodeFixAvailableOptions {
Expand Down
32 changes: 25 additions & 7 deletions src/services/codefixes/fixAddMissingMember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace ts.codefix {
const info = getInfo(context.sourceFile, context.span.start, context.program.getTypeChecker());
if (!info) return undefined;
const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs);
const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs, context.options);
const addMember = inJs ?
singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, classDeclarationSourceFile, classDeclaration, token.text, makeStatic)) :
getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, classDeclaration, token, makeStatic);
Expand All @@ -21,7 +21,7 @@ namespace ts.codefix {
getAllCodeActions: context => {
const seenNames = createMap<true>();
return codeFixAll(context, errorCodes, (changes, diag) => {
const { program } = context;
const { program, options } = context;
const info = getInfo(diag.file!, diag.start!, program.getTypeChecker());
if (!info) return;
const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
Expand All @@ -31,7 +31,7 @@ namespace ts.codefix {

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

function getActionForMethodDeclaration(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFixAction | undefined {
function getActionForMethodDeclaration(
context: CodeFixContext,
classDeclarationSourceFile: SourceFile,
classDeclaration: ClassLikeDeclaration,
token: Identifier,
callExpression: CallExpression,
makeStatic: boolean,
inJs: boolean,
options: Options,
): CodeFixAction | undefined {
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]);
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs));
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs, options));
return { description, changes, fixId };
}

function addMethodDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean) {
const methodDeclaration = createMethodFromCallExpression(callExpression, token.text, inJs, makeStatic);
function addMethodDeclaration(
changeTracker: textChanges.ChangeTracker,
classDeclarationSourceFile: SourceFile,
classDeclaration: ClassLikeDeclaration,
token: Identifier,
callExpression: CallExpression,
makeStatic: boolean,
inJs: boolean,
options: Options,
): void {
const methodDeclaration = createMethodFromCallExpression(callExpression, token.text, inJs, makeStatic, options);
changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace ts.codefix {
getCodeActions(context) {
const { program, sourceFile, span } = context;
const changes = textChanges.ChangeTracker.with(context, t =>
addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), t));
addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), t, context.options));
return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, fixId }];
},
fixIds: [fixId],
Expand All @@ -19,7 +19,7 @@ namespace ts.codefix {
return codeFixAll(context, errorCodes, (changes, diag) => {
const classDeclaration = getClass(diag.file!, diag.start!);
if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) {
addMissingMembers(classDeclaration, context.sourceFile, context.program.getTypeChecker(), changes);
addMissingMembers(classDeclaration, context.sourceFile, context.program.getTypeChecker(), changes, context.options);
}
});
},
Expand All @@ -32,15 +32,15 @@ namespace ts.codefix {
return cast(token.parent, isClassLike);
}

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

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

createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));
createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker, options, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));
}

function symbolPointsToNonPrivateAndAbstractMember(symbol: Symbol): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace ts.codefix {
const classDeclaration = getClass(sourceFile, span.start);
const checker = program.getTypeChecker();
return mapDefined<ExpressionWithTypeArguments, CodeFixAction>(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => {
const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t));
const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t, context.options));
if (changes.length === 0) return undefined;
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]);
return { description, changes, fixId };
Expand All @@ -23,7 +23,7 @@ namespace ts.codefix {
const classDeclaration = getClass(diag.file!, diag.start!);
if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) {
for (const implementedTypeNode of getClassImplementsHeritageClauseElements(classDeclaration)) {
addMissingDeclarations(context.program.getTypeChecker(), implementedTypeNode, diag.file!, classDeclaration, changes);
addMissingDeclarations(context.program.getTypeChecker(), implementedTypeNode, diag.file!, classDeclaration, changes, context.options);
}
}
});
Expand All @@ -39,7 +39,8 @@ namespace ts.codefix {
implementedTypeNode: ExpressionWithTypeArguments,
sourceFile: SourceFile,
classDeclaration: ClassLikeDeclaration,
changeTracker: textChanges.ChangeTracker
changeTracker: textChanges.ChangeTracker,
options: Options,
): void {
// Note that this is ultimately derived from a map indexed by symbol names,
// so duplicates cannot occur.
Expand All @@ -56,7 +57,7 @@ namespace ts.codefix {
createMissingIndexSignatureDeclaration(implementedType, IndexKind.String);
}

createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));
createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker, options, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));

function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void {
const indexInfoOfKind = checker.getIndexInfoOfType(type, kind);
Expand Down
43 changes: 29 additions & 14 deletions src/services/codefixes/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ namespace ts.codefix {
* @param possiblyMissingSymbols The collection of symbols to filter and then get insertions for.
* @returns Empty string iff there are no member insertions.
*/
export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: ReadonlyArray<Symbol>, checker: TypeChecker, out: (node: ClassElement) => void): void {
export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: ReadonlyArray<Symbol>, checker: TypeChecker, options: Options, out: (node: ClassElement) => void): void {
const classMembers = classDeclaration.symbol.members;
for (const symbol of possiblyMissingSymbols) {
if (!classMembers.has(symbol.escapedName)) {
addNewNodeForMemberSymbol(symbol, classDeclaration, checker, out);
addNewNodeForMemberSymbol(symbol, classDeclaration, checker, options, out);
}
}
}

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

Expand All @@ -74,11 +74,11 @@ namespace ts.codefix {

if (declarations.length > signatures.length) {
const signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration);
outputMethod(signature, modifiers, name, createStubbedMethodBody());
outputMethod(signature, modifiers, name, createStubbedMethodBody(options));
}
else {
Debug.assert(declarations.length === signatures.length);
out(createMethodImplementingSignatures(signatures, name, optional, modifiers));
out(createMethodImplementingSignatures(signatures, name, optional, modifiers, options));
}
break;
}
Expand Down Expand Up @@ -107,7 +107,13 @@ namespace ts.codefix {
return nodes && createNodeArray(nodes.map(getSynthesizedDeepClone));
}

export function createMethodFromCallExpression({ typeArguments, arguments: args }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean): MethodDeclaration {
export function createMethodFromCallExpression(
{ typeArguments, arguments: args }: CallExpression,
methodName: string,
inJs: boolean,
makeStatic: boolean,
options: Options,
): MethodDeclaration {
return createMethod(
/*decorators*/ undefined,
/*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
Expand All @@ -118,7 +124,7 @@ namespace ts.codefix {
createTypeParameterDeclaration(CharacterCodes.T + typeArguments.length - 1 <= CharacterCodes.Z ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`)),
/*parameters*/ createDummyParameters(args.length, /*names*/ undefined, /*minArgumentCount*/ undefined, inJs),
/*type*/ inJs ? undefined : createKeywordTypeNode(SyntaxKind.AnyKeyword),
createStubbedMethodBody());
createStubbedMethodBody(options));
}

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

function createMethodImplementingSignatures(signatures: ReadonlyArray<Signature>, name: PropertyName, optional: boolean, modifiers: ReadonlyArray<Modifier> | undefined): MethodDeclaration {
function createMethodImplementingSignatures(
signatures: ReadonlyArray<Signature>,
name: PropertyName,
optional: boolean,
modifiers: ReadonlyArray<Modifier> | undefined,
options: Options,
): MethodDeclaration {
/** This is *a* signature with the maximal number of arguments,
* such that if there is a "maximal" signature without rest arguments,
* this is one of them.
Expand Down Expand Up @@ -178,7 +190,8 @@ namespace ts.codefix {
optional,
/*typeParameters*/ undefined,
parameters,
/*returnType*/ undefined);
/*returnType*/ undefined,
options);
}

function createStubbedMethod(
Expand All @@ -187,7 +200,9 @@ namespace ts.codefix {
optional: boolean,
typeParameters: ReadonlyArray<TypeParameterDeclaration> | undefined,
parameters: ReadonlyArray<ParameterDeclaration>,
returnType: TypeNode | undefined) {
returnType: TypeNode | undefined,
options: Options
): MethodDeclaration {
return createMethod(
/*decorators*/ undefined,
modifiers,
Expand All @@ -197,16 +212,16 @@ namespace ts.codefix {
typeParameters,
parameters,
returnType,
createStubbedMethodBody());
createStubbedMethodBody(options));
}

function createStubbedMethodBody() {
function createStubbedMethodBody(options: Options): Block {
return createBlock(
[createThrow(
createNew(
createIdentifier("Error"),
/*typeArguments*/ undefined,
[createLiteral("Method not implemented.")]))],
[createLiteral("Method not implemented.", /*isSingleQuote*/ options.quote === "single")]))],
/*multiline*/ true);
}

Expand Down
9 changes: 2 additions & 7 deletions src/services/codefixes/importFixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,11 @@ namespace ts.codefix {
}

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

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

function createStringLiteralWithQuoteStyle(sourceFile: SourceFile, text: string, options: Options): StringLiteral {
const literal = createLiteral(text);
literal.singleQuote = shouldUseSingleQuote(sourceFile, options);
return literal;
}
function shouldUseSingleQuote(sourceFile: SourceFile, options: Options): boolean {
if (options.quote) {
return options.quote === "single";
Expand Down
Loading

0 comments on commit ca0beaf

Please sign in to comment.