Replace TextChangesContext with RefactorOrCodeFixContext

Thanks to @andy-ms for the suggestion!
This commit is contained in:
Andrew Casey 2018-01-17 15:43:36 -08:00
parent 13bf7f9c7c
commit 3a38c8ea58
23 changed files with 47 additions and 59 deletions

View file

@ -7,7 +7,7 @@ namespace ts {
getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions;
}
export interface CodeFixContextBase extends RefactorOrCodeFixContext {
export interface CodeFixContextBase extends textChanges.TextChangesContext {
sourceFile: SourceFile;
program: Program;
cancellationToken: CancellationToken;
@ -83,7 +83,7 @@ namespace ts {
export function codeFixAll(context: CodeFixAllContext, errorCodes: number[], use: (changes: textChanges.ChangeTracker, error: Diagnostic, commands: Push<CodeActionCommand>) => void): CombinedCodeActions {
const commands: CodeActionCommand[] = [];
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t =>
const changes = textChanges.ChangeTracker.with(context, t =>
eachDiagnostic(context, errorCodes, diag => use(t, diag, commands)));
return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands);
}

View file

@ -5,7 +5,7 @@ namespace ts.codefix {
registerCodeFix({
errorCodes,
getCodeActions: (context) => {
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => makeChange(t, context.sourceFile, context.span.start));
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start));
return [{ description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), changes, fixId }];
},
fixIds: [fixId],

View file

@ -7,7 +7,7 @@ namespace ts.codefix {
getCodeActions(context) {
const qualifiedName = getQualifiedName(context.sourceFile, context.span.start);
if (!qualifiedName) return undefined;
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, context.sourceFile, qualifiedName));
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, qualifiedName));
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${qualifiedName.left.text}["${qualifiedName.right.text}"]`]);
return [{ description, changes, fixId }];
},

View file

@ -15,7 +15,7 @@ namespace ts.codefix {
return undefined;
}
const newLineCharacter = getNewLineFromContext(context);
const newLineCharacter = textChanges.getNewLineFromContext(context);
return [{
description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message),
@ -38,7 +38,7 @@ namespace ts.codefix {
fixIds: [fixId], // No point applying as a group, doing it once will fix all errors
getAllCodeActions: context => codeFixAllWithTextChanges(context, errorCodes, (changes, err) => {
if (err.start !== undefined) {
changes.push(getIgnoreCommentLocationForLocation(err.file!, err.start, getNewLineFromContext(context)));
changes.push(getIgnoreCommentLocationForLocation(err.file!, err.start, textChanges.getNewLineFromContext(context)));
}
}),
});

View file

@ -96,7 +96,7 @@ namespace ts.codefix {
}
function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined {
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic));
const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic));
if (changes.length === 0) return undefined;
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]);
return { description, changes, fixId };
@ -144,7 +144,7 @@ namespace ts.codefix {
function createAddPropertyDeclarationAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFixAction {
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0), [tokenName]);
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addPropertyDeclaration(t, classDeclarationSourceFile, classDeclaration, tokenName, typeNode, makeStatic));
const changes = textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, classDeclarationSourceFile, classDeclaration, tokenName, typeNode, makeStatic));
return { description, changes, fixId };
}
@ -176,14 +176,14 @@ namespace ts.codefix {
[indexingParameter],
typeNode);
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => t.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature));
const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature));
// No fixId here because code-fix-all currently only works on adding individual named properties.
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 {
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]);
const changes = textChanges.ChangeTracker.with(toTextChangesContext(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));
return { description, changes, fixId };
}

View file

@ -11,7 +11,7 @@ namespace ts.codefix {
const { sourceFile, span } = context;
const nodes = getNodes(sourceFile, span.start);
if (!nodes) return undefined;
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, nodes));
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, nodes));
return [{ description: getLocaleSpecificMessage(Diagnostics.Add_async_modifier_to_containing_function), changes, fixId }];
},
fixIds: [fixId],

View file

@ -9,7 +9,7 @@ namespace ts.codefix {
errorCodes,
getCodeActions(context) {
const { program, sourceFile, span } = context;
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t =>
const changes = textChanges.ChangeTracker.with(context, t =>
addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), t));
return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, fixId }];
},

View file

@ -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(toTextChangesContext(context), t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t));
const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t));
if (changes.length === 0) return undefined;
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]);
return { description, changes, fixId };

View file

@ -9,7 +9,7 @@ namespace ts.codefix {
const nodes = getNodes(sourceFile, span.start);
if (!nodes) return undefined;
const { constructor, superCall } = nodes;
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, constructor, superCall));
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, constructor, superCall));
return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), changes, fixId }];
},
fixIds: [fixId],

View file

@ -7,7 +7,7 @@ namespace ts.codefix {
getCodeActions(context) {
const { sourceFile, span } = context;
const ctr = getNode(sourceFile, span.start);
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, ctr));
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, ctr));
return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), changes, fixId }];
},
fixIds: [fixId],

View file

@ -9,7 +9,7 @@ namespace ts.codefix {
const nodes = getNodes(sourceFile, context.span.start);
if (!nodes) return undefined;
const { extendsToken, heritageClauses } = nodes;
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChanges(t, sourceFile, extendsToken, heritageClauses));
const changes = textChanges.ChangeTracker.with(context, t => doChanges(t, sourceFile, extendsToken, heritageClauses));
return [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), changes, fixId }];
},
fixIds: [fixId],

View file

@ -7,7 +7,7 @@ namespace ts.codefix {
getCodeActions(context) {
const { sourceFile } = context;
const token = getNode(sourceFile, context.span.start);
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, token));
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, token));
return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), changes, fixId }];
},
fixIds: [fixId],

View file

@ -32,7 +32,7 @@ namespace ts.codefix {
createImportClause(namespace.name, /*namedBindings*/ undefined),
node.moduleSpecifier
);
const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context));
const changeTracker = textChanges.ChangeTracker.fromContext(context);
changeTracker.replaceNode(sourceFile, node, replacement, { useNonAdjustedEndPosition: true });
const changes = changeTracker.getChanges();
variations.push({
@ -48,7 +48,7 @@ namespace ts.codefix {
namespace.name,
createExternalModuleReference(node.moduleSpecifier)
);
const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context));
const changeTracker = textChanges.ChangeTracker.fromContext(context);
changeTracker.replaceNode(sourceFile, node, replacement, { useNonAdjustedEndPosition: true });
const changes = changeTracker.getChanges();
variations.push({
@ -86,7 +86,7 @@ namespace ts.codefix {
addRange(fixes, getCodeFixesForImportDeclaration(context, relatedImport));
}
const propertyAccess = createPropertyAccess(expr, "default");
const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context));
const changeTracker = textChanges.ChangeTracker.fromContext(context);
changeTracker.replaceNode(sourceFile, expr, propertyAccess, {});
const changes = changeTracker.getChanges();
fixes.push({

View file

@ -12,7 +12,7 @@ namespace ts.codefix {
const info = getInfo(sourceFile, context.span.start, context.program.getTypeChecker());
if (!info) return undefined;
const { node, suggestion } = info;
const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, node, suggestion));
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestion));
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]);
return [{ description, changes, fixId }];
},

View file

@ -13,13 +13,13 @@ namespace ts.codefix {
const token = getToken(sourceFile, context.span.start);
const result: CodeFixAction[] = [];
const deletion = textChanges.ChangeTracker.with(toTextChangesContext(context), t => tryDeleteDeclaration(t, sourceFile, token));
const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token));
if (deletion.length) {
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), [token.getText()]);
result.push({ description, changes: deletion, fixId: fixIdDelete });
}
const prefix = textChanges.ChangeTracker.with(toTextChangesContext(context), t => tryPrefixDeclaration(t, context.errorCode, sourceFile, token));
const prefix = textChanges.ChangeTracker.with(context, t => tryPrefixDeclaration(t, context.errorCode, sourceFile, token));
if (prefix.length) {
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), [token.getText()]);
result.push({ description, changes: prefix, fixId: fixIdPrefix });

View file

@ -257,7 +257,7 @@ namespace ts.codefix {
}
}
function getCodeActionForNewImport(context: SymbolContext & RefactorOrCodeFixContext & { kind: ImportKind }, moduleSpecifier: string): ImportCodeAction {
function getCodeActionForNewImport(context: SymbolContext & textChanges.TextChangesContext & { kind: ImportKind }, moduleSpecifier: string): ImportCodeAction {
const { kind, sourceFile, symbolName } = context;
const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax);
@ -275,7 +275,7 @@ namespace ts.codefix {
createIdentifier(symbolName),
createExternalModuleReference(quotedModuleSpecifier));
const changes = ChangeTracker.with(toTextChangesContext(context), changeTracker => {
const changes = ChangeTracker.with(context, changeTracker => {
if (lastImportDeclaration) {
changeTracker.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl);
}
@ -669,33 +669,33 @@ namespace ts.codefix {
return expression && isStringLiteral(expression) ? expression.text : undefined;
}
function tryUpdateExistingImport(context: SymbolContext & RefactorOrCodeFixContext & { kind: ImportKind }, importClause: ImportClause | ImportEqualsDeclaration): FileTextChanges[] | undefined {
function tryUpdateExistingImport(context: SymbolContext & textChanges.TextChangesContext & { kind: ImportKind }, importClause: ImportClause | ImportEqualsDeclaration): FileTextChanges[] | undefined {
const { symbolName, sourceFile, kind } = context;
const { name } = importClause;
const { namedBindings } = importClause.kind !== SyntaxKind.ImportEqualsDeclaration && importClause;
switch (kind) {
case ImportKind.Default:
return name ? undefined : ChangeTracker.with(toTextChangesContext(context), t =>
return name ? undefined : ChangeTracker.with(context, t =>
t.replaceNode(sourceFile, importClause, createImportClause(createIdentifier(symbolName), namedBindings)));
case ImportKind.Named: {
const newImportSpecifier = createImportSpecifier(/*propertyName*/ undefined, createIdentifier(symbolName));
if (namedBindings && namedBindings.kind === SyntaxKind.NamedImports && namedBindings.elements.length !== 0) {
// There are already named imports; add another.
return ChangeTracker.with(toTextChangesContext(context), t => t.insertNodeInListAfter(
return ChangeTracker.with(context, t => t.insertNodeInListAfter(
sourceFile,
namedBindings.elements[namedBindings.elements.length - 1],
newImportSpecifier));
}
if (!namedBindings || namedBindings.kind === SyntaxKind.NamedImports && namedBindings.elements.length === 0) {
return ChangeTracker.with(toTextChangesContext(context), t =>
return ChangeTracker.with(context, t =>
t.replaceNode(sourceFile, importClause, createImportClause(name, createNamedImports([newImportSpecifier]))));
}
return undefined;
}
case ImportKind.Namespace:
return namedBindings ? undefined : ChangeTracker.with(toTextChangesContext(context), t =>
return namedBindings ? undefined : ChangeTracker.with(context, t =>
t.replaceNode(sourceFile, importClause, createImportClause(name, createNamespaceImport(createIdentifier(symbolName)))));
case ImportKind.Equals:
@ -706,7 +706,7 @@ namespace ts.codefix {
}
}
function getCodeActionForUseExistingNamespaceImport(namespacePrefix: string, context: SymbolContext & RefactorOrCodeFixContext, symbolToken: Identifier): ImportCodeAction {
function getCodeActionForUseExistingNamespaceImport(namespacePrefix: string, context: SymbolContext & textChanges.TextChangesContext, symbolToken: Identifier): ImportCodeAction {
const { symbolName, sourceFile } = context;
/**
@ -720,7 +720,7 @@ namespace ts.codefix {
* become "ns.foo"
*/
// Prefix the node instead of it replacing it, because this may be used for import completions and we don't want the text changes to overlap with the identifier being completed.
const changes = ChangeTracker.with(toTextChangesContext(context), tracker =>
const changes = ChangeTracker.with(context, tracker =>
tracker.changeIdentifierToPropertyAccess(sourceFile, namespacePrefix, symbolToken));
return createCodeAction(Diagnostics.Change_0_to_1, [symbolName, `${namespacePrefix}.${symbolName}`], changes, "CodeChange", /*moduleSpecifier*/ undefined);
}

View file

@ -14,24 +14,7 @@ namespace ts {
getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined;
}
export interface RefactorOrCodeFixContext {
host: LanguageServiceHost;
formatContext: ts.formatting.FormatContext;
}
export function getNewLineFromContext(context: RefactorOrCodeFixContext) {
const formatSettings = context.formatContext.options;
return (formatSettings && formatSettings.newLineCharacter) || getNewLineOrDefaultFromHost(context.host);
}
export function toTextChangesContext(context: RefactorOrCodeFixContext): textChanges.TextChangesContext {
return {
newLineCharacter: getNewLineFromContext(context),
formatContext: context.formatContext,
};
}
export interface RefactorContext extends RefactorOrCodeFixContext {
export interface RefactorContext extends textChanges.TextChangesContext {
file: SourceFile;
startPosition: number;
endPosition?: number;

View file

@ -78,7 +78,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc {
return Debug.fail(`!decl || !jsdocType || decl.type: !${decl} || !${jsdocType} || ${decl.type}`);
}
const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context));
const changeTracker = textChanges.ChangeTracker.fromContext(context);
const declarationWithType = addType(decl, transformJSDocType(jsdocType) as TypeNode);
suppressLeadingAndTrailingTrivia(declarationWithType);
changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, declarationWithType);
@ -93,7 +93,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc {
const sourceFile = context.file;
const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false);
const decl = findAncestor(token, isFunctionLikeDeclaration);
const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context));
const changeTracker = textChanges.ChangeTracker.fromContext(context);
const functionWithType = addTypesToFunctionLike(decl);
suppressLeadingAndTrailingTrivia(functionWithType);
changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, functionWithType);

View file

@ -59,7 +59,7 @@ namespace ts.refactor.convertFunctionToES6Class {
}
const ctorDeclaration = ctorSymbol.valueDeclaration;
const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context));
const changeTracker = textChanges.ChangeTracker.fromContext(context);
let precedingNode: Node;
let newClassDeclaration: ClassDeclaration;

View file

@ -74,7 +74,7 @@ namespace ts.refactor {
Debug.assertEqual(actionName, _actionName);
const { file, program } = context;
Debug.assert(isSourceFileJavaScript(file));
const edits = textChanges.ChangeTracker.with(toTextChangesContext(context), changes => {
const edits = textChanges.ChangeTracker.with(context, changes => {
const moduleExportsChangedToDefault = convertFileToEs6Module(file, program.getTypeChecker(), changes, program.getCompilerOptions().target);
if (moduleExportsChangedToDefault) {
for (const importingFile of program.getSourceFiles()) {

View file

@ -806,7 +806,7 @@ namespace ts.refactor.extractSymbol {
);
}
const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context));
const changeTracker = textChanges.ChangeTracker.fromContext(context);
const minInsertionPos = (isReadonlyArray(range.range) ? last(range.range) : range.range).end;
const nodeToInsertBefore = getNodeToInsertFunctionBefore(minInsertionPos, scope);
if (nodeToInsertBefore) {
@ -1011,7 +1011,7 @@ namespace ts.refactor.extractSymbol {
const initializer = transformConstantInitializer(node, substitutions);
suppressLeadingAndTrailingTrivia(initializer);
const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context));
const changeTracker = textChanges.ChangeTracker.fromContext(context);
if (isClassLike(scope)) {
Debug.assert(!isJS); // See CannotExtractToJSClass

View file

@ -54,7 +54,7 @@ namespace ts.refactor.installTypesForPackage {
const newImportClause = createImportClause(name, /*namedBindings*/ undefined);
const newImportStatement = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, newImportClause, moduleSpecifier);
return {
edits: textChanges.ChangeTracker.with(toTextChangesContext(context), t => t.replaceNode(file, importStatement, newImportStatement)),
edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, importStatement, newImportStatement)),
renameFilename: undefined,
renameLocation: undefined,
};

View file

@ -187,10 +187,15 @@ namespace ts.textChanges {
}
export interface TextChangesContext {
newLineCharacter: string;
host: LanguageServiceHost;
formatContext: ts.formatting.FormatContext;
}
export function getNewLineFromContext(context: TextChangesContext) {
const formatSettings = context.formatContext.options;
return (formatSettings && formatSettings.newLineCharacter) || getNewLineOrDefaultFromHost(context.host);
}
export class ChangeTracker {
private readonly changes: Change[] = [];
private readonly newLineCharacter: string;
@ -199,7 +204,7 @@ namespace ts.textChanges {
private readonly nodesInsertedAtClassStarts = createMap<{ sourceFile: SourceFile, cls: ClassLikeDeclaration, members: ClassElement[] }>();
public static fromContext(context: TextChangesContext): ChangeTracker {
return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.formatContext);
return new ChangeTracker(getNewLineFromContext(context) === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.formatContext);
}
public static with(context: TextChangesContext, cb: (tracker: ChangeTracker) => void): FileTextChanges[] {