| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| "category": "Message", | ||
| "code": 95017 | ||
| }, | ||
| "Convert to getter and setter": { |
There was a problem hiding this comment.
"Generate 'get' and 'set' accessors."
You can potentially keep it as "getter" and "setter", but I don't know how well that localizes. I think the key thing I have in mind is that you should have the text "generate" in there somehow.
Sorry, something went wrong.
| function createGetter (fieldName: string, name: string, type: TypeNode) { | ||
| return createGetAccessor( | ||
| /*decorators*/ undefined, | ||
| [createToken(SyntaxKind.PublicKeyword)], |
There was a problem hiding this comment.
It'd be nice if we could detect whether other public members use modifiers instead of injecting one.
Sorry, something went wrong.
| function getConvertibleFieldAtPosition(file: SourceFile, startPosition: number): Info | undefined { | ||
| const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false); | ||
| const propertyDeclaration = findAncestor(node.parent, isPropertyDeclaration); | ||
| if (!(propertyDeclaration && propertyDeclaration.name.getText().charAt(0) === "_" && hasModifier(propertyDeclaration, ModifierFlags.Private))) return undefined; |
There was a problem hiding this comment.
Give a short comment explaining why you want to do this check.
Also, use charCodeAt(0) === CharacterCodes._.
Also, break this into at least two lines and use braces please.
Sorry, something went wrong.
There was a problem hiding this comment.
Also, does this work for computed properties? You probably want to make sure that your propertyDeclaration.name is an identifier. That way you could also use propertyDeclaration.name.text after you use a type-guard.
Sorry, something went wrong.
There was a problem hiding this comment.
I don't like that you have to write private _foo: number; to get this refactor. Wouldn't it make more sense to trigger on public foo: number; and convert it to private _foo: number; public get foo() {} public set foo() {}?
Sorry, something went wrong.
There was a problem hiding this comment.
updated
Sorry, something went wrong.
| function getConvertibleFieldAtPosition(file: SourceFile, startPosition: number): Info | undefined { | ||
| const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false); | ||
| const propertyDeclaration = findAncestor(node.parent, isPropertyDeclaration); | ||
| if (!(propertyDeclaration && propertyDeclaration.name.getText().charAt(0) === "_" && hasModifier(propertyDeclaration, ModifierFlags.Private))) return undefined; |
There was a problem hiding this comment.
Also, does this work for computed properties? You probably want to make sure that your propertyDeclaration.name is an identifier. That way you could also use propertyDeclaration.name.text after you use a type-guard.
Sorry, something went wrong.
|
|
||
| return { | ||
| fieldName: propertyDeclaration.name.getText(), | ||
| name: propertyDeclaration.name.getText().substring(1), |
There was a problem hiding this comment.
You will actually need to make sure that you create a unique name that doesn't exist in the current class. For simplicity, I would just bail out if the property with this name already exists.
Sorry, something went wrong.
| if (!(propertyDeclaration && propertyDeclaration.name.getText().charAt(0) === "_" && hasModifier(propertyDeclaration, ModifierFlags.Private))) return undefined; | ||
|
|
||
| return { | ||
| fieldName: propertyDeclaration.name.getText(), |
There was a problem hiding this comment.
Same deal with propertyDeclaration.name.text here and below.
Sorry, something went wrong.
| actionDescription: "Generate 'get' and 'set' accessors", | ||
| newContent: `class A { | ||
| protected _a: string; | ||
| public get a(): string { |
There was a problem hiding this comment.
If the initial property access is protected then this should be protected. The inner variable should be private
Sorry, something went wrong.
| /// <reference path="extractSymbol.ts" /> | ||
| /// <reference path="installTypesForPackage.ts" /> | ||
| /// <reference path="useDefaultImport.ts" /> | ||
| /// <reference path="ConvertToGetterAndSetter.ts" /> |
There was a problem hiding this comment.
Nit: please keep file casing consistent by renaming the file and this reference to it. (first letter should be lower-case.
Sorry, something went wrong.
There was a problem hiding this comment.
Getting close! Make sure you also have tests for a
Sorry, something went wrong.
| ); | ||
| } | ||
|
|
||
| function updateOriginPropertyDeclaration (propertyDeclaration: PropertyDeclaration, fieldName: string, modifiers: ModifiersArray) { |
There was a problem hiding this comment.
updateoriginalPropertyDeclaration
Sorry, something went wrong.
| function updateOriginPropertyDeclaration (propertyDeclaration: PropertyDeclaration, fieldName: string, modifiers: ModifiersArray) { | ||
| return updateProperty( | ||
| propertyDeclaration, | ||
| /*decorators*/ undefined, |
There was a problem hiding this comment.
Seems problematic to drop decorators here.
Sorry, something went wrong.
| /*decorators*/ undefined, | ||
| modifiers, | ||
| fieldName, | ||
| /*questionOrExclamationToken*/ undefined, |
There was a problem hiding this comment.
Also strange to drop the token.
Sorry, something went wrong.
| }; | ||
| } | ||
|
|
||
| interface Info { originName: string; fieldName: string; accessorName: string; propertyDeclaration: PropertyDeclaration; needUpdateName: boolean; hasModifiers: boolean; needUpdateModifiers: boolean; } |
There was a problem hiding this comment.
originName to originalName
Sorry, something went wrong.
There was a problem hiding this comment.
Put this on multiple lines.
Sorry, something went wrong.
| //// /*a*/public a: string;/*b*/ | ||
| //// } | ||
|
|
||
| goTo.select("a", "b"); |
There was a problem hiding this comment.
I believe we have a range API instead, but I haven't ever written one of these tests yet, so this might be fine?
Sorry, something went wrong.
There was a problem hiding this comment.
i'm not sure...
I copied it from the refactor Convert Es6 Module
Sorry, something went wrong.
|
|
||
| if (find(members, member => needUpdateName ? member.name.getText() === fieldName : member.name.getText() === accessorName)) return undefined; | ||
|
|
||
| const hasModifiers = !!find(members, member => !!member.modifiers); |
There was a problem hiding this comment.
Hmm, I think I initially meant whether a field had a public modifier explicitly
Sorry, something went wrong.
There was a problem hiding this comment.
what if the field is Protected
Sorry, something went wrong.
|
After thinking, I tend to not support readonly, because of the following two case: 1. Initializer is not a Literal (may be side effects)public readonly a : TT = new TT; it cannot be convert as Literal public get a () {
return "Literal";
}
2.before: public readonly a : TT = new TT; after: private _a: TT = new TT;
public get a () {
return this._a
}
it can mod _a in the context of same class or: private readonly _a: TT = new TT;
public get a () {
return this._a
}
it seems do nothing |
Sorry, something went wrong.
| //// } | ||
|
|
||
| goTo.select("a", "b"); | ||
| verify.not.refactorAvailable("Generate 'get' and 'set' accessors"); No newline at end of file |
There was a problem hiding this comment.
i do not see why we would not allow this. you just need to come up with a temporary name that starts with _a, set the rename location to it, and let the user decide what they want to call it. you can just call createUniqueName to create you an identifier with a unique name and use it instead.
Sorry, something went wrong.
| actionDescription: "Generate 'get' and 'set' accessors", | ||
| newContent: `class A { | ||
| _a: string; | ||
| get a(): string { |
There was a problem hiding this comment.
why? i would expect this to be get _a
Sorry, something went wrong.
| if (!fieldInfo) return undefined; | ||
|
|
||
| const changeTracker = textChanges.ChangeTracker.fromContext(context); | ||
| const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options); |
There was a problem hiding this comment.
newLineCharacter should now be available on ChangeTracker.newLineCharacter , no need to recompute it,
Sorry, something went wrong.
| const propertyDeclaration = findAncestor(node.parent, isPropertyDeclaration); | ||
|
|
||
| if (!propertyDeclaration || propertyDeclaration.name.kind !== SyntaxKind.Identifier) return undefined; | ||
| // make sure propertyDeclaration have only AccessibilityModifier |
There was a problem hiding this comment.
what about static? you can have static accessors
Sorry, something went wrong.
|
|
||
| function getConvertibleFieldAtPosition(file: SourceFile, startPosition: number): Info | undefined { | ||
| const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false); | ||
| const propertyDeclaration = findAncestor(node.parent, isPropertyDeclaration); |
There was a problem hiding this comment.
we also need to handle parameter properties. so this can be a property or a parameter declaration.
Sorry, something went wrong.
| // make sure propertyDeclaration have only AccessibilityModifier | ||
| if ((getModifierFlags(propertyDeclaration) | ModifierFlags.AccessibilityModifier) !== ModifierFlags.AccessibilityModifier) return undefined; | ||
|
|
||
| const containerClass = getContainingClass(propertyDeclaration); |
There was a problem hiding this comment.
instead of these two lines, you can just check that isClassLike(propertyDeclaration.parent) or isClassLike(parameterDeclaration.parent.parent)
Sorry, something went wrong.
| const members = getMembersOfDeclaration(containerClass); | ||
| if (!members) return undefined; | ||
|
|
||
| const needUpdateName = propertyDeclaration.name.text.charCodeAt(0) !== CharacterCodes._; |
There was a problem hiding this comment.
do not think you need that. as i noted in my other comment, we should pick a unique name that has a seed of "_" + <property name>, then set the rename location on this name. the user will get the chance to rename it right there and then.
Sorry, something went wrong.
There was a problem hiding this comment.
so in short the name should always be updated.
Sorry, something went wrong.
| const accessorName = needUpdateName ? propertyDeclaration.name.text : propertyDeclaration.name.text.substring(1); | ||
| const fieldName = `_${accessorName}`; | ||
|
|
||
| if (find(members, member => needUpdateName ? member.name.getText() === fieldName : member.name.getText() === accessorName)) return undefined; |
There was a problem hiding this comment.
do not think you need these either.
Sorry, something went wrong.
|
|
||
| const hasModifiers = !!find(members, member => !!member.modifiers); | ||
| const needUpdateModifiers = hasModifiers && (!propertyDeclaration.modifiers || !hasModifier(propertyDeclaration, ModifierFlags.Private)); | ||
| const accessorType = propertyDeclaration.questionToken ? mergeTypeNodeToUnion(propertyDeclaration.type, createKeywordTypeNode(SyntaxKind.UndefinedKeyword)) : propertyDeclaration.type; |
There was a problem hiding this comment.
i would say you do not need to create this type until you need to execute the refactoring. this function is called in both getAvailableActions and getEditsForAction, so ideally we would defer that until we are computing the edits.
Sorry, something went wrong.
| const fieldInfo = getConvertibleFieldAtPosition(file, startPosition); | ||
| if (!fieldInfo) return undefined; | ||
|
|
||
| return [ |
There was a problem hiding this comment.
nit, keep the [ and { on the same line.
Sorry, something went wrong.
| const hasModifiers = !!find(members, member => !!member.modifiers); | ||
| const needUpdateModifiers = hasModifiers && (!propertyDeclaration.modifiers || !hasModifier(propertyDeclaration, ModifierFlags.Private)); | ||
| const accessorType = propertyDeclaration.questionToken ? mergeTypeNodeToUnion(propertyDeclaration.type, createKeywordTypeNode(SyntaxKind.UndefinedKeyword)) : propertyDeclaration.type; | ||
|
|
There was a problem hiding this comment.
also keep in mind that propertyDeclaration.type may not be there. if this is the case, you want to not include a type, nor do you want to merge it with undefined.
also you should add a test for that.
Sorry, something went wrong.
There was a problem hiding this comment.
Looks good, a few comments. also please add a test for applying the refactoring on a .js file
Sorry, something went wrong.
| function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { | ||
| const { file, startPosition } = context; | ||
|
|
||
| const fieldInfo = getConvertibleFieldAtPosition(file, startPosition); |
There was a problem hiding this comment.
Variable can be inlined
Sorry, something went wrong.
| const accessorName = needUpdateName ? propertyDeclaration.name.text : propertyDeclaration.name.text.substring(1); | ||
| const fieldName = `_${accessorName}`; | ||
|
|
||
| if (find(members, member => needUpdateName ? member.name.getText() === fieldName : member.name.getText() === accessorName)) return undefined; |
There was a problem hiding this comment.
Prefer members.some(...) over find when just using as a boolean
Sorry, something went wrong.
There was a problem hiding this comment.
Also, .getText() on every member might be slow since getText() has to do scanning. Might want to do something like function getDeclarationName from services.ts to just get Identifier and StringLiteral-named properties.
Sorry, something went wrong.
There was a problem hiding this comment.
Also, getMemberName(member.name) === (needUpdateName ? fieldName : accessorName)
Sorry, something went wrong.
There was a problem hiding this comment.
Actually, can avoid looping over members and instead check for checker.getPropertyOfType(checker.getTypeAtLocation(containerClass.name), needUpdateName ? fieldName : accessorName) which avoids conflicts with supertypes too.
Sorry, something went wrong.
| const containerClass = getContainingClass(propertyDeclaration); | ||
| if (!containerClass) return undefined; | ||
|
|
||
| const members = getMembersOfDeclaration(containerClass); |
There was a problem hiding this comment.
Isn't this just containerClass.members?
Sorry, something went wrong.
| const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options); | ||
|
|
||
| const { fieldName, accessorName, accessorType, propertyDeclaration, needUpdateName, hasModifiers, needUpdateModifiers } = fieldInfo; | ||
| const accessorModifiers = hasModifiers ? ( |
There was a problem hiding this comment.
hasModifiers currently is true if any member on the class has modifiers. Instead this could just preserve whatever modifier was there before on the particular member without looking at other members. I.e., public x: number becomes public get x() { ... } public set x() { ... } and x: number becomes get x() { ... } set x() { ... }, making hasModifiers unnecessary.
Sorry, something went wrong.
| const changeTracker = textChanges.ChangeTracker.fromContext(context); | ||
| const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options); | ||
|
|
||
| const { fieldName, accessorName, accessorType, propertyDeclaration, needUpdateName, hasModifiers, needUpdateModifiers } = fieldInfo; |
There was a problem hiding this comment.
needUpdateName and needUpdateModifiers aren't used separately, so I would combine them to one property.
Sorry, something went wrong.
| const fieldInfo = getConvertibleFieldAtPosition(file, startPosition); | ||
| if (!fieldInfo) return undefined; | ||
|
|
||
| const changeTracker = textChanges.ChangeTracker.fromContext(context); |
There was a problem hiding this comment.
Nit: would move this closer to its use.
Sorry, something went wrong.
| const modifiers = hasModifiers ? createNodeArray([createToken(SyntaxKind.PrivateKeyword)]) : undefined; | ||
| if (needUpdateName || needUpdateModifiers) { | ||
| changeTracker.replaceNode(file, propertyDeclaration, updateoriginalPropertyDeclaration(propertyDeclaration, fieldName, modifiers), { | ||
| suffix: newLineCharacter |
There was a problem hiding this comment.
With the latest changes to ChangeTracker it shouldn't be necessary to explicitly specify options. If it is, file an issue and we can fix it later after this PR is merged.
Sorry, something went wrong.
| name, | ||
| [createParameter( | ||
| /*decorators*/ undefined, | ||
| /*modifies*/ undefined, |
There was a problem hiding this comment.
*modifiers
Sorry, something went wrong.
| const accessorType = propertyDeclaration.questionToken ? mergeTypeNodeToUnion(propertyDeclaration.type, createKeywordTypeNode(SyntaxKind.UndefinedKeyword)) : propertyDeclaration.type; | ||
|
|
||
| return { | ||
| originalName: propertyDeclaration.name.text, |
There was a problem hiding this comment.
Might want to make const originalName early since it's used in 3 other places.
Sorry, something went wrong.
|
Mohamed Hegazy (@mhegazy) |
Sorry, something went wrong.
we do allow all "standard-track" features in JS: |
Sorry, something went wrong.
|
Wenlu Wang (@Kingwl) i think we should support readonly as well.. no reason not to.. class C {
readonly x = 0;
}should convert to class C {
private _x = 0;
get x() {
return this._x;
}
}I think we should do that in a separate PR though, since there is some complications with constructor access here.. you want to replace all references to the readonly property in the constructor with the new one that was created and not the getter/setter. i.e. class C {
readonly x: number;
constructor() {
this.x = 0;
}
}should become: class C {
private _x: number;
get x() {
return this._x;
}
constructor () {
this._x = 0; // _x and not x here
}
} |
Sorry, something went wrong.
|
gave it a quick test, found two issues..
/** class comment */
class C {
// Field commment
x = 22;
}as i said before, we should look into the readonly case, but i would do that in a separate PR. if you can get these two issues fixed, we should be good to go on this PR. |
Sorry, something went wrong.
|
Sure, but it might be late. I'm enjoying my holiday in Disney(three days)😁 |
Sorry, something went wrong.
|
no worries. take your time. and have a great vacation °o° |
Sorry, something went wrong.
|
ahhhh, could refactor apply withPropertyAssign ? |
Sorry, something went wrong.
i do not see why not. good point. |
Sorry, something went wrong.
| return { prefix: ", " }; | ||
| } | ||
| else if (isPropertyAssignment(node)) { | ||
| return { suffix: "," + this.newLineCharacter } |
There was a problem hiding this comment.
Mohamed Hegazy (@mhegazy) some thing need help
in this case
how should i add the , after every props
if i update the ObjectLiteralExpression, the original PropertyAssignment will overship with this change
Sorry, something went wrong.
| function getPropertyAssignmentDeclarationInfo(propertyAssignment: PropertyAssignment): DeclarationInfo | undefined { | ||
| return { | ||
| isStatic: false, | ||
| type: undefined, |
There was a problem hiding this comment.
what should i do with this type declaration,
should i get the type with typeChecker?
Sorry, something went wrong.
There was a problem hiding this comment.
no, you are fine. the type will be inferred from the return type of the getter.
Sorry, something went wrong.
|
|
||
| function getFieldModifiers(isStatic: boolean): NodeArray<Modifier> { | ||
| function getFieldModifiers(isJS: boolean, isStatic: boolean, isClassLike: boolean): NodeArray<Modifier> | undefined { | ||
| if (isJS || !isClassLike) return undefined; |
There was a problem hiding this comment.
you still want to handel the static modifier in the case of js. i would jsut make it
return createNodeArray(
append<Modifier>(isJS ? [] : [createToken(SyntaxKind.PrivateKeyword)],
isStatic ? createToken(SyntaxKind.StaticKeyword) : undefined));
Sorry, something went wrong.
|
Andy (Andrewkraft) (@Andy-MS) any more comments? |
Sorry, something went wrong.
|
thanks Wenlu Wang (@Kingwl)!. wanna get the readonly support in next? |
Sorry, something went wrong.
|
sure 😅 |
Sorry, something went wrong.
|
wait for pr been merged which Andy (Andrewkraft) (@Andy-MS) has send and thanks for the review 😬 |
Sorry, something went wrong.
|
thanks all for Mohamed Hegazy (@mhegazy) Andy (Andrewkraft) (@Andy-MS) 's review and improve |
Sorry, something went wrong.
|
Go for it. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Fixes #12417