Skip to content

Commit 6eacc9c

Browse files
committed
move into new, centrally (?) located file
1 parent 3030cd8 commit 6eacc9c

File tree

4 files changed

+224
-220
lines changed

4 files changed

+224
-220
lines changed

src/services/codefixes/fixPropertyOverrideAccessor.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ namespace ts.codefix {
5353
Debug.fail("fixPropertyOverrideAccessor codefix got unexpected error code " + context.errorCode);
5454
}
5555
const refactorContext = { ...context, file: context.sourceFile, startPosition, endPosition }
56-
// TODO: Maybe just move most of this into a neutral area.
57-
return ts.refactor.generateGetAccessorAndSetAccessor.getEditsForAction(refactorContext, Diagnostics.Generate_get_and_set_accessors.message);
56+
return getEditsForAction(refactorContext, Diagnostics.Generate_get_and_set_accessors.message);
5857
}
5958

6059
// TODO: Stolen from a different codefix, should dedupe it somewhere.
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
type AcceptedDeclaration = ParameterPropertyDeclaration | PropertyDeclaration | PropertyAssignment;
4+
type AcceptedNameType = Identifier | StringLiteral;
5+
type ContainerDeclaration = ClassLikeDeclaration | ObjectLiteralExpression;
6+
7+
interface Info {
8+
readonly container: ContainerDeclaration;
9+
readonly isStatic: boolean;
10+
readonly isReadonly: boolean;
11+
readonly type: TypeNode | undefined;
12+
readonly declaration: AcceptedDeclaration;
13+
readonly fieldName: AcceptedNameType;
14+
readonly accessorName: AcceptedNameType;
15+
readonly originalName: string;
16+
readonly renameAccessor: boolean;
17+
}
18+
19+
// TODO: Use a general type instead of Refactor* types
20+
// TODO: Rename this to show that it generates accessor
21+
export function getEditsForAction(context: RefactorContext, _actionName: string): RefactorEditInfo | undefined {
22+
const { file } = context;
23+
24+
const fieldInfo = getConvertibleFieldAtPosition(context);
25+
if (!fieldInfo) return undefined;
26+
27+
const isJS = isSourceFileJS(file);
28+
const changeTracker = textChanges.ChangeTracker.fromContext(context);
29+
const { isStatic, isReadonly, fieldName, accessorName, originalName, type, container, declaration, renameAccessor } = fieldInfo;
30+
31+
suppressLeadingAndTrailingTrivia(fieldName);
32+
suppressLeadingAndTrailingTrivia(accessorName);
33+
suppressLeadingAndTrailingTrivia(declaration);
34+
suppressLeadingAndTrailingTrivia(container);
35+
36+
const isInClassLike = isClassLike(container);
37+
// avoid Readonly modifier because it will convert to get accessor
38+
const modifierFlags = getModifierFlags(declaration) & ~ModifierFlags.Readonly;
39+
const accessorModifiers = isInClassLike
40+
? !modifierFlags || modifierFlags & ModifierFlags.Private
41+
? getModifiers(isJS, isStatic, SyntaxKind.PublicKeyword)
42+
: createNodeArray(createModifiersFromModifierFlags(modifierFlags))
43+
: undefined;
44+
const fieldModifiers = isInClassLike ? getModifiers(isJS, isStatic, SyntaxKind.PrivateKeyword) : undefined;
45+
46+
updateFieldDeclaration(changeTracker, file, declaration, fieldName, fieldModifiers);
47+
48+
const getAccessor = generateGetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
49+
suppressLeadingAndTrailingTrivia(getAccessor);
50+
insertAccessor(changeTracker, file, getAccessor, declaration, container);
51+
52+
if (isReadonly) {
53+
// readonly modifier only existed in classLikeDeclaration
54+
const constructor = getFirstConstructorWithBody(<ClassLikeDeclaration>container);
55+
if (constructor) {
56+
updateReadonlyPropertyInitializerStatementConstructor(changeTracker, file, constructor, fieldName.text, originalName);
57+
}
58+
}
59+
else {
60+
const setAccessor = generateSetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
61+
suppressLeadingAndTrailingTrivia(setAccessor);
62+
insertAccessor(changeTracker, file, setAccessor, declaration, container);
63+
}
64+
65+
const edits = changeTracker.getChanges();
66+
const renameFilename = file.fileName;
67+
68+
const nameNeedRename = renameAccessor ? accessorName : fieldName;
69+
const renameLocationOffset = isIdentifier(nameNeedRename) ? 0 : -1;
70+
const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, nameNeedRename.text, /*preferLastLocation*/ isParameter(declaration));
71+
return { renameFilename, renameLocation, edits };
72+
}
73+
74+
function isConvertibleName(name: DeclarationName): name is AcceptedNameType {
75+
return isIdentifier(name) || isStringLiteral(name);
76+
}
77+
78+
function isAcceptedDeclaration(node: Node): node is AcceptedDeclaration {
79+
return isParameterPropertyDeclaration(node, node.parent) || isPropertyDeclaration(node) || isPropertyAssignment(node);
80+
}
81+
82+
function createPropertyName(name: string, originalName: AcceptedNameType) {
83+
return isIdentifier(originalName) ? createIdentifier(name) : createLiteral(name);
84+
}
85+
86+
function createAccessorAccessExpression(fieldName: AcceptedNameType, isStatic: boolean, container: ContainerDeclaration) {
87+
const leftHead = isStatic ? (<ClassLikeDeclaration>container).name! : createThis(); // TODO: GH#18217
88+
return isIdentifier(fieldName) ? createPropertyAccess(leftHead, fieldName) : createElementAccess(leftHead, createLiteral(fieldName));
89+
}
90+
91+
function getModifiers(isJS: boolean, isStatic: boolean, accessModifier: SyntaxKind.PublicKeyword | SyntaxKind.PrivateKeyword): NodeArray<Modifier> | undefined {
92+
const modifiers = append<Modifier>(
93+
!isJS ? [createToken(accessModifier) as Token<SyntaxKind.PublicKeyword> | Token<SyntaxKind.PrivateKeyword>] : undefined,
94+
isStatic ? createToken(SyntaxKind.StaticKeyword) : undefined
95+
);
96+
return modifiers && createNodeArray(modifiers);
97+
}
98+
99+
export function getConvertibleFieldAtPosition(context: RefactorContext): Info | undefined {
100+
const { file, startPosition, endPosition } = context;
101+
102+
const node = getTokenAtPosition(file, startPosition);
103+
const declaration = findAncestor(node.parent, isAcceptedDeclaration);
104+
// make sure declaration have AccessibilityModifier or Static Modifier or Readonly Modifier
105+
const meaning = ModifierFlags.AccessibilityModifier | ModifierFlags.Static | ModifierFlags.Readonly;
106+
if (!declaration || !nodeOverlapsWithStartEnd(declaration.name, file, startPosition, endPosition!) // TODO: GH#18217
107+
|| !isConvertibleName(declaration.name) || (getModifierFlags(declaration) | meaning) !== meaning) return undefined;
108+
109+
const name = declaration.name.text;
110+
const startWithUnderscore = startsWithUnderscore(name);
111+
const fieldName = createPropertyName(startWithUnderscore ? name : getUniqueName(`_${name}`, file), declaration.name);
112+
const accessorName = createPropertyName(startWithUnderscore ? getUniqueName(name.substring(1), file) : name, declaration.name);
113+
return {
114+
isStatic: hasStaticModifier(declaration),
115+
isReadonly: hasReadonlyModifier(declaration),
116+
type: getTypeAnnotationNode(declaration),
117+
container: declaration.kind === SyntaxKind.Parameter ? declaration.parent.parent : declaration.parent,
118+
originalName: (<AcceptedNameType>declaration.name).text,
119+
declaration,
120+
fieldName,
121+
accessorName,
122+
renameAccessor: startWithUnderscore
123+
};
124+
}
125+
126+
function generateGetAccessor(fieldName: AcceptedNameType, accessorName: AcceptedNameType, type: TypeNode | undefined, modifiers: ModifiersArray | undefined, isStatic: boolean, container: ContainerDeclaration) {
127+
return createGetAccessor(
128+
/*decorators*/ undefined,
129+
modifiers,
130+
accessorName,
131+
/*parameters*/ undefined!, // TODO: GH#18217
132+
type,
133+
createBlock([
134+
createReturn(
135+
createAccessorAccessExpression(fieldName, isStatic, container)
136+
)
137+
], /*multiLine*/ true)
138+
);
139+
}
140+
141+
function generateSetAccessor(fieldName: AcceptedNameType, accessorName: AcceptedNameType, type: TypeNode | undefined, modifiers: ModifiersArray | undefined, isStatic: boolean, container: ContainerDeclaration) {
142+
return createSetAccessor(
143+
/*decorators*/ undefined,
144+
modifiers,
145+
accessorName,
146+
[createParameter(
147+
/*decorators*/ undefined,
148+
/*modifiers*/ undefined,
149+
/*dotDotDotToken*/ undefined,
150+
createIdentifier("value"),
151+
/*questionToken*/ undefined,
152+
type
153+
)],
154+
createBlock([
155+
createStatement(
156+
createAssignment(
157+
createAccessorAccessExpression(fieldName, isStatic, container),
158+
createIdentifier("value")
159+
)
160+
)
161+
], /*multiLine*/ true)
162+
);
163+
}
164+
165+
function updatePropertyDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: PropertyDeclaration, fieldName: AcceptedNameType, modifiers: ModifiersArray | undefined) {
166+
const property = updateProperty(
167+
declaration,
168+
declaration.decorators,
169+
modifiers,
170+
fieldName,
171+
declaration.questionToken || declaration.exclamationToken,
172+
declaration.type,
173+
declaration.initializer
174+
);
175+
changeTracker.replaceNode(file, declaration, property);
176+
}
177+
178+
function updatePropertyAssignmentDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: PropertyAssignment, fieldName: AcceptedNameType) {
179+
const assignment = updatePropertyAssignment(declaration, fieldName, declaration.initializer);
180+
changeTracker.replacePropertyAssignment(file, declaration, assignment);
181+
}
182+
183+
function updateFieldDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: AcceptedDeclaration, fieldName: AcceptedNameType, modifiers: ModifiersArray | undefined) {
184+
if (isPropertyDeclaration(declaration)) {
185+
updatePropertyDeclaration(changeTracker, file, declaration, fieldName, modifiers);
186+
}
187+
else if (isPropertyAssignment(declaration)) {
188+
updatePropertyAssignmentDeclaration(changeTracker, file, declaration, fieldName);
189+
}
190+
else {
191+
changeTracker.replaceNode(file, declaration,
192+
updateParameter(declaration, declaration.decorators, modifiers, declaration.dotDotDotToken, cast(fieldName, isIdentifier), declaration.questionToken, declaration.type, declaration.initializer));
193+
}
194+
}
195+
196+
function insertAccessor(changeTracker: textChanges.ChangeTracker, file: SourceFile, accessor: AccessorDeclaration, declaration: AcceptedDeclaration, container: ContainerDeclaration) {
197+
isParameterPropertyDeclaration(declaration, declaration.parent) ? changeTracker.insertNodeAtClassStart(file, <ClassLikeDeclaration>container, accessor) :
198+
isPropertyAssignment(declaration) ? changeTracker.insertNodeAfterComma(file, declaration, accessor) :
199+
changeTracker.insertNodeAfter(file, declaration, accessor);
200+
}
201+
202+
function updateReadonlyPropertyInitializerStatementConstructor(changeTracker: textChanges.ChangeTracker, file: SourceFile, constructor: ConstructorDeclaration, fieldName: string, originalName: string) {
203+
if (!constructor.body) return;
204+
constructor.body.forEachChild(function recur(node) {
205+
if (isElementAccessExpression(node) &&
206+
node.expression.kind === SyntaxKind.ThisKeyword &&
207+
isStringLiteral(node.argumentExpression) &&
208+
node.argumentExpression.text === originalName &&
209+
isWriteAccess(node)) {
210+
changeTracker.replaceNode(file, node.argumentExpression, createStringLiteral(fieldName));
211+
}
212+
if (isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword && node.name.text === originalName && isWriteAccess(node)) {
213+
changeTracker.replaceNode(file, node.name, createIdentifier(fieldName));
214+
}
215+
if (!isFunctionLike(node) && !isClassLike(node)) {
216+
node.forEachChild(recur);
217+
}
218+
});
219+
}
220+
}

0 commit comments

Comments
 (0)