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