/
RBAddParameterRefactoring.class.st
189 lines (171 loc) · 5.36 KB
/
RBAddParameterRefactoring.class.st
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
"
I am a refactoring operations for adding method arguments.
You can modify the method name and add an additional keyword argument and the default value used by senders of the original method. Only one new argument can be added. But you can change the whole method name, as long as the number of argument matches.
For example, for #r:g:b: add another parameter ""a"" the new method is
#r:g:b:a:
or change the whole method to
#setRed:green:blue:alpha:
This refactoring will add a new method with the new argument, remove the old method (for all implementors) and replace every sender of the prior method with the new one, using the specified default argument.
"
Class {
#name : #RBAddParameterRefactoring,
#superclass : #RBChangeMethodNameRefactoring,
#instVars : [
'initializer',
'senders'
],
#category : #'Refactoring-Core-Refactorings'
}
{ #category : #'instance creation' }
RBAddParameterRefactoring class >> addParameterToMethod: aSelector in: aClass newSelector: newSelector initializer: init [
^self new addParameterToMethod: aSelector
in: aClass
newSelector: newSelector
initializer: init
]
{ #category : #'instance creation' }
RBAddParameterRefactoring class >> model: aRBSmalltalk addParameterToMethod: aSelector in: aClass newSelector: newSelector initializer: init [
^(self new)
model: aRBSmalltalk;
addParameterToMethod: aSelector
in: aClass
newSelector: newSelector
initializer: init;
yourself
]
{ #category : #initialization }
RBAddParameterRefactoring >> addParameterToMethod: aSelector in: aClass newSelector: newSel initializer: init [
self
renameMethod: aSelector
in: aClass
to: newSel
permutation: (1 to: newSel numArgs).
initializer := init
]
{ #category : #preconditions }
RBAddParameterRefactoring >> checkSendersAccessTo: name [
(#('self' 'super') includes: name) ifTrue: [ ^ self ].
self senders
detect: [ :each | (self canReferenceVariable: name in: each) not ]
ifFound: [ :violatorClass |
self
refactoringError:
('<1s> doesn''t appear to be defined in <2p>'
expandMacrosWith: name
with: violatorClass) ]
]
{ #category : #preconditions }
RBAddParameterRefactoring >> checkVariableReferencesIn: aParseTree [
| searcher |
searcher := self parseTreeSearcher.
searcher
matches: '`var'
do: [ :aNode :answer |
| name |
name := aNode name.
(aNode whoDefines: name) isNil
ifTrue: [ self checkSendersAccessTo: name ] ].
searcher executeTree: aParseTree
]
{ #category : #private }
RBAddParameterRefactoring >> modifyImplementorParseTree: parseTree in: aClass [
| name newArg allTempVars |
allTempVars := parseTree allDefinedVariables.
name := self safeVariableNameFor: aClass temporaries: allTempVars.
newArg := RBVariableNode named: name.
parseTree
renameSelector: newSelector
andArguments: parseTree arguments , (Array with: newArg)
]
{ #category : #preconditions }
RBAddParameterRefactoring >> myConditions [
^RBCondition withBlock:
[oldSelector numArgs + 1 = newSelector numArgs
ifFalse:
[self refactoringFailure: newSelector printString
, ' doesn''t have the proper number of arguments.'].
self verifyInitializationExpression.
true]
]
{ #category : #private }
RBAddParameterRefactoring >> newSelectorString [
| stream keywords |
stream := WriteStream on: String new.
keywords := newSelector keywords.
1 to: keywords size
do:
[:i |
stream nextPutAll: (keywords at: i).
i == keywords size
ifTrue:
[stream
nextPut: $(;
nextPutAll: initializer;
nextPut: $)]
ifFalse:
[stream
nextPutAll: ' ``@arg';
nextPutAll: i printString].
stream nextPut: $ ].
^stream contents
]
{ #category : #private }
RBAddParameterRefactoring >> parseTreeRewriter [
| rewriteRule oldString newString |
rewriteRule := RBParseTreeRewriter new.
oldString := self buildSelectorString: oldSelector.
newString := self newSelectorString.
rewriteRule replace: '``@object ' , oldString
with: '``@object ' , newString.
^rewriteRule
]
{ #category : #private }
RBAddParameterRefactoring >> safeVariableNameFor: aClass temporaries: allTempVars [
| baseString i newString |
newString := baseString := 'anObject'.
i := 0.
[(allTempVars includes: newString)
or: [aClass definesInstanceVariable: newString]]
whileTrue:
[i := i + 1.
newString := baseString , i printString].
^newString
]
{ #category : #private }
RBAddParameterRefactoring >> senders [
senders isNil
ifTrue:
[senders := Set new.
self model allReferencesTo: oldSelector
do: [:each | senders add: each modelClass]].
^senders
]
{ #category : #printing }
RBAddParameterRefactoring >> storeOn: aStream [
aStream nextPut: $(.
self class storeOn: aStream.
aStream
nextPutAll: ' addParameterToMethod: #';
nextPutAll: oldSelector;
nextPutAll: ' in: '.
class storeOn: aStream.
aStream
nextPutAll: ' newSelector: #';
nextPutAll: newSelector;
nextPutAll: ' initializer: ''';
nextPutAll: initializer;
nextPutAll: ''')'
]
{ #category : #preconditions }
RBAddParameterRefactoring >> verifyInitializationExpression [
| tree |
tree := RBParser parseExpression: initializer
onError:
[:msg :index |
self refactoringFailure: 'Illegal initialization code because:.' , msg].
tree isValue
ifFalse:
[self
refactoringFailure: 'The initialization code cannot be a return node or a list of statements'].
self checkVariableReferencesIn: tree
]