Minor cleanup

1. Fix lint.
2. Make code easier to read.
3. Turns some asserts into bails instead.
This commit is contained in:
Nathan Shively-Sanders
2020-04-10 09:55:36 -07:00
parent 3428e275e6
commit 4d541d2b74
3 changed files with 307 additions and 307 deletions
@@ -1,59 +1,56 @@
/* @internal */
namespace ts.codefix {
const errorCodes = [
Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property.code,
Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor.code,
];
const fixId = "fixPropertyOverrideAccessor";
registerCodeFix({
errorCodes,
getCodeActions(context) {
const r = doChange(context);
if (r) {
return [createCodeFixAction(fixId, r.edits, Diagnostics.Generate_get_and_set_accessors, fixId, Diagnostics.Generate_get_and_set_accessors_for_all_overriding_properties)];
}
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
const codeFixContext = { ...context, span: { start: diag.start, length: diag.length }, errorCode: diag.code };
const r = doChange(codeFixContext)
if (r) {
for (const e of r.edits) {
changes.pushRaw(context.sourceFile, e)
}
}
}),
});
function doChange(context: CodeFixContext) {
let startPosition: number
let endPosition: number
if (context.errorCode === Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property.code) {
startPosition = context.span.start
endPosition = context.span.start + context.span.length
}
else if (context.errorCode === Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor.code) {
// TODO: A lot of these should be bails instead of asserts
const checker = context.program.getTypeChecker()
const node = getTokenAtPosition(context.sourceFile, context.span.start).parent;
Debug.assert(isAccessor(node), "it wasn't an accessor");
const name = unescapeLeadingUnderscores(getTextOfPropertyName(node.name))
const containingClass = node.parent;
Debug.assert(isClassLike(containingClass), "it wasn't classlike")
const bases = getAllSupers(containingClass, checker)
const base = singleOrUndefined(bases)
Debug.assert(!!base, "Porbably more than one super:" + bases.length)
const baseType = checker.getTypeAtLocation(base)
const baseProp = checker.getPropertyOfType(baseType, name)
Debug.assert(!!baseProp, "Couldn't find base property for " + name)
startPosition = baseProp.valueDeclaration.pos
endPosition = baseProp.valueDeclaration.end
}
else {
Debug.fail("fixPropertyOverrideAccessor codefix got unexpected error code " + context.errorCode);
}
const refactorContext = { ...context, file: context.sourceFile, startPosition, endPosition }
return getEditsForAction(refactorContext, Diagnostics.Generate_get_and_set_accessors.message);
}
}
/* @internal */
namespace ts.codefix {
const errorCodes = [
Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property.code,
Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor.code,
];
const fixId = "fixPropertyOverrideAccessor";
registerCodeFix({
errorCodes,
getCodeActions(context) {
const edits = doChange(context.sourceFile, context.span.start, context.span.length, context.errorCode, context);
if (edits) {
return [createCodeFixAction(fixId, edits, Diagnostics.Generate_get_and_set_accessors, fixId, Diagnostics.Generate_get_and_set_accessors_for_all_overriding_properties)];
}
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
const edits = doChange(diag.file, diag.start, diag.length, diag.code, context);
if (edits) {
for (const edit of edits) {
changes.pushRaw(context.sourceFile, edit);
}
}
}),
});
function doChange(file: SourceFile, start: number, length: number, code: number, context: CodeFixContext | CodeFixAllContext) {
let startPosition: number;
let endPosition: number;
if (code === Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property.code) {
startPosition = start;
endPosition = start + length;
}
else if (code === Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor.code) {
const checker = context.program.getTypeChecker();
const node = getTokenAtPosition(file, start).parent;
Debug.assert(isAccessor(node), "error span of fixPropertyOverrideAccessor should only be on an accessor");
const containingClass = node.parent;
Debug.assert(isClassLike(containingClass), "erroneous accessors should only be inside classes");
const base = singleOrUndefined(getAllSupers(containingClass, checker));
if (!base) return [];
const name = unescapeLeadingUnderscores(getTextOfPropertyName(node.name));
const baseProp = checker.getPropertyOfType(checker.getTypeAtLocation(base), name);
if (!baseProp || !baseProp.valueDeclaration) return [];
startPosition = baseProp.valueDeclaration.pos;
endPosition = baseProp.valueDeclaration.end;
}
else {
Debug.fail("fixPropertyOverrideAccessor codefix got unexpected error code " + code);
}
return generateAccessorFromProperty(file, startPosition, endPosition, context, Diagnostics.Generate_get_and_set_accessors.message);
}
}
+222 -234
View File
@@ -1,234 +1,222 @@
/* @internal */
namespace ts.codefix {
type AcceptedDeclaration = ParameterPropertyDeclaration | PropertyDeclaration | PropertyAssignment;
type AcceptedNameType = Identifier | StringLiteral;
type ContainerDeclaration = ClassLikeDeclaration | ObjectLiteralExpression;
interface Info {
readonly container: ContainerDeclaration;
readonly isStatic: boolean;
readonly isReadonly: boolean;
readonly type: TypeNode | undefined;
readonly declaration: AcceptedDeclaration;
readonly fieldName: AcceptedNameType;
readonly accessorName: AcceptedNameType;
readonly originalName: string;
readonly renameAccessor: boolean;
}
// TODO: Use a general type instead of Refactor* types
// TODO: Rename this to show that it generates accessors
export function getEditsForAction(context: RefactorContext, _actionName: string): RefactorEditInfo | undefined {
const { file } = context;
const fieldInfo = getConvertibleFieldAtPosition(context);
if (!fieldInfo) return undefined;
const isJS = isSourceFileJS(file);
const changeTracker = textChanges.ChangeTracker.fromContext(context);
const { isStatic, isReadonly, fieldName, accessorName, originalName, type, container, declaration, renameAccessor } = fieldInfo;
suppressLeadingAndTrailingTrivia(fieldName);
suppressLeadingAndTrailingTrivia(accessorName);
suppressLeadingAndTrailingTrivia(declaration);
suppressLeadingAndTrailingTrivia(container);
const isInClassLike = isClassLike(container);
// avoid Readonly modifier because it will convert to get accessor
const modifierFlags = getModifierFlags(declaration) & ~ModifierFlags.Readonly;
const accessorModifiers = isInClassLike
? !modifierFlags || modifierFlags & ModifierFlags.Private
? getModifiers(isJS, isStatic, SyntaxKind.PublicKeyword)
: createNodeArray(createModifiersFromModifierFlags(modifierFlags))
: undefined;
const fieldModifiers = isInClassLike ? getModifiers(isJS, isStatic, SyntaxKind.PrivateKeyword) : undefined;
updateFieldDeclaration(changeTracker, file, declaration, fieldName, fieldModifiers);
const getAccessor = generateGetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
suppressLeadingAndTrailingTrivia(getAccessor);
insertAccessor(changeTracker, file, getAccessor, declaration, container);
if (isReadonly) {
// readonly modifier only existed in classLikeDeclaration
const constructor = getFirstConstructorWithBody(<ClassLikeDeclaration>container);
if (constructor) {
updateReadonlyPropertyInitializerStatementConstructor(changeTracker, file, constructor, fieldName.text, originalName);
}
}
else {
const setAccessor = generateSetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
suppressLeadingAndTrailingTrivia(setAccessor);
insertAccessor(changeTracker, file, setAccessor, declaration, container);
}
const edits = changeTracker.getChanges();
const renameFilename = file.fileName;
const nameNeedRename = renameAccessor ? accessorName : fieldName;
const renameLocationOffset = isIdentifier(nameNeedRename) ? 0 : -1;
const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, nameNeedRename.text, /*preferLastLocation*/ isParameter(declaration));
return { renameFilename, renameLocation, edits };
}
function isConvertibleName(name: DeclarationName): name is AcceptedNameType {
return isIdentifier(name) || isStringLiteral(name);
}
function isAcceptedDeclaration(node: Node): node is AcceptedDeclaration {
return isParameterPropertyDeclaration(node, node.parent) || isPropertyDeclaration(node) || isPropertyAssignment(node);
}
function createPropertyName(name: string, originalName: AcceptedNameType) {
return isIdentifier(originalName) ? createIdentifier(name) : createLiteral(name);
}
function createAccessorAccessExpression(fieldName: AcceptedNameType, isStatic: boolean, container: ContainerDeclaration) {
const leftHead = isStatic ? (<ClassLikeDeclaration>container).name! : createThis(); // TODO: GH#18217
return isIdentifier(fieldName) ? createPropertyAccess(leftHead, fieldName) : createElementAccess(leftHead, createLiteral(fieldName));
}
function getModifiers(isJS: boolean, isStatic: boolean, accessModifier: SyntaxKind.PublicKeyword | SyntaxKind.PrivateKeyword): NodeArray<Modifier> | undefined {
const modifiers = append<Modifier>(
!isJS ? [createToken(accessModifier) as Token<SyntaxKind.PublicKeyword> | Token<SyntaxKind.PrivateKeyword>] : undefined,
isStatic ? createToken(SyntaxKind.StaticKeyword) : undefined
);
return modifiers && createNodeArray(modifiers);
}
export function getConvertibleFieldAtPosition(context: RefactorContext): Info | undefined {
const { file, startPosition, endPosition } = context;
const node = getTokenAtPosition(file, startPosition);
const declaration = findAncestor(node.parent, isAcceptedDeclaration);
// make sure declaration have AccessibilityModifier or Static Modifier or Readonly Modifier
const meaning = ModifierFlags.AccessibilityModifier | ModifierFlags.Static | ModifierFlags.Readonly;
if (!declaration || !nodeOverlapsWithStartEnd(declaration.name, file, startPosition, endPosition!) // TODO: GH#18217
|| !isConvertibleName(declaration.name) || (getModifierFlags(declaration) | meaning) !== meaning) return undefined;
const name = declaration.name.text;
const startWithUnderscore = startsWithUnderscore(name);
const fieldName = createPropertyName(startWithUnderscore ? name : getUniqueName(`_${name}`, file), declaration.name);
const accessorName = createPropertyName(startWithUnderscore ? getUniqueName(name.substring(1), file) : name, declaration.name);
return {
isStatic: hasStaticModifier(declaration),
isReadonly: hasReadonlyModifier(declaration),
type: getTypeAnnotationNode(declaration),
container: declaration.kind === SyntaxKind.Parameter ? declaration.parent.parent : declaration.parent,
originalName: (<AcceptedNameType>declaration.name).text,
declaration,
fieldName,
accessorName,
renameAccessor: startWithUnderscore
};
}
function generateGetAccessor(fieldName: AcceptedNameType, accessorName: AcceptedNameType, type: TypeNode | undefined, modifiers: ModifiersArray | undefined, isStatic: boolean, container: ContainerDeclaration) {
return createGetAccessor(
/*decorators*/ undefined,
modifiers,
accessorName,
/*parameters*/ undefined!, // TODO: GH#18217
type,
createBlock([
createReturn(
createAccessorAccessExpression(fieldName, isStatic, container)
)
], /*multiLine*/ true)
);
}
function generateSetAccessor(fieldName: AcceptedNameType, accessorName: AcceptedNameType, type: TypeNode | undefined, modifiers: ModifiersArray | undefined, isStatic: boolean, container: ContainerDeclaration) {
return createSetAccessor(
/*decorators*/ undefined,
modifiers,
accessorName,
[createParameter(
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*dotDotDotToken*/ undefined,
createIdentifier("value"),
/*questionToken*/ undefined,
type
)],
createBlock([
createStatement(
createAssignment(
createAccessorAccessExpression(fieldName, isStatic, container),
createIdentifier("value")
)
)
], /*multiLine*/ true)
);
}
function updatePropertyDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: PropertyDeclaration, fieldName: AcceptedNameType, modifiers: ModifiersArray | undefined) {
const property = updateProperty(
declaration,
declaration.decorators,
modifiers,
fieldName,
declaration.questionToken || declaration.exclamationToken,
declaration.type,
declaration.initializer
);
changeTracker.replaceNode(file, declaration, property);
}
function updatePropertyAssignmentDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: PropertyAssignment, fieldName: AcceptedNameType) {
const assignment = updatePropertyAssignment(declaration, fieldName, declaration.initializer);
changeTracker.replacePropertyAssignment(file, declaration, assignment);
}
function updateFieldDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: AcceptedDeclaration, fieldName: AcceptedNameType, modifiers: ModifiersArray | undefined) {
if (isPropertyDeclaration(declaration)) {
updatePropertyDeclaration(changeTracker, file, declaration, fieldName, modifiers);
}
else if (isPropertyAssignment(declaration)) {
updatePropertyAssignmentDeclaration(changeTracker, file, declaration, fieldName);
}
else {
changeTracker.replaceNode(file, declaration,
updateParameter(declaration, declaration.decorators, modifiers, declaration.dotDotDotToken, cast(fieldName, isIdentifier), declaration.questionToken, declaration.type, declaration.initializer));
}
}
function insertAccessor(changeTracker: textChanges.ChangeTracker, file: SourceFile, accessor: AccessorDeclaration, declaration: AcceptedDeclaration, container: ContainerDeclaration) {
isParameterPropertyDeclaration(declaration, declaration.parent) ? changeTracker.insertNodeAtClassStart(file, <ClassLikeDeclaration>container, accessor) :
isPropertyAssignment(declaration) ? changeTracker.insertNodeAfterComma(file, declaration, accessor) :
changeTracker.insertNodeAfter(file, declaration, accessor);
}
function updateReadonlyPropertyInitializerStatementConstructor(changeTracker: textChanges.ChangeTracker, file: SourceFile, constructor: ConstructorDeclaration, fieldName: string, originalName: string) {
if (!constructor.body) return;
constructor.body.forEachChild(function recur(node) {
if (isElementAccessExpression(node) &&
node.expression.kind === SyntaxKind.ThisKeyword &&
isStringLiteral(node.argumentExpression) &&
node.argumentExpression.text === originalName &&
isWriteAccess(node)) {
changeTracker.replaceNode(file, node.argumentExpression, createStringLiteral(fieldName));
}
if (isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword && node.name.text === originalName && isWriteAccess(node)) {
changeTracker.replaceNode(file, node.name, createIdentifier(fieldName));
}
if (!isFunctionLike(node) && !isClassLike(node)) {
node.forEachChild(recur);
}
});
}
export function getAllSupers(decl: ClassOrInterface | undefined, checker: TypeChecker): readonly ClassOrInterface[] {
const res: ClassLikeDeclaration[] = [];
while (decl) {
const superElement = getClassExtendsHeritageElement(decl);
const superSymbol = superElement && checker.getSymbolAtLocation(superElement.expression);
const superDecl = superSymbol && find(superSymbol.declarations, isClassLike);
if (superDecl) { res.push(superDecl); }
decl = superDecl;
}
return res;
}
export type ClassOrInterface = ClassLikeDeclaration | InterfaceDeclaration;
}
/* @internal */
namespace ts.codefix {
type AcceptedDeclaration = ParameterPropertyDeclaration | PropertyDeclaration | PropertyAssignment;
type AcceptedNameType = Identifier | StringLiteral;
type ContainerDeclaration = ClassLikeDeclaration | ObjectLiteralExpression;
interface Info {
readonly container: ContainerDeclaration;
readonly isStatic: boolean;
readonly isReadonly: boolean;
readonly type: TypeNode | undefined;
readonly declaration: AcceptedDeclaration;
readonly fieldName: AcceptedNameType;
readonly accessorName: AcceptedNameType;
readonly originalName: string;
readonly renameAccessor: boolean;
}
export function generateAccessorFromProperty(file: SourceFile, start: number, end: number, context: textChanges.TextChangesContext, _actionName: string): FileTextChanges[] | undefined {
const fieldInfo = getAccessorConvertiblePropertyAtPosition(file, start, end);
if (!fieldInfo) return undefined;
const isJS = isSourceFileJS(file);
const changeTracker = textChanges.ChangeTracker.fromContext(context);
const { isStatic, isReadonly, fieldName, accessorName, originalName, type, container, declaration } = fieldInfo;
suppressLeadingAndTrailingTrivia(fieldName);
suppressLeadingAndTrailingTrivia(accessorName);
suppressLeadingAndTrailingTrivia(declaration);
suppressLeadingAndTrailingTrivia(container);
const isInClassLike = isClassLike(container);
// avoid Readonly modifier because it will convert to get accessor
const modifierFlags = getModifierFlags(declaration) & ~ModifierFlags.Readonly;
const accessorModifiers = isInClassLike
? !modifierFlags || modifierFlags & ModifierFlags.Private
? getModifiers(isJS, isStatic, SyntaxKind.PublicKeyword)
: createNodeArray(createModifiersFromModifierFlags(modifierFlags))
: undefined;
const fieldModifiers = isInClassLike ? getModifiers(isJS, isStatic, SyntaxKind.PrivateKeyword) : undefined;
updateFieldDeclaration(changeTracker, file, declaration, fieldName, fieldModifiers);
const getAccessor = generateGetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
suppressLeadingAndTrailingTrivia(getAccessor);
insertAccessor(changeTracker, file, getAccessor, declaration, container);
if (isReadonly) {
// readonly modifier only existed in classLikeDeclaration
const constructor = getFirstConstructorWithBody(<ClassLikeDeclaration>container);
if (constructor) {
updateReadonlyPropertyInitializerStatementConstructor(changeTracker, file, constructor, fieldName.text, originalName);
}
}
else {
const setAccessor = generateSetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
suppressLeadingAndTrailingTrivia(setAccessor);
insertAccessor(changeTracker, file, setAccessor, declaration, container);
}
return changeTracker.getChanges();
}
function isConvertibleName(name: DeclarationName): name is AcceptedNameType {
return isIdentifier(name) || isStringLiteral(name);
}
function isAcceptedDeclaration(node: Node): node is AcceptedDeclaration {
return isParameterPropertyDeclaration(node, node.parent) || isPropertyDeclaration(node) || isPropertyAssignment(node);
}
function createPropertyName(name: string, originalName: AcceptedNameType) {
return isIdentifier(originalName) ? createIdentifier(name) : createLiteral(name);
}
function createAccessorAccessExpression(fieldName: AcceptedNameType, isStatic: boolean, container: ContainerDeclaration) {
const leftHead = isStatic ? (<ClassLikeDeclaration>container).name! : createThis(); // TODO: GH#18217
return isIdentifier(fieldName) ? createPropertyAccess(leftHead, fieldName) : createElementAccess(leftHead, createLiteral(fieldName));
}
function getModifiers(isJS: boolean, isStatic: boolean, accessModifier: SyntaxKind.PublicKeyword | SyntaxKind.PrivateKeyword): NodeArray<Modifier> | undefined {
const modifiers = append<Modifier>(
!isJS ? [createToken(accessModifier) as Token<SyntaxKind.PublicKeyword> | Token<SyntaxKind.PrivateKeyword>] : undefined,
isStatic ? createToken(SyntaxKind.StaticKeyword) : undefined
);
return modifiers && createNodeArray(modifiers);
}
export function getAccessorConvertiblePropertyAtPosition(file: SourceFile, start: number, end: number): Info | undefined {
const node = getTokenAtPosition(file, start);
const declaration = findAncestor(node.parent, isAcceptedDeclaration);
// make sure declaration have AccessibilityModifier or Static Modifier or Readonly Modifier
const meaning = ModifierFlags.AccessibilityModifier | ModifierFlags.Static | ModifierFlags.Readonly;
if (!declaration || !nodeOverlapsWithStartEnd(declaration.name, file, start, end)
|| !isConvertibleName(declaration.name) || (getModifierFlags(declaration) | meaning) !== meaning) return undefined;
const name = declaration.name.text;
const startWithUnderscore = startsWithUnderscore(name);
const fieldName = createPropertyName(startWithUnderscore ? name : getUniqueName(`_${name}`, file), declaration.name);
const accessorName = createPropertyName(startWithUnderscore ? getUniqueName(name.substring(1), file) : name, declaration.name);
return {
isStatic: hasStaticModifier(declaration),
isReadonly: hasReadonlyModifier(declaration),
type: getTypeAnnotationNode(declaration),
container: declaration.kind === SyntaxKind.Parameter ? declaration.parent.parent : declaration.parent,
originalName: (<AcceptedNameType>declaration.name).text,
declaration,
fieldName,
accessorName,
renameAccessor: startWithUnderscore
};
}
function generateGetAccessor(fieldName: AcceptedNameType, accessorName: AcceptedNameType, type: TypeNode | undefined, modifiers: ModifiersArray | undefined, isStatic: boolean, container: ContainerDeclaration) {
return createGetAccessor(
/*decorators*/ undefined,
modifiers,
accessorName,
/*parameters*/ undefined!, // TODO: GH#18217
type,
createBlock([
createReturn(
createAccessorAccessExpression(fieldName, isStatic, container)
)
], /*multiLine*/ true)
);
}
function generateSetAccessor(fieldName: AcceptedNameType, accessorName: AcceptedNameType, type: TypeNode | undefined, modifiers: ModifiersArray | undefined, isStatic: boolean, container: ContainerDeclaration) {
return createSetAccessor(
/*decorators*/ undefined,
modifiers,
accessorName,
[createParameter(
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*dotDotDotToken*/ undefined,
createIdentifier("value"),
/*questionToken*/ undefined,
type
)],
createBlock([
createStatement(
createAssignment(
createAccessorAccessExpression(fieldName, isStatic, container),
createIdentifier("value")
)
)
], /*multiLine*/ true)
);
}
function updatePropertyDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: PropertyDeclaration, fieldName: AcceptedNameType, modifiers: ModifiersArray | undefined) {
const property = updateProperty(
declaration,
declaration.decorators,
modifiers,
fieldName,
declaration.questionToken || declaration.exclamationToken,
declaration.type,
declaration.initializer
);
changeTracker.replaceNode(file, declaration, property);
}
function updatePropertyAssignmentDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: PropertyAssignment, fieldName: AcceptedNameType) {
const assignment = updatePropertyAssignment(declaration, fieldName, declaration.initializer);
changeTracker.replacePropertyAssignment(file, declaration, assignment);
}
function updateFieldDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: AcceptedDeclaration, fieldName: AcceptedNameType, modifiers: ModifiersArray | undefined) {
if (isPropertyDeclaration(declaration)) {
updatePropertyDeclaration(changeTracker, file, declaration, fieldName, modifiers);
}
else if (isPropertyAssignment(declaration)) {
updatePropertyAssignmentDeclaration(changeTracker, file, declaration, fieldName);
}
else {
changeTracker.replaceNode(file, declaration,
updateParameter(declaration, declaration.decorators, modifiers, declaration.dotDotDotToken, cast(fieldName, isIdentifier), declaration.questionToken, declaration.type, declaration.initializer));
}
}
function insertAccessor(changeTracker: textChanges.ChangeTracker, file: SourceFile, accessor: AccessorDeclaration, declaration: AcceptedDeclaration, container: ContainerDeclaration) {
isParameterPropertyDeclaration(declaration, declaration.parent) ? changeTracker.insertNodeAtClassStart(file, <ClassLikeDeclaration>container, accessor) :
isPropertyAssignment(declaration) ? changeTracker.insertNodeAfterComma(file, declaration, accessor) :
changeTracker.insertNodeAfter(file, declaration, accessor);
}
function updateReadonlyPropertyInitializerStatementConstructor(changeTracker: textChanges.ChangeTracker, file: SourceFile, constructor: ConstructorDeclaration, fieldName: string, originalName: string) {
if (!constructor.body) return;
constructor.body.forEachChild(function recur(node) {
if (isElementAccessExpression(node) &&
node.expression.kind === SyntaxKind.ThisKeyword &&
isStringLiteral(node.argumentExpression) &&
node.argumentExpression.text === originalName &&
isWriteAccess(node)) {
changeTracker.replaceNode(file, node.argumentExpression, createStringLiteral(fieldName));
}
if (isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword && node.name.text === originalName && isWriteAccess(node)) {
changeTracker.replaceNode(file, node.name, createIdentifier(fieldName));
}
if (!isFunctionLike(node) && !isClassLike(node)) {
node.forEachChild(recur);
}
});
}
export function getAllSupers(decl: ClassOrInterface | undefined, checker: TypeChecker): readonly ClassOrInterface[] {
const res: ClassLikeDeclaration[] = [];
while (decl) {
const superElement = getClassExtendsHeritageElement(decl);
const superSymbol = superElement && checker.getSymbolAtLocation(superElement.expression);
const superDecl = superSymbol && find(superSymbol.declarations, isClassLike);
if (superDecl) { res.push(superDecl); }
decl = superDecl;
}
return res;
}
export type ClassOrInterface = ClassLikeDeclaration | InterfaceDeclaration;
}
@@ -2,20 +2,35 @@
namespace ts.refactor.generateGetAccessorAndSetAccessor {
const actionName = "Generate 'get' and 'set' accessors";
const actionDescription = Diagnostics.Generate_get_and_set_accessors.message;
registerRefactor(actionName, { getEditsForAction: codefix.getEditsForAction, getAvailableActions });
registerRefactor(actionName, {
getEditsForAction(context, actionName) {
if (!context.endPosition) return undefined;
const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.startPosition, context.endPosition);
if (!info) return undefined;
const edits = codefix.generateAccessorFromProperty(context.file, context.startPosition, context.endPosition, context, actionName);
if (!edits) return undefined;
function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
if (!codefix.getConvertibleFieldAtPosition(context)) return emptyArray;
const renameFilename = context.file.fileName;
const nameNeedRename = info.renameAccessor ? info.accessorName : info.fieldName;
const renameLocationOffset = isIdentifier(nameNeedRename) ? 0 : -1;
const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, nameNeedRename.text, /*preferLastLocation*/ isParameter(info.declaration));
return [{
name: actionName,
description: actionDescription,
actions: [
{
name: actionName,
description: actionDescription
}
]
}];
}
return { renameFilename, renameLocation, edits };
},
getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
if (!context.endPosition) return emptyArray;
if (!codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.startPosition, context.endPosition)) return emptyArray;
return [{
name: actionName,
description: actionDescription,
actions: [
{
name: actionName,
description: actionDescription
}
]
}];
}
});
}