OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2017, 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 import 'dart:collection'; |
| 6 import 'dart:convert' hide JsonDecoder; |
| 7 |
| 8 import 'package:analyzer_plugin/protocol/generated_protocol.dart'; |
| 9 import 'package:analyzer_plugin/protocol/protocol.dart'; |
| 10 |
| 11 final Map<String, RefactoringKind> REQUEST_ID_REFACTORING_KINDS = |
| 12 new HashMap<String, RefactoringKind>(); |
| 13 |
| 14 /** |
| 15 * Adds the given [sourceEdits] to the list in [sourceFileEdit]. |
| 16 */ |
| 17 void addAllEditsForSource( |
| 18 SourceFileEdit sourceFileEdit, Iterable<SourceEdit> edits) { |
| 19 edits.forEach(sourceFileEdit.add); |
| 20 } |
| 21 |
| 22 /** |
| 23 * Adds the given [sourceEdit] to the list in [sourceFileEdit]. |
| 24 */ |
| 25 void addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit) { |
| 26 List<SourceEdit> edits = sourceFileEdit.edits; |
| 27 int index = 0; |
| 28 while (index < edits.length && edits[index].offset > sourceEdit.offset) { |
| 29 index++; |
| 30 } |
| 31 edits.insert(index, sourceEdit); |
| 32 } |
| 33 |
| 34 /** |
| 35 * Adds [edit] to the [FileEdit] for the given [file]. |
| 36 */ |
| 37 void addEditToSourceChange( |
| 38 SourceChange change, String file, int fileStamp, SourceEdit edit) { |
| 39 SourceFileEdit fileEdit = change.getFileEdit(file); |
| 40 if (fileEdit == null) { |
| 41 fileEdit = new SourceFileEdit(file, fileStamp); |
| 42 change.addFileEdit(fileEdit); |
| 43 } |
| 44 fileEdit.add(edit); |
| 45 } |
| 46 |
| 47 /** |
| 48 * Get the result of applying the edit to the given [code]. Access via |
| 49 * SourceEdit.apply(). |
| 50 */ |
| 51 String applyEdit(String code, SourceEdit edit) { |
| 52 if (edit.length < 0) { |
| 53 throw new RangeError('length is negative'); |
| 54 } |
| 55 return code.replaceRange(edit.offset, edit.end, edit.replacement); |
| 56 } |
| 57 |
| 58 /** |
| 59 * Get the result of applying a set of [edits] to the given [code]. Edits |
| 60 * are applied in the order they appear in [edits]. Access via |
| 61 * SourceEdit.applySequence(). |
| 62 */ |
| 63 String applySequenceOfEdits(String code, Iterable<SourceEdit> edits) { |
| 64 edits.forEach((SourceEdit edit) { |
| 65 code = edit.apply(code); |
| 66 }); |
| 67 return code; |
| 68 } |
| 69 |
| 70 /** |
| 71 * Returns the [FileEdit] for the given [file], maybe `null`. |
| 72 */ |
| 73 SourceFileEdit getChangeFileEdit(SourceChange change, String file) { |
| 74 for (SourceFileEdit fileEdit in change.edits) { |
| 75 if (fileEdit.file == file) { |
| 76 return fileEdit; |
| 77 } |
| 78 } |
| 79 return null; |
| 80 } |
| 81 |
| 82 /** |
| 83 * Compare the lists [listA] and [listB], using [itemEqual] to compare |
| 84 * list elements. |
| 85 */ |
| 86 bool listEqual(List listA, List listB, bool itemEqual(a, b)) { |
| 87 if (listA == null) { |
| 88 return listB == null; |
| 89 } |
| 90 if (listB == null) { |
| 91 return false; |
| 92 } |
| 93 if (listA.length != listB.length) { |
| 94 return false; |
| 95 } |
| 96 for (int i = 0; i < listA.length; i++) { |
| 97 if (!itemEqual(listA[i], listB[i])) { |
| 98 return false; |
| 99 } |
| 100 } |
| 101 return true; |
| 102 } |
| 103 |
| 104 /** |
| 105 * Compare the maps [mapA] and [mapB], using [valueEqual] to compare map |
| 106 * values. |
| 107 */ |
| 108 bool mapEqual(Map mapA, Map mapB, bool valueEqual(a, b)) { |
| 109 if (mapA == null) { |
| 110 return mapB == null; |
| 111 } |
| 112 if (mapB == null) { |
| 113 return false; |
| 114 } |
| 115 if (mapA.length != mapB.length) { |
| 116 return false; |
| 117 } |
| 118 for (var key in mapA.keys) { |
| 119 if (!mapB.containsKey(key)) { |
| 120 return false; |
| 121 } |
| 122 if (!valueEqual(mapA[key], mapB[key])) { |
| 123 return false; |
| 124 } |
| 125 } |
| 126 return true; |
| 127 } |
| 128 |
| 129 /** |
| 130 * Translate the input [map], applying [keyCallback] to all its keys, and |
| 131 * [valueCallback] to all its values. |
| 132 */ |
| 133 Map/*<KR, VR>*/ mapMap/*<KP, VP, KR, VR>*/(Map/*<KP, VP>*/ map, |
| 134 {dynamic/*=KR*/ keyCallback(/*<KP>*/ key), |
| 135 dynamic/*=VR*/ valueCallback(/*<VP>*/ value)}) { |
| 136 Map/*<KR, VR>*/ result = new HashMap/*<KR, VR>*/(); |
| 137 map.forEach((key, value) { |
| 138 Object/*=KR*/ resultKey; |
| 139 Object/*=VR*/ resultValue; |
| 140 if (keyCallback != null) { |
| 141 resultKey = keyCallback(key); |
| 142 } else { |
| 143 resultKey = key as Object/*=KR*/; |
| 144 } |
| 145 if (valueCallback != null) { |
| 146 resultValue = valueCallback(value); |
| 147 } else { |
| 148 resultValue = value as Object/*=VR*/; |
| 149 } |
| 150 result[resultKey] = resultValue; |
| 151 }); |
| 152 return result; |
| 153 } |
| 154 |
| 155 RefactoringProblemSeverity maxRefactoringProblemSeverity( |
| 156 RefactoringProblemSeverity a, RefactoringProblemSeverity b) { |
| 157 if (b == null) { |
| 158 return a; |
| 159 } |
| 160 if (a == null) { |
| 161 return b; |
| 162 } else if (a == RefactoringProblemSeverity.INFO) { |
| 163 return b; |
| 164 } else if (a == RefactoringProblemSeverity.WARNING) { |
| 165 if (b == RefactoringProblemSeverity.ERROR || |
| 166 b == RefactoringProblemSeverity.FATAL) { |
| 167 return b; |
| 168 } |
| 169 } else if (a == RefactoringProblemSeverity.ERROR) { |
| 170 if (b == RefactoringProblemSeverity.FATAL) { |
| 171 return b; |
| 172 } |
| 173 } |
| 174 return a; |
| 175 } |
| 176 |
| 177 /** |
| 178 * Create a [RefactoringFeedback] corresponding the given [kind]. |
| 179 */ |
| 180 RefactoringFeedback refactoringFeedbackFromJson( |
| 181 JsonDecoder jsonDecoder, String jsonPath, Object json, Map feedbackJson) { |
| 182 RefactoringKind kind = jsonDecoder.refactoringKind; |
| 183 if (kind == RefactoringKind.EXTRACT_LOCAL_VARIABLE) { |
| 184 return new ExtractLocalVariableFeedback.fromJson( |
| 185 jsonDecoder, jsonPath, json); |
| 186 } |
| 187 if (kind == RefactoringKind.EXTRACT_METHOD) { |
| 188 return new ExtractMethodFeedback.fromJson(jsonDecoder, jsonPath, json); |
| 189 } |
| 190 if (kind == RefactoringKind.INLINE_LOCAL_VARIABLE) { |
| 191 return new InlineLocalVariableFeedback.fromJson( |
| 192 jsonDecoder, jsonPath, json); |
| 193 } |
| 194 if (kind == RefactoringKind.INLINE_METHOD) { |
| 195 return new InlineMethodFeedback.fromJson(jsonDecoder, jsonPath, json); |
| 196 } |
| 197 if (kind == RefactoringKind.RENAME) { |
| 198 return new RenameFeedback.fromJson(jsonDecoder, jsonPath, json); |
| 199 } |
| 200 return null; |
| 201 } |
| 202 |
| 203 /** |
| 204 * Create a [RefactoringOptions] corresponding the given [kind]. |
| 205 */ |
| 206 RefactoringOptions refactoringOptionsFromJson(JsonDecoder jsonDecoder, |
| 207 String jsonPath, Object json, RefactoringKind kind) { |
| 208 if (kind == RefactoringKind.EXTRACT_LOCAL_VARIABLE) { |
| 209 return new ExtractLocalVariableOptions.fromJson( |
| 210 jsonDecoder, jsonPath, json); |
| 211 } |
| 212 if (kind == RefactoringKind.EXTRACT_METHOD) { |
| 213 return new ExtractMethodOptions.fromJson(jsonDecoder, jsonPath, json); |
| 214 } |
| 215 if (kind == RefactoringKind.INLINE_METHOD) { |
| 216 return new InlineMethodOptions.fromJson(jsonDecoder, jsonPath, json); |
| 217 } |
| 218 if (kind == RefactoringKind.MOVE_FILE) { |
| 219 return new MoveFileOptions.fromJson(jsonDecoder, jsonPath, json); |
| 220 } |
| 221 if (kind == RefactoringKind.RENAME) { |
| 222 return new RenameOptions.fromJson(jsonDecoder, jsonPath, json); |
| 223 } |
| 224 return null; |
| 225 } |
| 226 |
| 227 ///** |
| 228 // * Create a [RefactoringFeedback] corresponding the given [kind]. |
| 229 // */ |
| 230 //RefactoringFeedback refactoringFeedbackFromJson( |
| 231 // JsonDecoder jsonDecoder, String jsonPath, Object json, Map feedbackJson) { |
| 232 // RefactoringKind kind = jsonDecoder.refactoringKind; |
| 233 // if (kind == RefactoringKind.EXTRACT_LOCAL_VARIABLE) { |
| 234 // return new ExtractLocalVariableFeedback.fromJson( |
| 235 // jsonDecoder, jsonPath, json); |
| 236 // } |
| 237 // if (kind == RefactoringKind.EXTRACT_METHOD) { |
| 238 // return new ExtractMethodFeedback.fromJson(jsonDecoder, jsonPath, json); |
| 239 // } |
| 240 // if (kind == RefactoringKind.INLINE_LOCAL_VARIABLE) { |
| 241 // return new InlineLocalVariableFeedback.fromJson( |
| 242 // jsonDecoder, jsonPath, json); |
| 243 // } |
| 244 // if (kind == RefactoringKind.INLINE_METHOD) { |
| 245 // return new InlineMethodFeedback.fromJson(jsonDecoder, jsonPath, json); |
| 246 // } |
| 247 // if (kind == RefactoringKind.RENAME) { |
| 248 // return new RenameFeedback.fromJson(jsonDecoder, jsonPath, json); |
| 249 // } |
| 250 // return null; |
| 251 //} |
| 252 // |
| 253 ///** |
| 254 // * Create a [RefactoringOptions] corresponding the given [kind]. |
| 255 // */ |
| 256 //RefactoringOptions refactoringOptionsFromJson(JsonDecoder jsonDecoder, |
| 257 // String jsonPath, Object json, RefactoringKind kind) { |
| 258 // if (kind == RefactoringKind.EXTRACT_LOCAL_VARIABLE) { |
| 259 // return new ExtractLocalVariableOptions.fromJson( |
| 260 // jsonDecoder, jsonPath, json); |
| 261 // } |
| 262 // if (kind == RefactoringKind.EXTRACT_METHOD) { |
| 263 // return new ExtractMethodOptions.fromJson(jsonDecoder, jsonPath, json); |
| 264 // } |
| 265 // if (kind == RefactoringKind.INLINE_METHOD) { |
| 266 // return new InlineMethodOptions.fromJson(jsonDecoder, jsonPath, json); |
| 267 // } |
| 268 // if (kind == RefactoringKind.MOVE_FILE) { |
| 269 // return new MoveFileOptions.fromJson(jsonDecoder, jsonPath, json); |
| 270 // } |
| 271 // if (kind == RefactoringKind.RENAME) { |
| 272 // return new RenameOptions.fromJson(jsonDecoder, jsonPath, json); |
| 273 // } |
| 274 // return null; |
| 275 //} |
| 276 |
| 277 /** |
| 278 * Type of callbacks used to decode parts of JSON objects. [jsonPath] is a |
| 279 * string describing the part of the JSON object being decoded, and [value] is |
| 280 * the part to decode. |
| 281 */ |
| 282 typedef E JsonDecoderCallback<E>(String jsonPath, Object value); |
| 283 |
| 284 /** |
| 285 * Instances of the class [HasToJson] implement [toJson] method that returns |
| 286 * a JSON presentation. |
| 287 */ |
| 288 abstract class HasToJson { |
| 289 /** |
| 290 * Returns a JSON presentation of the object. |
| 291 */ |
| 292 Map<String, Object> toJson(); |
| 293 } |
| 294 |
| 295 /** |
| 296 * Base class for decoding JSON objects. The derived class must implement |
| 297 * error reporting logic. |
| 298 */ |
| 299 abstract class JsonDecoder { |
| 300 /** |
| 301 * Retrieve the RefactoringKind that should be assumed when decoding |
| 302 * refactoring feedback objects, or null if no refactoring feedback object is |
| 303 * expected to be encountered. |
| 304 */ |
| 305 RefactoringKind get refactoringKind; |
| 306 |
| 307 /** |
| 308 * Decode a JSON object that is expected to be a boolean. The strings "true" |
| 309 * and "false" are also accepted. |
| 310 */ |
| 311 bool decodeBool(String jsonPath, Object json) { |
| 312 if (json is bool) { |
| 313 return json; |
| 314 } else if (json == 'true') { |
| 315 return true; |
| 316 } else if (json == 'false') { |
| 317 return false; |
| 318 } |
| 319 throw mismatch(jsonPath, 'bool', json); |
| 320 } |
| 321 |
| 322 /** |
| 323 * Decode a JSON object that is expected to be an integer. A string |
| 324 * representation of an integer is also accepted. |
| 325 */ |
| 326 int decodeInt(String jsonPath, Object json) { |
| 327 if (json is int) { |
| 328 return json; |
| 329 } else if (json is String) { |
| 330 return int.parse(json, onError: (String value) { |
| 331 throw mismatch(jsonPath, 'int', json); |
| 332 }); |
| 333 } |
| 334 throw mismatch(jsonPath, 'int', json); |
| 335 } |
| 336 |
| 337 /** |
| 338 * Decode a JSON object that is expected to be a List. The [decoder] is used |
| 339 * to decode the items in the list. |
| 340 * |
| 341 * The type parameter [E] is the expected type of the elements in the list. |
| 342 */ |
| 343 List/*<E>*/ decodeList/*<E>*/(String jsonPath, Object json, |
| 344 [JsonDecoderCallback/*<E>*/ decoder]) { |
| 345 if (json == null) { |
| 346 return/*<E>*/ []; |
| 347 } else if (json is List) { |
| 348 List/*<E>*/ result = /*<E>*/ []; |
| 349 for (int i = 0; i < json.length; i++) { |
| 350 result.add(decoder('$jsonPath[$i]', json[i])); |
| 351 } |
| 352 return result; |
| 353 } else { |
| 354 throw mismatch(jsonPath, 'List', json); |
| 355 } |
| 356 } |
| 357 |
| 358 /** |
| 359 * Decode a JSON object that is expected to be a Map. [keyDecoder] is used |
| 360 * to decode the keys, and [valueDecoder] is used to decode the values. |
| 361 */ |
| 362 Map/*<K, V>*/ decodeMap/*<K, V>*/(String jsonPath, Object json, |
| 363 {JsonDecoderCallback/*<K>*/ keyDecoder, |
| 364 JsonDecoderCallback/*<V>*/ valueDecoder}) { |
| 365 if (json == null) { |
| 366 return {}; |
| 367 } else if (json is Map) { |
| 368 Map/*<K, V>*/ result = /*<K, V>*/ {}; |
| 369 json.forEach((String key, value) { |
| 370 Object/*=K*/ decodedKey; |
| 371 if (keyDecoder != null) { |
| 372 decodedKey = keyDecoder('$jsonPath.key', key); |
| 373 } else { |
| 374 decodedKey = key as Object/*=K*/; |
| 375 } |
| 376 if (valueDecoder != null) { |
| 377 value = valueDecoder('$jsonPath[${JSON.encode(key)}]', value); |
| 378 } |
| 379 result[decodedKey] = value as Object/*=V*/; |
| 380 }); |
| 381 return result; |
| 382 } else { |
| 383 throw mismatch(jsonPath, 'Map', json); |
| 384 } |
| 385 } |
| 386 |
| 387 /** |
| 388 * Decode a JSON object that is expected to be a string. |
| 389 */ |
| 390 String decodeString(String jsonPath, Object json) { |
| 391 if (json is String) { |
| 392 return json; |
| 393 } else { |
| 394 throw mismatch(jsonPath, 'String', json); |
| 395 } |
| 396 } |
| 397 |
| 398 /** |
| 399 * Decode a JSON object that is expected to be one of several choices, |
| 400 * where the choices are disambiguated by the contents of the field [field]. |
| 401 * [decoders] is a map from each possible string in the field to the decoder |
| 402 * that should be used to decode the JSON object. |
| 403 */ |
| 404 Object decodeUnion(String jsonPath, Map json, String field, |
| 405 Map<String, JsonDecoderCallback> decoders) { |
| 406 if (json is Map) { |
| 407 if (!json.containsKey(field)) { |
| 408 throw missingKey(jsonPath, field); |
| 409 } |
| 410 var disambiguatorPath = '$jsonPath[${JSON.encode(field)}]'; |
| 411 String disambiguator = decodeString(disambiguatorPath, json[field]); |
| 412 if (!decoders.containsKey(disambiguator)) { |
| 413 throw mismatch( |
| 414 disambiguatorPath, 'One of: ${decoders.keys.toList()}', json); |
| 415 } |
| 416 return decoders[disambiguator](jsonPath, json); |
| 417 } else { |
| 418 throw mismatch(jsonPath, 'Map', json); |
| 419 } |
| 420 } |
| 421 |
| 422 /** |
| 423 * Create an exception to throw if the JSON object at [jsonPath] fails to |
| 424 * match the API definition of [expected]. |
| 425 */ |
| 426 dynamic mismatch(String jsonPath, String expected, [Object actual]); |
| 427 |
| 428 /** |
| 429 * Create an exception to throw if the JSON object at [jsonPath] is missing |
| 430 * the key [key]. |
| 431 */ |
| 432 dynamic missingKey(String jsonPath, String key); |
| 433 } |
| 434 |
| 435 /** |
| 436 * JsonDecoder for decoding requests. Errors are reporting by throwing a |
| 437 * [RequestFailure]. |
| 438 */ |
| 439 class RequestDecoder extends JsonDecoder { |
| 440 /** |
| 441 * The request being deserialized. |
| 442 */ |
| 443 final Request _request; |
| 444 |
| 445 RequestDecoder(this._request); |
| 446 |
| 447 @override |
| 448 RefactoringKind get refactoringKind { |
| 449 // Refactoring feedback objects should never appear in requests. |
| 450 return null; |
| 451 } |
| 452 |
| 453 @override |
| 454 dynamic mismatch(String jsonPath, String expected, [Object actual]) { |
| 455 StringBuffer buffer = new StringBuffer(); |
| 456 buffer.write('Expected to be '); |
| 457 buffer.write(expected); |
| 458 if (actual != null) { |
| 459 buffer.write('; found "'); |
| 460 buffer.write(JSON.encode(actual)); |
| 461 buffer.write('"'); |
| 462 } |
| 463 return new RequestFailure(RequestErrorFactory.invalidParameter( |
| 464 _request, jsonPath, buffer.toString())); |
| 465 } |
| 466 |
| 467 @override |
| 468 dynamic missingKey(String jsonPath, String key) { |
| 469 return new RequestFailure(RequestErrorFactory.invalidParameter( |
| 470 _request, jsonPath, 'Expected to contain key ${JSON.encode(key)}')); |
| 471 } |
| 472 } |
| 473 |
| 474 /** |
| 475 * JsonDecoder for decoding responses from the server. This is intended to be |
| 476 * used only for testing. Errors are reported using bare [Exception] objects. |
| 477 */ |
| 478 class ResponseDecoder extends JsonDecoder { |
| 479 @override |
| 480 final RefactoringKind refactoringKind; |
| 481 |
| 482 ResponseDecoder(this.refactoringKind); |
| 483 |
| 484 @override |
| 485 dynamic mismatch(String jsonPath, String expected, [Object actual]) { |
| 486 StringBuffer buffer = new StringBuffer(); |
| 487 buffer.write('Expected '); |
| 488 buffer.write(expected); |
| 489 if (actual != null) { |
| 490 buffer.write(' found "'); |
| 491 buffer.write(JSON.encode(actual)); |
| 492 buffer.write('"'); |
| 493 } |
| 494 buffer.write(' at '); |
| 495 buffer.write(jsonPath); |
| 496 return new Exception(buffer.toString()); |
| 497 } |
| 498 |
| 499 @override |
| 500 dynamic missingKey(String jsonPath, String key) { |
| 501 return new Exception('Missing key $key at $jsonPath'); |
| 502 } |
| 503 } |
| 504 |
| 505 /** |
| 506 * The result data associated with a response. |
| 507 */ |
| 508 abstract class ResponseResult implements HasToJson { |
| 509 /** |
| 510 * Return a response whose result data is this object for the request with the |
| 511 * given [id]. |
| 512 */ |
| 513 Response toResponse(String id); |
| 514 } |
OLD | NEW |