| 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 protocol2; | |
| 6 | |
| 7 import 'dart:convert'; | |
| 8 | |
| 9 import 'package:analysis_server/src/computer/element.dart' show | |
| 10 elementFromEngine; | |
| 11 import 'package:analysis_server/src/services/correction/fix.dart' show Fix; | |
| 12 import 'package:analysis_server/src/search/search_result.dart' show | |
| 13 searchResultFromMatch; | |
| 14 import 'package:analysis_server/src/services/json.dart'; | |
| 15 import 'package:analysis_server/src/services/search/search_engine.dart' as | |
| 16 engine; | |
| 17 import 'package:analyzer/src/generated/ast.dart' as engine; | |
| 18 import 'package:analyzer/src/generated/element.dart' as engine; | |
| 19 import 'package:analyzer/src/generated/engine.dart' as engine; | |
| 20 import 'package:analyzer/src/generated/error.dart' as engine; | |
| 21 import 'package:analyzer/src/generated/source.dart' as engine; | |
| 22 | |
| 23 import 'protocol.dart'; | |
| 24 | |
| 25 part 'generated_protocol.dart'; | |
| 26 | |
| 27 /** | |
| 28 * Translate the input [map], applying [keyCallback] to all its keys, and | |
| 29 * [valueCallback] to all its values. | |
| 30 */ | |
| 31 mapMap(Map map, {dynamic keyCallback(key), dynamic valueCallback(value)}) { | |
| 32 Map result = {}; | |
| 33 map.forEach((key, value) { | |
| 34 if (keyCallback != null) { | |
| 35 key = keyCallback(key); | |
| 36 } | |
| 37 if (valueCallback != null) { | |
| 38 value = valueCallback(value); | |
| 39 } | |
| 40 result[key] = value; | |
| 41 }); | |
| 42 return result; | |
| 43 } | |
| 44 | |
| 45 /** | |
| 46 * Adds the given [sourceEdits] to the list in [sourceFileEdit]. | |
| 47 */ | |
| 48 void _addAllEditsForSource(SourceFileEdit sourceFileEdit, | |
| 49 Iterable<SourceEdit> edits) { | |
| 50 edits.forEach(sourceFileEdit.add); | |
| 51 } | |
| 52 | |
| 53 /** | |
| 54 * Adds the given [sourceEdit] to the list in [sourceFileEdit]. | |
| 55 */ | |
| 56 void _addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit) { | |
| 57 List<SourceEdit> edits = sourceFileEdit.edits; | |
| 58 int index = 0; | |
| 59 while (index < edits.length && edits[index].offset > sourceEdit.offset) { | |
| 60 index++; | |
| 61 } | |
| 62 edits.insert(index, sourceEdit); | |
| 63 } | |
| 64 | |
| 65 /** | |
| 66 * Adds [edit] to the [FileEdit] for the given [file]. | |
| 67 */ | |
| 68 void _addEditToSourceChange(SourceChange change, String file, SourceEdit edit) { | |
| 69 SourceFileEdit fileEdit = change.getFileEdit(file); | |
| 70 if (fileEdit == null) { | |
| 71 fileEdit = new SourceFileEdit(file); | |
| 72 change.addFileEdit(fileEdit); | |
| 73 } | |
| 74 fileEdit.add(edit); | |
| 75 } | |
| 76 | |
| 77 /** | |
| 78 * Returns the [FileEdit] for the given [file], maybe `null`. | |
| 79 */ | |
| 80 SourceFileEdit _getChangeFileEdit(SourceChange change, String file) { | |
| 81 for (SourceFileEdit fileEdit in change.edits) { | |
| 82 if (fileEdit.file == file) { | |
| 83 return fileEdit; | |
| 84 } | |
| 85 } | |
| 86 return null; | |
| 87 } | |
| 88 | |
| 89 /** | |
| 90 * Create an AnalysisError based on error information from the analyzer | |
| 91 * engine. Access via AnalysisError.fromEngine(). | |
| 92 */ | |
| 93 AnalysisError _analysisErrorFromEngine(engine.LineInfo lineInfo, | |
| 94 engine.AnalysisError error) { | |
| 95 engine.ErrorCode errorCode = error.errorCode; | |
| 96 // prepare location | |
| 97 Location location; | |
| 98 { | |
| 99 String file = error.source.fullName; | |
| 100 int offset = error.offset; | |
| 101 int length = error.length; | |
| 102 int startLine = -1; | |
| 103 int startColumn = -1; | |
| 104 if (lineInfo != null) { | |
| 105 engine.LineInfo_Location lineLocation = lineInfo.getLocation(offset); | |
| 106 if (lineLocation != null) { | |
| 107 startLine = lineLocation.lineNumber; | |
| 108 startColumn = lineLocation.columnNumber; | |
| 109 } | |
| 110 } | |
| 111 location = new Location(file, offset, length, startLine, startColumn); | |
| 112 } | |
| 113 // done | |
| 114 var severity = new ErrorSeverity(errorCode.errorSeverity.name); | |
| 115 var type = new ErrorType(errorCode.type.name); | |
| 116 String message = error.message; | |
| 117 String correction = error.correction; | |
| 118 return new AnalysisError( | |
| 119 severity, | |
| 120 type, | |
| 121 location, | |
| 122 message, | |
| 123 correction: correction); | |
| 124 } | |
| 125 | |
| 126 /** | |
| 127 * Returns a list of AnalysisErrors correponding to the given list of Engine | |
| 128 * errors. Access via AnalysisError.listFromEngine(). | |
| 129 */ | |
| 130 List<AnalysisError> _analysisErrorListFromEngine(engine.LineInfo lineInfo, | |
| 131 List<engine.AnalysisError> errors) { | |
| 132 return errors.map((engine.AnalysisError error) { | |
| 133 return new AnalysisError.fromEngine(lineInfo, error); | |
| 134 }).toList(); | |
| 135 } | |
| 136 | |
| 137 /** | |
| 138 * Get the result of applying the edit to the given [code]. Access via | |
| 139 * SourceEdit.apply(). | |
| 140 */ | |
| 141 String _applyEdit(String code, SourceEdit edit) { | |
| 142 return code.substring(0, edit.offset) + | |
| 143 edit.replacement + | |
| 144 code.substring(edit.end); | |
| 145 } | |
| 146 | |
| 147 /** | |
| 148 * Get the result of applying a set of [edits] to the given [code]. Edits | |
| 149 * are applied in the order they appear in [edits]. Access via | |
| 150 * SourceEdit.applySequence(). | |
| 151 */ | |
| 152 String _applySequence(String code, Iterable<SourceEdit> edits) { | |
| 153 edits.forEach((SourceEdit edit) { | |
| 154 code = edit.apply(code); | |
| 155 }); | |
| 156 return code; | |
| 157 } | |
| 158 | |
| 159 /** | |
| 160 * Map an element kind from the analyzer engine to a [CompletionSuggestionKind]. | |
| 161 */ | |
| 162 CompletionSuggestionKind _completionSuggestionKindFromElementKind(engine.Element
Kind kind) { | |
| 163 // ElementKind.ANGULAR_FORMATTER, | |
| 164 // ElementKind.ANGULAR_COMPONENT, | |
| 165 // ElementKind.ANGULAR_CONTROLLER, | |
| 166 // ElementKind.ANGULAR_DIRECTIVE, | |
| 167 // ElementKind.ANGULAR_PROPERTY, | |
| 168 // ElementKind.ANGULAR_SCOPE_PROPERTY, | |
| 169 // ElementKind.ANGULAR_SELECTOR, | |
| 170 // ElementKind.ANGULAR_VIEW, | |
| 171 if (kind == engine.ElementKind.CLASS) return CompletionSuggestionKind.CLASS; | |
| 172 // ElementKind.COMPILATION_UNIT, | |
| 173 if (kind == engine.ElementKind.CONSTRUCTOR) return CompletionSuggestionKind.CO
NSTRUCTOR; | |
| 174 // ElementKind.DYNAMIC, | |
| 175 // ElementKind.EMBEDDED_HTML_SCRIPT, | |
| 176 // ElementKind.ERROR, | |
| 177 // ElementKind.EXPORT, | |
| 178 // ElementKind.EXTERNAL_HTML_SCRIPT, | |
| 179 if (kind == engine.ElementKind.FIELD) return CompletionSuggestionKind.FIELD; | |
| 180 if (kind == engine.ElementKind.FUNCTION) return CompletionSuggestionKind.FUNCT
ION; | |
| 181 if (kind == engine.ElementKind.FUNCTION_TYPE_ALIAS) return CompletionSuggestio
nKind.FUNCTION_TYPE_ALIAS; | |
| 182 if (kind == engine.ElementKind.GETTER) return CompletionSuggestionKind.GETTER; | |
| 183 // ElementKind.HTML, | |
| 184 if (kind == engine.ElementKind.IMPORT) return CompletionSuggestionKind.IMPORT; | |
| 185 // ElementKind.LABEL, | |
| 186 // ElementKind.LIBRARY, | |
| 187 if (kind == engine.ElementKind.LOCAL_VARIABLE) return CompletionSuggestionKind
.LOCAL_VARIABLE; | |
| 188 if (kind == engine.ElementKind.METHOD) return CompletionSuggestionKind.METHOD; | |
| 189 // ElementKind.NAME, | |
| 190 if (kind == engine.ElementKind.PARAMETER) return CompletionSuggestionKind.PARA
METER; | |
| 191 // ElementKind.POLYMER_ATTRIBUTE, | |
| 192 // ElementKind.POLYMER_TAG_DART, | |
| 193 // ElementKind.POLYMER_TAG_HTML, | |
| 194 // ElementKind.PREFIX, | |
| 195 if (kind == engine.ElementKind.SETTER) return CompletionSuggestionKind.SETTER; | |
| 196 if (kind == engine.ElementKind.TOP_LEVEL_VARIABLE) return CompletionSuggestion
Kind.TOP_LEVEL_VARIABLE; | |
| 197 // ElementKind.TYPE_PARAMETER, | |
| 198 // ElementKind.UNIVERSE | |
| 199 throw new ArgumentError('Unknown CompletionSuggestionKind for: $kind'); | |
| 200 } | |
| 201 | |
| 202 /** | |
| 203 * Create an ElementKind based on a value from the analyzer engine. Access | |
| 204 * this function via new ElementKind.fromEngine(). | |
| 205 */ | |
| 206 ElementKind _elementKindFromEngine(engine.ElementKind kind) { | |
| 207 if (kind == engine.ElementKind.CLASS) { | |
| 208 return ElementKind.CLASS; | |
| 209 } | |
| 210 if (kind == engine.ElementKind.COMPILATION_UNIT) { | |
| 211 return ElementKind.COMPILATION_UNIT; | |
| 212 } | |
| 213 if (kind == engine.ElementKind.CONSTRUCTOR) { | |
| 214 return ElementKind.CONSTRUCTOR; | |
| 215 } | |
| 216 if (kind == engine.ElementKind.FIELD) { | |
| 217 return ElementKind.FIELD; | |
| 218 } | |
| 219 if (kind == engine.ElementKind.FUNCTION) { | |
| 220 return ElementKind.FUNCTION; | |
| 221 } | |
| 222 if (kind == engine.ElementKind.FUNCTION_TYPE_ALIAS) { | |
| 223 return ElementKind.FUNCTION_TYPE_ALIAS; | |
| 224 } | |
| 225 if (kind == engine.ElementKind.GETTER) { | |
| 226 return ElementKind.GETTER; | |
| 227 } | |
| 228 if (kind == engine.ElementKind.LIBRARY) { | |
| 229 return ElementKind.LIBRARY; | |
| 230 } | |
| 231 if (kind == engine.ElementKind.LOCAL_VARIABLE) { | |
| 232 return ElementKind.LOCAL_VARIABLE; | |
| 233 } | |
| 234 if (kind == engine.ElementKind.METHOD) { | |
| 235 return ElementKind.METHOD; | |
| 236 } | |
| 237 if (kind == engine.ElementKind.PARAMETER) { | |
| 238 return ElementKind.PARAMETER; | |
| 239 } | |
| 240 if (kind == engine.ElementKind.SETTER) { | |
| 241 return ElementKind.SETTER; | |
| 242 } | |
| 243 if (kind == engine.ElementKind.TOP_LEVEL_VARIABLE) { | |
| 244 return ElementKind.TOP_LEVEL_VARIABLE; | |
| 245 } | |
| 246 if (kind == engine.ElementKind.TYPE_PARAMETER) { | |
| 247 return ElementKind.TYPE_PARAMETER; | |
| 248 } | |
| 249 return ElementKind.UNKNOWN; | |
| 250 } | |
| 251 | |
| 252 /** | |
| 253 * Compare the lists [listA] and [listB], using [itemEqual] to compare | |
| 254 * list elements. | |
| 255 */ | |
| 256 bool _listEqual(List listA, List listB, bool itemEqual(a, b)) { | |
| 257 if (listA.length != listB.length) { | |
| 258 return false; | |
| 259 } | |
| 260 for (int i = 0; i < listA.length; i++) { | |
| 261 if (!itemEqual(listA[i], listB[i])) { | |
| 262 return false; | |
| 263 } | |
| 264 } | |
| 265 return true; | |
| 266 } | |
| 267 | |
| 268 /** | |
| 269 * Creates a new [Location]. | |
| 270 */ | |
| 271 Location _locationForArgs(engine.AnalysisContext context, engine.Source source, | |
| 272 engine.SourceRange range) { | |
| 273 int startLine = 0; | |
| 274 int startColumn = 0; | |
| 275 { | |
| 276 engine.LineInfo lineInfo = context.getLineInfo(source); | |
| 277 if (lineInfo != null) { | |
| 278 engine.LineInfo_Location offsetLocation = | |
| 279 lineInfo.getLocation(range.offset); | |
| 280 startLine = offsetLocation.lineNumber; | |
| 281 startColumn = offsetLocation.columnNumber; | |
| 282 } | |
| 283 } | |
| 284 return new Location( | |
| 285 source.fullName, | |
| 286 range.offset, | |
| 287 range.length, | |
| 288 startLine, | |
| 289 startColumn); | |
| 290 } | |
| 291 | |
| 292 /** | |
| 293 * Creates a new [Location] for the given [engine.Element]. | |
| 294 */ | |
| 295 Location _locationFromElement(engine.Element element) { | |
| 296 engine.AnalysisContext context = element.context; | |
| 297 engine.Source source = element.source; | |
| 298 String name = element.displayName; | |
| 299 int offset = element.nameOffset; | |
| 300 int length = name != null ? name.length : 0; | |
| 301 if (element is engine.CompilationUnitElement) { | |
| 302 offset = 0; | |
| 303 length = 0; | |
| 304 } | |
| 305 engine.SourceRange range = new engine.SourceRange(offset, length); | |
| 306 return _locationForArgs(context, source, range); | |
| 307 } | |
| 308 | |
| 309 | |
| 310 /** | |
| 311 * Creates a new [Location] for the given [engine.SearchMatch]. | |
| 312 */ | |
| 313 Location _locationFromMatch(engine.SearchMatch match) { | |
| 314 engine.Element enclosingElement = match.element; | |
| 315 return _locationForArgs( | |
| 316 enclosingElement.context, | |
| 317 enclosingElement.source, | |
| 318 match.sourceRange); | |
| 319 } | |
| 320 | |
| 321 | |
| 322 /** | |
| 323 * Creates a new [Location] for the given [engine.AstNode]. | |
| 324 */ | |
| 325 Location _locationFromNode(engine.AstNode node) { | |
| 326 engine.CompilationUnit unit = | |
| 327 node.getAncestor((node) => node is engine.CompilationUnit); | |
| 328 engine.CompilationUnitElement unitElement = unit.element; | |
| 329 engine.AnalysisContext context = unitElement.context; | |
| 330 engine.Source source = unitElement.source; | |
| 331 engine.SourceRange range = new engine.SourceRange(node.offset, node.length); | |
| 332 return _locationForArgs(context, source, range); | |
| 333 } | |
| 334 | |
| 335 /** | |
| 336 * Creates a new [Location] for the given [engine.CompilationUnit]. | |
| 337 */ | |
| 338 Location _locationFromUnit(engine.CompilationUnit unit, | |
| 339 engine.SourceRange range) { | |
| 340 engine.CompilationUnitElement unitElement = unit.element; | |
| 341 engine.AnalysisContext context = unitElement.context; | |
| 342 engine.Source source = unitElement.source; | |
| 343 return _locationForArgs(context, source, range); | |
| 344 } | |
| 345 | |
| 346 | |
| 347 /** | |
| 348 * Compare the maps [mapA] and [mapB], using [valueEqual] to compare map | |
| 349 * values. | |
| 350 */ | |
| 351 bool _mapEqual(Map mapA, Map mapB, bool valueEqual(a, b)) { | |
| 352 if (mapA.length != mapB.length) { | |
| 353 return false; | |
| 354 } | |
| 355 for (var key in mapA.keys) { | |
| 356 if (!mapB.containsKey(key)) { | |
| 357 return false; | |
| 358 } | |
| 359 if (!valueEqual(mapA[key], mapB[key])) { | |
| 360 return false; | |
| 361 } | |
| 362 } | |
| 363 return true; | |
| 364 } | |
| 365 | |
| 366 | |
| 367 RefactoringProblemSeverity | |
| 368 _maxRefactoringProblemSeverity(RefactoringProblemSeverity a, | |
| 369 RefactoringProblemSeverity b) { | |
| 370 if (b == null) { | |
| 371 return a; | |
| 372 } | |
| 373 if (a == null) { | |
| 374 return b; | |
| 375 } else if (a == RefactoringProblemSeverity.INFO) { | |
| 376 return b; | |
| 377 } else if (a == RefactoringProblemSeverity.WARNING) { | |
| 378 if (b == RefactoringProblemSeverity.ERROR || | |
| 379 b == RefactoringProblemSeverity.FATAL) { | |
| 380 return b; | |
| 381 } | |
| 382 } else if (a == RefactoringProblemSeverity.ERROR) { | |
| 383 if (b == RefactoringProblemSeverity.FATAL) { | |
| 384 return b; | |
| 385 } | |
| 386 } | |
| 387 return a; | |
| 388 } | |
| 389 | |
| 390 /** | |
| 391 * Create an OverriddenMember based on an element from the analyzer engine. | |
| 392 */ | |
| 393 OverriddenMember _overriddenMemberFromEngine(engine.Element member) { | |
| 394 Element element = elementFromEngine(member); | |
| 395 String className = member.enclosingElement.displayName; | |
| 396 return new OverriddenMember(element, className); | |
| 397 } | |
| 398 | |
| 399 /** | |
| 400 * Create a SearchResultKind based on a value from the search engine. | |
| 401 */ | |
| 402 SearchResultKind _searchResultKindFromEngine(engine.MatchKind kind) { | |
| 403 if (kind == engine.MatchKind.DECLARATION) { | |
| 404 return SearchResultKind.DECLARATION; | |
| 405 } | |
| 406 if (kind == engine.MatchKind.READ) { | |
| 407 return SearchResultKind.READ; | |
| 408 } | |
| 409 if (kind == engine.MatchKind.READ_WRITE) { | |
| 410 return SearchResultKind.READ_WRITE; | |
| 411 } | |
| 412 if (kind == engine.MatchKind.WRITE) { | |
| 413 return SearchResultKind.WRITE; | |
| 414 } | |
| 415 if (kind == engine.MatchKind.INVOCATION) { | |
| 416 return SearchResultKind.INVOCATION; | |
| 417 } | |
| 418 if (kind == engine.MatchKind.REFERENCE) { | |
| 419 return SearchResultKind.REFERENCE; | |
| 420 } | |
| 421 return SearchResultKind.UNKNOWN; | |
| 422 } | |
| 423 | |
| 424 | |
| 425 /** | |
| 426 * Type of callbacks used to decode parts of JSON objects. [jsonPath] is a | |
| 427 * string describing the part of the JSON object being decoded, and [value] is | |
| 428 * the part to decode. | |
| 429 */ | |
| 430 typedef Object JsonDecoderCallback(String jsonPath, Object value); | |
| 431 | |
| 432 /** | |
| 433 * Base class for decoding JSON objects. The derived class must implement | |
| 434 * error reporting logic. | |
| 435 */ | |
| 436 abstract class JsonDecoder { | |
| 437 /** | |
| 438 * Create an exception to throw if the JSON object at [jsonPath] fails to | |
| 439 * match the API definition of [expected]. | |
| 440 */ | |
| 441 dynamic mismatch(String jsonPath, String expected); | |
| 442 | |
| 443 /** | |
| 444 * Create an exception to throw if the JSON object at [jsonPath] is missing | |
| 445 * the key [key]. | |
| 446 */ | |
| 447 dynamic missingKey(String jsonPath, String key); | |
| 448 | |
| 449 /** | |
| 450 * Decode a JSON object that is expected to be a boolean. The strings "true" | |
| 451 * and "false" are also accepted. | |
| 452 */ | |
| 453 bool _decodeBool(String jsonPath, Object json) { | |
| 454 if (json is bool) { | |
| 455 return json; | |
| 456 } else if (json == 'true') { | |
| 457 return true; | |
| 458 } else if (json == 'false') { | |
| 459 return false; | |
| 460 } | |
| 461 throw mismatch(jsonPath, 'bool'); | |
| 462 } | |
| 463 | |
| 464 /** | |
| 465 * Decode a JSON object that is expected to be an integer. A string | |
| 466 * representation of an integer is also accepted. | |
| 467 */ | |
| 468 int _decodeInt(String jsonPath, Object json) { | |
| 469 if (json is int) { | |
| 470 return json; | |
| 471 } else if (json is String) { | |
| 472 return int.parse(json, onError: (String value) { | |
| 473 throw mismatch(jsonPath, 'int'); | |
| 474 }); | |
| 475 } | |
| 476 throw mismatch(jsonPath, 'int'); | |
| 477 } | |
| 478 | |
| 479 /** | |
| 480 * Decode a JSON object that is expected to be a List. [decoder] is used to | |
| 481 * decode the items in the list. | |
| 482 */ | |
| 483 List _decodeList(String jsonPath, Object json, | |
| 484 [JsonDecoderCallback decoder]) { | |
| 485 if (json == null) { | |
| 486 return []; | |
| 487 } else if (json is List) { | |
| 488 List result = []; | |
| 489 for (int i = 0; i < json.length; i++) { | |
| 490 result.add(decoder('$jsonPath[$i]', json[i])); | |
| 491 } | |
| 492 return result; | |
| 493 } else { | |
| 494 throw mismatch(jsonPath, 'List'); | |
| 495 } | |
| 496 } | |
| 497 | |
| 498 /** | |
| 499 * Decode a JSON object that is expected to be a Map. [keyDecoder] is used | |
| 500 * to decode the keys, and [valueDecoder] is used to decode the values. | |
| 501 */ | |
| 502 Map _decodeMap(String jsonPath, Object json, {JsonDecoderCallback keyDecoder, | |
| 503 JsonDecoderCallback valueDecoder}) { | |
| 504 if (json == null) { | |
| 505 return {}; | |
| 506 } else if (json is Map) { | |
| 507 Map result = {}; | |
| 508 json.forEach((String key, value) { | |
| 509 Object decodedKey; | |
| 510 if (keyDecoder != null) { | |
| 511 decodedKey = keyDecoder('$jsonPath.key', key); | |
| 512 } else { | |
| 513 decodedKey = key; | |
| 514 } | |
| 515 if (valueDecoder != null) { | |
| 516 value = valueDecoder('$jsonPath[${JSON.encode(key)}]', value); | |
| 517 } | |
| 518 result[decodedKey] = value; | |
| 519 }); | |
| 520 return result; | |
| 521 } else { | |
| 522 throw mismatch(jsonPath, 'Map'); | |
| 523 } | |
| 524 } | |
| 525 | |
| 526 /** | |
| 527 * Decode a JSON object that is expected to be a string. | |
| 528 */ | |
| 529 String _decodeString(String jsonPath, Object json) { | |
| 530 if (json is String) { | |
| 531 return json; | |
| 532 } else { | |
| 533 throw mismatch(jsonPath, 'String'); | |
| 534 } | |
| 535 } | |
| 536 | |
| 537 /** | |
| 538 * Decode a JSON object that is expected to be one of several choices, | |
| 539 * where the choices are disambiguated by the contents of the field [field]. | |
| 540 * [decoders] is a map from each possible string in the field to the decoder | |
| 541 * that should be used to decode the JSON object. | |
| 542 */ | |
| 543 Object _decodeUnion(String jsonPath, Map json, String field, Map<String, | |
| 544 JsonDecoderCallback> decoders) { | |
| 545 if (json is Map) { | |
| 546 if (!json.containsKey(field)) { | |
| 547 throw missingKey(jsonPath, field); | |
| 548 } | |
| 549 var disambiguatorPath = '$jsonPath[${JSON.encode(field)}]'; | |
| 550 String disambiguator = _decodeString(disambiguatorPath, json[field]); | |
| 551 if (!decoders.containsKey(disambiguator)) { | |
| 552 throw mismatch(disambiguatorPath, 'One of: ${decoders.keys.toList()}'); | |
| 553 } | |
| 554 return decoders[disambiguator](jsonPath, json); | |
| 555 } else { | |
| 556 throw mismatch(jsonPath, 'Map'); | |
| 557 } | |
| 558 } | |
| 559 } | |
| 560 | |
| 561 /** | |
| 562 * JsonDecoder for decoding requests. Errors are reporting by throwing a | |
| 563 * [RequestFailure]. | |
| 564 */ | |
| 565 class RequestDecoder extends JsonDecoder { | |
| 566 /** | |
| 567 * The request being deserialized. | |
| 568 */ | |
| 569 final Request _request; | |
| 570 | |
| 571 RequestDecoder(this._request); | |
| 572 | |
| 573 @override | |
| 574 dynamic mismatch(String jsonPath, String expected) { | |
| 575 return new RequestFailure( | |
| 576 new Response.invalidParameter(_request, jsonPath, 'be $expected')); | |
| 577 } | |
| 578 | |
| 579 @override | |
| 580 dynamic missingKey(String jsonPath, String key) { | |
| 581 return new RequestFailure( | |
| 582 new Response.invalidParameter( | |
| 583 _request, | |
| 584 jsonPath, | |
| 585 'contain key ${JSON.encode(key)}')); | |
| 586 } | |
| 587 } | |
| 588 | |
| 589 /** | |
| 590 * JsonDecoder for decoding responses from the server. This is intended to be | |
| 591 * used only for testing. Errors are reported using bare [Exception] objects. | |
| 592 */ | |
| 593 class ResponseDecoder extends JsonDecoder { | |
| 594 @override | |
| 595 dynamic mismatch(String jsonPath, String expected) { | |
| 596 return new Exception('Expected $expected at $jsonPath'); | |
| 597 } | |
| 598 | |
| 599 @override | |
| 600 dynamic missingKey(String jsonPath, String key) { | |
| 601 return new Exception('Missing key $key at $jsonPath'); | |
| 602 } | |
| 603 } | |
| 604 | |
| 605 /** | |
| 606 * Jenkins hash function, optimized for small integers. Borrowed from | |
| 607 * sdk/lib/math/jenkins_smi_hash.dart. | |
| 608 * | |
| 609 * TODO(paulberry): Move to somewhere that can be shared with other code. | |
| 610 */ | |
| 611 class _JenkinsSmiHash { | |
| 612 static int combine(int hash, int value) { | |
| 613 hash = 0x1fffffff & (hash + value); | |
| 614 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | |
| 615 return hash ^ (hash >> 6); | |
| 616 } | |
| 617 | |
| 618 static int finish(int hash) { | |
| 619 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | |
| 620 hash = hash ^ (hash >> 11); | |
| 621 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | |
| 622 } | |
| 623 | |
| 624 static int hash2(a, b) => finish(combine(combine(0, a), b)); | |
| 625 | |
| 626 static int hash4(a, b, c, d) => | |
| 627 finish(combine(combine(combine(combine(0, a), b), c), d)); | |
| 628 } | |
| OLD | NEW |