OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library services.correction.change; | |
6 | |
7 import 'package:analysis_services/constants.dart'; | |
8 import 'package:analysis_services/json.dart'; | |
9 import 'package:analyzer/src/generated/source.dart'; | |
10 | |
11 | |
12 /** | |
13 * A description of a single change to one or more files. | |
14 */ | |
15 class Change implements HasToJson { | |
16 /** | |
17 * A textual description of the change to be applied. | |
18 */ | |
19 final String message; | |
20 | |
21 /** | |
22 * A list of the [FileEdit]s used to effect the change. | |
23 */ | |
24 final List<FileEdit> fileEdits = <FileEdit>[]; | |
25 | |
26 /** | |
27 * A list of the [LinkedEditGroup]s in the change. | |
28 */ | |
29 final List<LinkedEditGroup> linkedEditGroups = <LinkedEditGroup>[]; | |
30 | |
31 /** | |
32 * The position that should be selected after the edits have been applied. | |
33 */ | |
34 Position selection; | |
35 | |
36 Change(this.message); | |
37 | |
38 /** | |
39 * Adds [edit] to the [FileEdit] for the given [file]. | |
40 */ | |
41 void addEdit(String file, Edit edit) { | |
42 FileEdit fileEdit = getFileEdit(file); | |
43 if (fileEdit == null) { | |
44 fileEdit = new FileEdit(file); | |
45 addFileEdit(fileEdit); | |
46 } | |
47 fileEdit.add(edit); | |
48 } | |
49 | |
50 /** | |
51 * Adds the given [FileEdit]. | |
52 */ | |
53 void addFileEdit(FileEdit edit) { | |
54 fileEdits.add(edit); | |
55 } | |
56 | |
57 /** | |
58 * Adds the given [LinkedEditGroup]. | |
59 */ | |
60 void addLinkedEditGroup(LinkedEditGroup linkedEditGroup) { | |
61 linkedEditGroups.add(linkedEditGroup); | |
62 } | |
63 | |
64 /** | |
65 * Returns the [FileEdit] for the given [file], maybe `null`. | |
66 */ | |
67 FileEdit getFileEdit(String file) { | |
68 for (FileEdit fileEdit in fileEdits) { | |
69 if (fileEdit.file == file) { | |
70 return fileEdit; | |
71 } | |
72 } | |
73 return null; | |
74 } | |
75 | |
76 @override | |
77 Map<String, Object> toJson() { | |
78 Map<String, Object> json = { | |
79 MESSAGE: message, | |
80 EDITS: objectToJson(fileEdits), | |
81 LINKED_EDIT_GROUPS: objectToJson(linkedEditGroups) | |
82 }; | |
83 if (selection != null) { | |
84 json[SELECTION] = selection.toJson(); | |
85 } | |
86 return json; | |
87 } | |
88 | |
89 @override | |
90 String toString() => | |
91 'Change(message=$message, edits=$fileEdits, ' | |
92 'linkedEditGroups=$linkedEditGroups, selection=$selection)'; | |
93 } | |
94 | |
95 | |
96 /** | |
97 * A description of a single change to a single file. | |
98 */ | |
99 class Edit implements HasToJson { | |
100 /** | |
101 * The offset of the region to be modified. | |
102 */ | |
103 final int offset; | |
104 | |
105 /** | |
106 * The length of the region to be modified. | |
107 */ | |
108 final int length; | |
109 | |
110 /** | |
111 * The text that is to replace the specified region in the original text. | |
112 */ | |
113 final String replacement; | |
114 | |
115 /** | |
116 * An identifier that uniquely identifies this source edit from other edits in | |
117 * the same response. This field is omitted unless a containing structure | |
118 * needs to be able to identify the edit for some reason. | |
119 * | |
120 * For example, some refactoring operations can produce edits that might not | |
121 * be appropriate (referred to as potential edits). Such edits will have an id | |
122 * so that they can be referenced. Edits in the same response that do not need | |
123 * to be referenced will not have an id. | |
124 */ | |
125 String id; | |
126 | |
127 Edit(this.offset, this.length, this.replacement); | |
128 | |
129 Edit.range(SourceRange range, String replacement) | |
130 : this(range.offset, range.length, replacement); | |
131 | |
132 /** | |
133 * The offset of a character immediately after the region to be modified. | |
134 */ | |
135 int get end => offset + length; | |
136 | |
137 bool operator ==(other) { | |
138 if (other is Edit) { | |
139 return other.offset == offset && | |
140 other.length == length && | |
141 other.replacement == replacement; | |
142 } | |
143 return false; | |
144 } | |
145 | |
146 /** | |
147 * Get the result of applying the edit to the given [code]. | |
148 */ | |
149 String apply(String code) { | |
150 return code.substring(0, offset) + replacement + code.substring(end); | |
151 } | |
152 | |
153 @override | |
154 Map<String, Object> toJson() { | |
155 return { | |
156 OFFSET: offset, | |
157 LENGTH: length, | |
158 REPLACEMENT: replacement | |
159 }; | |
160 } | |
161 | |
162 @override | |
163 String toString() { | |
164 StringBuffer sb = new StringBuffer(); | |
165 sb.write('Edit(offset='); | |
166 sb.write(offset); | |
167 sb.write(', length='); | |
168 sb.write(length); | |
169 sb.write(', replacement=:>'); | |
170 sb.write(replacement); | |
171 sb.write('<:'); | |
172 if (id != null) { | |
173 sb.write(', id='); | |
174 sb.write(id); | |
175 } | |
176 sb.write(')'); | |
177 return sb.toString(); | |
178 } | |
179 | |
180 /** | |
181 * Get the result of applying a set of [edits] to the given [code]. Edits | |
182 * are applied in the order they appear in [edits]. | |
183 */ | |
184 static String applySequence(String code, Iterable<Edit> edits) { | |
185 edits.forEach((Edit edit) { | |
186 code = edit.apply(code); | |
187 }); | |
188 return code; | |
189 } | |
190 } | |
191 | |
192 | |
193 /** | |
194 * A description of a set of changes to a single file. | |
195 * | |
196 * [Edit]s are added in the order of decreasing offset, so they are easy to | |
197 * apply to the original file content without correcting offsets. | |
198 */ | |
199 class FileEdit implements HasToJson { | |
200 /** | |
201 * The file to be modified. | |
202 */ | |
203 final String file; | |
204 | |
205 /** | |
206 * A list of the [Edit]s used to effect the change. | |
207 */ | |
208 final List<Edit> edits = <Edit>[]; | |
209 | |
210 FileEdit(this.file); | |
211 | |
212 /** | |
213 * Adds the given [Edit] to the list. | |
214 */ | |
215 void add(Edit edit) { | |
216 int index = 0; | |
217 while (index < edits.length && edits[index].offset > edit.offset) { | |
218 index++; | |
219 } | |
220 edits.insert(index, edit); | |
221 } | |
222 | |
223 /** | |
224 * Adds the given [Edit]s. | |
225 */ | |
226 void addAll(Iterable<Edit> edits) { | |
227 edits.forEach(add); | |
228 } | |
229 | |
230 @override | |
231 Map<String, Object> toJson() { | |
232 return { | |
233 FILE: file, | |
234 EDITS: objectToJson(edits) | |
235 }; | |
236 } | |
237 | |
238 @override | |
239 String toString() => "FileEdit(file=$file, edits=$edits)"; | |
240 } | |
241 | |
242 | |
243 /** | |
244 * A group of linked [Position]s in multiple files that are simultaneously | |
245 * modified - if one gets edited, all other positions in a group are edited the | |
246 * same way. All linked positions in a group have the same content. | |
247 */ | |
248 class LinkedEditGroup implements HasToJson { | |
249 final String id; | |
250 int length; | |
251 final List<Position> positions = <Position>[]; | |
252 final List<LinkedEditSuggestion> suggestions = <LinkedEditSuggestion>[]; | |
253 | |
254 LinkedEditGroup(this.id); | |
255 | |
256 void addPosition(Position position, int length) { | |
257 positions.add(position); | |
258 this.length = length; | |
259 } | |
260 | |
261 void addSuggestion(LinkedEditSuggestion suggestion) { | |
262 suggestions.add(suggestion); | |
263 } | |
264 | |
265 @override | |
266 Map<String, Object> toJson() { | |
267 return { | |
268 ID: id, | |
269 LENGTH: length, | |
270 POSITIONS: objectToJson(positions), | |
271 SUGGESTIONS: objectToJson(suggestions) | |
272 }; | |
273 } | |
274 | |
275 @override | |
276 String toString() => | |
277 'LinkedEditGroup(id=$id, length=$length, ' | |
278 'positions=$positions, suggestions=$suggestions)'; | |
279 } | |
280 | |
281 | |
282 /** | |
283 * A suggestion of a value that could be used to replace all of the linked edit | |
284 * regions in a [LinkedEditGroup]. | |
285 */ | |
286 class LinkedEditSuggestion implements HasToJson { | |
287 final LinkedEditSuggestionKind kind; | |
288 final String value; | |
289 | |
290 LinkedEditSuggestion(this.kind, this.value); | |
291 | |
292 bool operator ==(other) { | |
293 if (other is LinkedEditSuggestion) { | |
294 return other.kind == kind && other.value == value; | |
295 } | |
296 return false; | |
297 } | |
298 | |
299 @override | |
300 Map<String, Object> toJson() { | |
301 return { | |
302 KIND: kind.name, | |
303 VALUE: value | |
304 }; | |
305 } | |
306 | |
307 @override | |
308 String toString() => '(kind=$kind, value=$value)'; | |
309 } | |
310 | |
311 | |
312 /** | |
313 * An enumeration of the kind of values that can be suggested for a linked edit. | |
314 */ | |
315 class LinkedEditSuggestionKind { | |
316 static const METHOD = const LinkedEditSuggestionKind('METHOD'); | |
317 static const PARAMETER = const LinkedEditSuggestionKind('PARAMETER'); | |
318 static const TYPE = const LinkedEditSuggestionKind('TYPE'); | |
319 static const VARIABLE = const LinkedEditSuggestionKind('VARIABLE'); | |
320 final String name; | |
321 | |
322 const LinkedEditSuggestionKind(this.name); | |
323 | |
324 @override | |
325 String toString() => name; | |
326 } | |
327 | |
328 | |
329 /** | |
330 * A position in a file. | |
331 */ | |
332 class Position implements HasToJson { | |
333 final String file; | |
334 final int offset; | |
335 | |
336 Position(this.file, this.offset); | |
337 | |
338 int get hashCode { | |
339 int hash = file.hashCode; | |
340 hash = hash * 31 + offset; | |
341 return hash; | |
342 } | |
343 | |
344 bool operator ==(other) { | |
345 if (other is Position) { | |
346 return other.file == file && other.offset == offset; | |
347 } | |
348 return false; | |
349 } | |
350 | |
351 @override | |
352 Map<String, Object> toJson() { | |
353 return { | |
354 FILE: file, | |
355 OFFSET: offset | |
356 }; | |
357 } | |
358 | |
359 @override | |
360 String toString() => 'Position(file=$file, offset=$offset)'; | |
361 } | |
OLD | NEW |