diff --git a/src/services/codefixes/fixPropertyOverrideAccessor.ts b/src/services/codefixes/fixPropertyOverrideAccessor.ts index ac4b292b5bf..4729042bedf 100644 --- a/src/services/codefixes/fixPropertyOverrideAccessor.ts +++ b/src/services/codefixes/fixPropertyOverrideAccessor.ts @@ -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); + } +} diff --git a/src/services/codefixes/generateAccessors.ts b/src/services/codefixes/generateAccessors.ts index 067c47923db..b234c490f77 100644 --- a/src/services/codefixes/generateAccessors.ts +++ b/src/services/codefixes/generateAccessors.ts @@ -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(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 ? (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 | undefined { - const modifiers = append( - !isJS ? [createToken(accessModifier) as Token | Token] : 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: (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, 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(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 ? (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 | undefined { + const modifiers = append( + !isJS ? [createToken(accessModifier) as Token | Token] : 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: (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, 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; +} diff --git a/src/services/refactors/generateGetAccessorAndSetAccessor.ts b/src/services/refactors/generateGetAccessorAndSetAccessor.ts index 42108db7436..e942305f6b9 100644 --- a/src/services/refactors/generateGetAccessorAndSetAccessor.ts +++ b/src/services/refactors/generateGetAccessorAndSetAccessor.ts @@ -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 + } + ] + }]; + } + }); }