mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-04-17 01:49:41 -05:00
add support for readonly modifier
This commit is contained in:
@@ -11,6 +11,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
|
||||
interface Info {
|
||||
container: ContainerDeclaration;
|
||||
isStatic: boolean;
|
||||
isReadonly: boolean;
|
||||
type: TypeNode | undefined;
|
||||
declaration: AcceptedDeclaration;
|
||||
fieldName: AcceptedNameType;
|
||||
@@ -41,21 +42,40 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
|
||||
|
||||
const isJS = isSourceFileJavaScript(file);
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
const { isStatic, fieldName, accessorName, type, container, declaration } = fieldInfo;
|
||||
const { isStatic, isReadonly, fieldName, accessorName, type, container, declaration } = fieldInfo;
|
||||
|
||||
suppressLeadingAndTrailingTrivia(fieldName);
|
||||
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
|
||||
? !declaration.modifiers || getModifierFlags(declaration) & ModifierFlags.Private ? getModifiers(isJS, isStatic, SyntaxKind.PublicKeyword) : declaration.modifiers
|
||||
? !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);
|
||||
const setAccessor = generateSetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
|
||||
|
||||
suppressLeadingAndTrailingTrivia(getAccessor);
|
||||
insertAccessor(changeTracker, file, getAccessor, declaration, container);
|
||||
insertAccessor(changeTracker, file, setAccessor, declaration, container);
|
||||
|
||||
if (isReadonly) {
|
||||
// readonly modifier only existed in classLikeDeclaration
|
||||
const constructor = getFirstConstructorWithBody(<ClassLikeDeclaration>container);
|
||||
if (constructor) {
|
||||
updateReadonlyPropertyInitializerStatementConstructor(changeTracker, file, constructor, accessorName, fieldName);
|
||||
}
|
||||
}
|
||||
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;
|
||||
@@ -92,16 +112,15 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
|
||||
function getConvertibleFieldAtPosition(file: SourceFile, startPosition: number): Info | undefined {
|
||||
const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false);
|
||||
const declaration = findAncestor(node.parent, isAcceptedDeclaration);
|
||||
// make sure propertyDeclaration have AccessibilityModifier or Static Modifier
|
||||
const meaning = ModifierFlags.AccessibilityModifier | ModifierFlags.Static;
|
||||
// make sure declaration have AccessibilityModifier or Static Modifier or Readonly Modifier
|
||||
const meaning = ModifierFlags.AccessibilityModifier | ModifierFlags.Static | ModifierFlags.Readonly;
|
||||
if (!declaration || !isConvertableName(declaration.name) || (getModifierFlags(declaration) | meaning) !== meaning) return undefined;
|
||||
|
||||
const fieldName = createPropertyName(getUniqueName(`_${declaration.name.text}`, file.text), declaration.name);
|
||||
const accessorName = createPropertyName(declaration.name.text, declaration.name);
|
||||
suppressLeadingAndTrailingTrivia(fieldName);
|
||||
suppressLeadingAndTrailingTrivia(declaration);
|
||||
return {
|
||||
isStatic: hasStaticModifier(declaration),
|
||||
isReadonly: hasReadonlyModifier(declaration),
|
||||
type: getTypeAnnotationNode(declaration),
|
||||
container: declaration.kind === SyntaxKind.Parameter ? declaration.parent.parent : declaration.parent,
|
||||
declaration,
|
||||
@@ -159,7 +178,6 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
|
||||
declaration.type,
|
||||
declaration.initializer
|
||||
);
|
||||
|
||||
changeTracker.replaceNode(file, declaration, property);
|
||||
}
|
||||
|
||||
@@ -186,4 +204,38 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
|
||||
? changeTracker.insertNodeAtClassStart(file, <ClassLikeDeclaration>container, accessor)
|
||||
: changeTracker.insertNodeAfter(file, declaration, accessor);
|
||||
}
|
||||
|
||||
function updateReadonlyPropertyInitializerStatementConstructor(changeTracker: textChanges.ChangeTracker, file: SourceFile, constructor: ConstructorDeclaration, accessorName: AcceptedNameType, fieldName: AcceptedNameType) {
|
||||
if (constructor.body) {
|
||||
const initializerStatement = find(constructor.body.statements, (stmt =>
|
||||
isExpressionStatement(stmt) &&
|
||||
isAssignmentExpression(stmt.expression) &&
|
||||
stmt.expression.operatorToken.kind === SyntaxKind.EqualsToken &&
|
||||
(isPropertyAccessExpression(stmt.expression.left) || isElementAccessExpression(stmt.expression.left)) &&
|
||||
isThis(stmt.expression.left.expression) &&
|
||||
(isPropertyAccessExpression(stmt.expression.left)
|
||||
? (getNameFromPropertyName(stmt.expression.left.name) === accessorName.text)
|
||||
: (isPropertyName(stmt.expression.left.argumentExpression) && isConvertableName(stmt.expression.left.argumentExpression) && getNameFromPropertyName(stmt.expression.left.argumentExpression) === accessorName.text
|
||||
))
|
||||
));
|
||||
if (initializerStatement) {
|
||||
const initializerLeftHead = (<PropertyAccessExpression | ElementAccessExpression>(<AssignmentExpression<Token<SyntaxKind.EqualsToken>>>(<ExpressionStatement>initializerStatement).expression).left);
|
||||
|
||||
if (isPropertyAccessExpression(initializerLeftHead)) {
|
||||
changeTracker.replaceNode(file, initializerLeftHead, updatePropertyAccess(
|
||||
initializerLeftHead,
|
||||
initializerLeftHead.expression,
|
||||
createIdentifier(fieldName.text)
|
||||
));
|
||||
}
|
||||
else {
|
||||
changeTracker.replaceNode(file, initializerLeftHead, updateElementAccess(
|
||||
initializerLeftHead,
|
||||
initializerLeftHead.expression,
|
||||
createPropertyName(fieldName.text, <AcceptedNameType>initializerLeftHead.argumentExpression)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,15 @@
|
||||
//// }
|
||||
|
||||
goTo.select("a", "b");
|
||||
verify.not.refactorAvailable("Generate 'get' and 'set' accessors");
|
||||
goTo.select("a", "b");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Generate 'get' and 'set' accessors",
|
||||
actionName: "Generate 'get' and 'set' accessors",
|
||||
actionDescription: "Generate 'get' and 'set' accessors",
|
||||
newContent: `class A {
|
||||
private /*RENAME*/_a: string = "foo";
|
||||
public get a(): string {
|
||||
return this._a;
|
||||
}
|
||||
}`,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// class A {
|
||||
//// public readonly /*a*/a/*b*/: number;
|
||||
//// constructor () {
|
||||
//// this.a = 1;
|
||||
//// }
|
||||
//// }
|
||||
|
||||
goTo.select("a", "b");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Generate 'get' and 'set' accessors",
|
||||
actionName: "Generate 'get' and 'set' accessors",
|
||||
actionDescription: "Generate 'get' and 'set' accessors",
|
||||
newContent: `class A {
|
||||
private /*RENAME*/_a: number;
|
||||
public get a(): number {
|
||||
return this._a;
|
||||
}
|
||||
constructor () {
|
||||
this._a = 1;
|
||||
}
|
||||
}`,
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// class A {
|
||||
//// public readonly /*a*/a/*b*/: number;
|
||||
//// constructor () {
|
||||
//// this["a"] = 1;
|
||||
//// }
|
||||
//// }
|
||||
|
||||
goTo.select("a", "b");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Generate 'get' and 'set' accessors",
|
||||
actionName: "Generate 'get' and 'set' accessors",
|
||||
actionDescription: "Generate 'get' and 'set' accessors",
|
||||
newContent: `class A {
|
||||
private /*RENAME*/_a: number;
|
||||
public get a(): number {
|
||||
return this._a;
|
||||
}
|
||||
constructor () {
|
||||
this["_a"] = 1;
|
||||
}
|
||||
}`,
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// class A {
|
||||
//// public readonly /*a*/"a"/*b*/: number;
|
||||
//// constructor () {
|
||||
//// this["a"] = 1;
|
||||
//// }
|
||||
//// }
|
||||
|
||||
goTo.select("a", "b");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Generate 'get' and 'set' accessors",
|
||||
actionName: "Generate 'get' and 'set' accessors",
|
||||
actionDescription: "Generate 'get' and 'set' accessors",
|
||||
newContent: `class A {
|
||||
private /*RENAME*/"_a": number;
|
||||
public get "a"(): number {
|
||||
return this["_a"];
|
||||
}
|
||||
constructor () {
|
||||
this["_a"] = 1;
|
||||
}
|
||||
}`,
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// class A {
|
||||
//// public readonly /*a*/"a-a"/*b*/: number;
|
||||
//// constructor () {
|
||||
//// this["a-a"] = 1;
|
||||
//// }
|
||||
//// }
|
||||
|
||||
goTo.select("a", "b");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Generate 'get' and 'set' accessors",
|
||||
actionName: "Generate 'get' and 'set' accessors",
|
||||
actionDescription: "Generate 'get' and 'set' accessors",
|
||||
newContent: `class A {
|
||||
private /*RENAME*/"_a-a": number;
|
||||
public get "a-a"(): number {
|
||||
return this["_a-a"];
|
||||
}
|
||||
constructor () {
|
||||
this["_a-a"] = 1;
|
||||
}
|
||||
}`,
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// class A {
|
||||
//// public readonly /*a*/a/*b*/: number;
|
||||
//// constructor () {
|
||||
//// if (Math.random()) {
|
||||
//// this.a = 1; // only top level assignment
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
|
||||
goTo.select("a", "b");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Generate 'get' and 'set' accessors",
|
||||
actionName: "Generate 'get' and 'set' accessors",
|
||||
actionDescription: "Generate 'get' and 'set' accessors",
|
||||
newContent: `class A {
|
||||
private /*RENAME*/_a: number;
|
||||
public get a(): number {
|
||||
return this._a;
|
||||
}
|
||||
constructor () {
|
||||
if (Math.random()) {
|
||||
this.a = 1; // only top level assignment
|
||||
}
|
||||
}
|
||||
}`,
|
||||
});
|
||||
Reference in New Issue
Block a user