Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(683)

Side by Side Diff: pkg/analysis_server/lib/src/status/get_handler.dart

Issue 1363653005: Add more performance data (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library analysis_server.src.status.get_handler; 5 library analysis_server.src.status.get_handler;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:collection'; 8 import 'dart:collection';
9 import 'dart:convert'; 9 import 'dart:convert';
10 import 'dart:io'; 10 import 'dart:io';
(...skipping 10 matching lines...) Expand all
21 import 'package:analysis_server/src/services/index/local_index.dart'; 21 import 'package:analysis_server/src/services/index/local_index.dart';
22 import 'package:analysis_server/src/services/index/store/split_store.dart'; 22 import 'package:analysis_server/src/services/index/store/split_store.dart';
23 import 'package:analysis_server/src/socket_server.dart'; 23 import 'package:analysis_server/src/socket_server.dart';
24 import 'package:analysis_server/src/status/ast_writer.dart'; 24 import 'package:analysis_server/src/status/ast_writer.dart';
25 import 'package:analysis_server/src/status/element_writer.dart'; 25 import 'package:analysis_server/src/status/element_writer.dart';
26 import 'package:analyzer/file_system/file_system.dart'; 26 import 'package:analyzer/file_system/file_system.dart';
27 import 'package:analyzer/src/context/cache.dart'; 27 import 'package:analyzer/src/context/cache.dart';
28 import 'package:analyzer/src/generated/ast.dart'; 28 import 'package:analyzer/src/generated/ast.dart';
29 import 'package:analyzer/src/generated/element.dart'; 29 import 'package:analyzer/src/generated/element.dart';
30 import 'package:analyzer/src/generated/engine.dart' 30 import 'package:analyzer/src/generated/engine.dart'
31 hide AnalysisContextImpl, AnalysisTask; 31 hide AnalysisCache, AnalysisContextImpl, AnalysisTask;
32 import 'package:analyzer/src/generated/java_engine.dart'; 32 import 'package:analyzer/src/generated/java_engine.dart';
33 import 'package:analyzer/src/generated/source.dart'; 33 import 'package:analyzer/src/generated/source.dart';
34 import 'package:analyzer/src/generated/utilities_collection.dart'; 34 import 'package:analyzer/src/generated/utilities_collection.dart';
35 import 'package:analyzer/src/generated/utilities_general.dart'; 35 import 'package:analyzer/src/generated/utilities_general.dart';
36 import 'package:analyzer/src/task/dart.dart'; 36 import 'package:analyzer/src/task/dart.dart';
37 import 'package:analyzer/src/task/html.dart';
37 import 'package:analyzer/task/dart.dart'; 38 import 'package:analyzer/task/dart.dart';
39 import 'package:analyzer/task/general.dart';
40 import 'package:analyzer/task/html.dart';
38 import 'package:analyzer/task/model.dart'; 41 import 'package:analyzer/task/model.dart';
39 import 'package:plugin/plugin.dart'; 42 import 'package:plugin/plugin.dart';
40 43
41 /** 44 /**
42 * A function that can be used to generate HTML output into the given [buffer]. 45 * A function that can be used to generate HTML output into the given [buffer].
43 * The HTML that is generated must be valid (special characters must already be 46 * The HTML that is generated must be valid (special characters must already be
44 * encoded). 47 * encoded).
45 */ 48 */
46 typedef void HtmlGenerator(StringBuffer buffer); 49 typedef void HtmlGenerator(StringBuffer buffer);
47 50
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after
264 return unit; 267 return unit;
265 } 268 }
266 unit = entry.getValue(RESOLVED_UNIT8); 269 unit = entry.getValue(RESOLVED_UNIT8);
267 if (unit != null) { 270 if (unit != null) {
268 return unit; 271 return unit;
269 } 272 }
270 return entry.getValue(RESOLVED_UNIT); 273 return entry.getValue(RESOLVED_UNIT);
271 } 274 }
272 275
273 /** 276 /**
277 * Return a list of the result descriptors whose state should be displayed for
278 * the given cache [entry].
279 */
280 List<ResultDescriptor> _getExpectedResults(CacheEntry entry) {
281 AnalysisTarget target = entry.target;
282 Set<ResultDescriptor> results = entry.nonInvalidResults.toSet();
283 if (target is Source) {
284 String name = target.shortName;
285 results.add(CONTENT);
286 results.add(LINE_INFO);
287 results.add(MODIFICATION_TIME);
288 if (AnalysisEngine.isDartFileName(name)) {
289 results.add(BUILD_DIRECTIVES_ERRORS);
290 results.add(BUILD_LIBRARY_ERRORS);
291 results.add(CONTAINING_LIBRARIES);
292 results.add(DART_ERRORS);
293 results.add(EXPLICITLY_IMPORTED_LIBRARIES);
294 results.add(EXPORT_SOURCE_CLOSURE);
295 results.add(EXPORTED_LIBRARIES);
296 results.add(IMPORT_EXPORT_SOURCE_CLOSURE);
297 results.add(IMPORTED_LIBRARIES);
298 results.add(INCLUDED_PARTS);
299 results.add(IS_CLIENT);
300 results.add(IS_LAUNCHABLE);
301 results.add(LIBRARY_ELEMENT1);
302 results.add(LIBRARY_ELEMENT2);
303 results.add(LIBRARY_ELEMENT3);
304 results.add(LIBRARY_ELEMENT4);
305 results.add(LIBRARY_ELEMENT5);
306 results.add(LIBRARY_ELEMENT);
307 results.add(LIBRARY_ERRORS_READY);
308 results.add(PARSE_ERRORS);
309 results.add(PARSED_UNIT);
310 results.add(REFERENCED_NAMES);
311 results.add(SCAN_ERRORS);
312 results.add(SOURCE_KIND);
313 results.add(TOKEN_STREAM);
314 results.add(UNITS);
315 } else if (AnalysisEngine.isHtmlFileName(name)) {
316 results.add(DART_SCRIPTS);
317 results.add(HTML_DOCUMENT);
318 results.add(HTML_DOCUMENT_ERRORS);
319 results.add(HTML_ERRORS);
320 results.add(REFERENCED_LIBRARIES);
321 }
322 } else if (target is LibrarySpecificUnit) {
323 results.add(COMPILATION_UNIT_CONSTANTS);
324 results.add(COMPILATION_UNIT_ELEMENT);
325 results.add(HINTS);
326 results.add(INFER_STATIC_VARIABLE_TYPES_ERRORS);
327 results.add(INFERABLE_STATIC_VARIABLES_IN_UNIT);
328 results.add(LIBRARY_UNIT_ERRORS);
329 results.add(PARTIALLY_RESOLVE_REFERENCES_ERRORS);
330 results.add(RESOLVE_FUNCTION_BODIES_ERRORS);
331 results.add(RESOLVE_TYPE_NAMES_ERRORS);
332 results.add(RESOLVED_UNIT1);
333 results.add(RESOLVED_UNIT2);
334 results.add(RESOLVED_UNIT3);
335 results.add(RESOLVED_UNIT4);
336 results.add(RESOLVED_UNIT5);
337 results.add(RESOLVED_UNIT6);
338 results.add(RESOLVED_UNIT7);
339 results.add(RESOLVED_UNIT8);
340 results.add(RESOLVED_UNIT);
341 results.add(USED_IMPORTED_ELEMENTS);
342 results.add(USED_LOCAL_ELEMENTS);
343 results.add(VARIABLE_REFERENCE_ERRORS);
344 results.add(VERIFY_ERRORS);
345 } else if (target is ConstantEvaluationTarget) {
346 results.add(CONSTANT_DEPENDENCIES);
347 results.add(CONSTANT_VALUE);
348 if (target is VariableElement) {
349 results.add(INFER_STATIC_VARIABLE_ERRORS);
350 results.add(INFERABLE_STATIC_VARIABLE_DEPENDENCIES);
351 results.add(INFERRED_STATIC_VARIABLE);
352 }
353 } else if (target is AnalysisContextTarget) {
354 results.add(TYPE_PROVIDER);
355 }
356 return results.toList();
357 }
358
359 /**
274 * Return `true` if the given analysis [context] has at least one entry with 360 * Return `true` if the given analysis [context] has at least one entry with
275 * an exception. 361 * an exception.
276 */ 362 */
277 bool _hasException(InternalAnalysisContext context) { 363 bool _hasException(InternalAnalysisContext context) {
278 MapIterator<AnalysisTarget, CacheEntry> iterator = 364 MapIterator<AnalysisTarget, CacheEntry> iterator =
279 context.analysisCache.iterator(); 365 context.analysisCache.iterator();
280 while (iterator.moveNext()) { 366 while (iterator.moveNext()) {
281 if (iterator.value.exception != null) { 367 if (iterator.value.exception != null) {
282 return true; 368 return true;
283 } 369 }
(...skipping 20 matching lines...) Expand all
304 */ 390 */
305 void _returnAnalysisPerformance(HttpRequest request) { 391 void _returnAnalysisPerformance(HttpRequest request) {
306 AnalysisServer analysisServer = _server.analysisServer; 392 AnalysisServer analysisServer = _server.analysisServer;
307 if (analysisServer == null) { 393 if (analysisServer == null) {
308 return _returnFailure(request, 'Analysis server is not running'); 394 return _returnFailure(request, 'Analysis server is not running');
309 } 395 }
310 _writeResponse(request, (StringBuffer buffer) { 396 _writeResponse(request, (StringBuffer buffer) {
311 _writePage(buffer, 'Analysis Server - Analysis Performance', [], 397 _writePage(buffer, 'Analysis Server - Analysis Performance', [],
312 (StringBuffer buffer) { 398 (StringBuffer buffer) {
313 buffer.write('<h3>Analysis Performance</h3>'); 399 buffer.write('<h3>Analysis Performance</h3>');
314 // 400 _writeTwoColumns(buffer, (StringBuffer buffer) {
315 // Write performance tags. 401 //
316 // 402 // Write performance tags.
317 buffer.write('<p><b>Performance tag data</b></p>'); 403 //
318 buffer.write( 404 buffer.write('<p><b>Performance tag data</b></p>');
319 '<table style="border-collapse: separate; border-spacing: 10px 5px;" >'); 405 buffer.write(
320 _writeRow(buffer, ['Time (in ms)', 'Percent', 'Tag name'], 406 '<table style="border-collapse: separate; border-spacing: 10px 5px ;">');
321 header: true); 407 _writeRow(buffer, ['Time (in ms)', 'Percent', 'Tag name'],
322 // prepare sorted tags 408 header: true);
323 List<PerformanceTag> tags = PerformanceTag.all.toList(); 409 // prepare sorted tags
324 tags.remove(ServerPerformanceStatistics.idle); 410 List<PerformanceTag> tags = PerformanceTag.all.toList();
325 tags.sort((a, b) => b.elapsedMs - a.elapsedMs); 411 tags.remove(ServerPerformanceStatistics.idle);
326 // prepare total time 412 tags.sort((a, b) => b.elapsedMs - a.elapsedMs);
327 int totalTagTime = 0; 413 // prepare total time
328 tags.forEach((PerformanceTag tag) { 414 int totalTagTime = 0;
329 totalTagTime += tag.elapsedMs; 415 tags.forEach((PerformanceTag tag) {
416 totalTagTime += tag.elapsedMs;
417 });
418 // write rows
419 void writeRow(PerformanceTag tag) {
420 double percent = (tag.elapsedMs * 100) / totalTagTime;
421 String percentStr = '${percent.toStringAsFixed(2)}%';
422 _writeRow(buffer, [tag.elapsedMs, percentStr, tag.label],
423 classes: ["right", "right", null]);
424 }
425 tags.forEach(writeRow);
426 buffer.write('</table>');
427 //
428 // Write target counts.
429 //
430 void incrementCount(Map<String, int> counts, String key) {
431 int count = counts[key];
432 if (count == null) {
433 count = 1;
434 } else {
435 count++;
436 }
437 counts[key] = count;
438 }
439 Set<AnalysisTarget> countedTargets = new HashSet<AnalysisTarget>();
440 Map<String, int> sourceTypeCounts = new HashMap<String, int>();
441 Map<String, int> typeCounts = new HashMap<String, int>();
442 analysisServer.folderMap
443 .forEach((Folder folder, InternalAnalysisContext context) {
444 AnalysisCache cache = context.analysisCache;
445 MapIterator<AnalysisTarget, CacheEntry> iterator = cache.iterator();
446 while (iterator.moveNext()) {
447 AnalysisTarget target = iterator.key;
448 if (countedTargets.add(target)) {
449 if (target is Source) {
450 String name = target.fullName;
451 String sourceName;
452 if (AnalysisEngine.isDartFileName(name)) {
453 if (iterator.value.explicitlyAdded) {
454 sourceName = 'Dart file (explicit)';
455 } else {
456 sourceName = 'Dart file (implicit)';
457 }
458 } else if (AnalysisEngine.isHtmlFileName(name)) {
459 if (iterator.value.explicitlyAdded) {
460 sourceName = 'Html file (explicit)';
461 } else {
462 sourceName = 'Html file (implicit)';
463 }
464 } else {
465 if (iterator.value.explicitlyAdded) {
466 sourceName = 'Unknown file (explicit)';
467 } else {
468 sourceName = 'Unknown file (implicit)';
469 }
470 }
471 incrementCount(sourceTypeCounts, sourceName);
472 } else if (target is ConstantEvaluationTarget) {
473 incrementCount(typeCounts, 'ConstantEvaluationTarget');
474 } else {
475 String typeName = target.runtimeType.toString();
476 incrementCount(typeCounts, typeName);
477 }
478 }
479 }
480 });
481 List<String> sourceTypeNames = sourceTypeCounts.keys.toList();
482 sourceTypeNames.sort();
483 List<String> typeNames = typeCounts.keys.toList();
484 typeNames.sort();
485
486 buffer.write('<p><b>Target counts</b></p>');
487 buffer.write(
488 '<table style="border-collapse: separate; border-spacing: 10px 5px ;">');
489 _writeRow(buffer, ['Target', 'Count'], header: true);
490 for (String sourceTypeName in sourceTypeNames) {
491 _writeRow(
492 buffer, [sourceTypeName, sourceTypeCounts[sourceTypeName]],
493 classes: [null, "right"]);
494 }
495 for (String typeName in typeNames) {
496 _writeRow(buffer, [typeName, typeCounts[typeName]],
497 classes: [null, "right"]);
498 }
499 buffer.write('</table>');
500 }, (StringBuffer buffer) {
501 //
502 // Write task model timing information.
503 //
504 buffer.write('<p><b>Task performace data</b></p>');
505 buffer.write(
506 '<table style="border-collapse: separate; border-spacing: 10px 5px ;">');
507 _writeRow(
508 buffer,
509 [
510 'Task Name',
511 'Count',
512 'Total Time (in ms)',
513 'Average Time (in ms)'
514 ],
515 header: true);
516
517 Map<Type, int> countMap = AnalysisTask.countMap;
518 Map<Type, Stopwatch> stopwatchMap = AnalysisTask.stopwatchMap;
519 List<Type> taskClasses = stopwatchMap.keys.toList();
520 taskClasses.sort((Type first, Type second) =>
521 first.toString().compareTo(second.toString()));
522 int totalTaskTime = 0;
523 taskClasses.forEach((Type taskClass) {
524 int count = countMap[taskClass];
525 if (count == null) {
526 count = 0;
527 }
528 int taskTime = stopwatchMap[taskClass].elapsedMilliseconds;
529 totalTaskTime += taskTime;
530 _writeRow(buffer, [
531 taskClass.toString(),
532 count,
533 taskTime,
534 count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3)
535 ], classes: [
536 null,
537 "right",
538 "right",
539 "right"
540 ]);
541 });
542 _writeRow(buffer, ['Total', '-', totalTaskTime, '-'],
543 classes: [null, "right", "right", "right"]);
544 buffer.write('</table>');
330 }); 545 });
331 // write rows
332 void writeRow(PerformanceTag tag) {
333 double percent = (tag.elapsedMs * 100) / totalTagTime;
334 String percentStr = '${percent.toStringAsFixed(2)}%';
335 _writeRow(buffer, [tag.elapsedMs, percentStr, tag.label],
336 classes: ["right", "right", null]);
337 }
338 tags.forEach(writeRow);
339 buffer.write('</table>');
340 //
341 // Write task model timing information.
342 //
343 buffer.write('<p><b>Task performace data</b></p>');
344 buffer.write(
345 '<table style="border-collapse: separate; border-spacing: 10px 5px;" >');
346 _writeRow(
347 buffer,
348 [
349 'Task Name',
350 'Count',
351 'Total Time (in ms)',
352 'Average Time (in ms)'
353 ],
354 header: true);
355
356 Map<Type, int> countMap = AnalysisTask.countMap;
357 Map<Type, Stopwatch> stopwatchMap = AnalysisTask.stopwatchMap;
358 List<Type> taskClasses = stopwatchMap.keys.toList();
359 taskClasses.sort((Type first, Type second) =>
360 first.toString().compareTo(second.toString()));
361 int totalTaskTime = 0;
362 taskClasses.forEach((Type taskClass) {
363 int count = countMap[taskClass];
364 if (count == null) {
365 count = 0;
366 }
367 int taskTime = stopwatchMap[taskClass].elapsedMilliseconds;
368 totalTaskTime += taskTime;
369 _writeRow(buffer, [
370 taskClass.toString(),
371 count,
372 taskTime,
373 count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3)
374 ], classes: [
375 null,
376 "right",
377 "right",
378 "right"
379 ]);
380 });
381 _writeRow(buffer, ['Total', '-', totalTaskTime, '-'],
382 classes: [null, "right", "right", "right"]);
383 buffer.write('</table>');
384 }); 546 });
385 }); 547 });
386 } 548 }
387 549
388 /** 550 /**
389 * Return a response containing information about an AST structure. 551 * Return a response containing information about an AST structure.
390 */ 552 */
391 void _returnAst(HttpRequest request) { 553 void _returnAst(HttpRequest request) {
392 AnalysisServer analysisServer = _server.analysisServer; 554 AnalysisServer analysisServer = _server.analysisServer;
393 if (analysisServer == null) { 555 if (analysisServer == null) {
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
510 buffer.write(folder.path); 672 buffer.write(folder.path);
511 } else { 673 } else {
512 buffer.write(makeLink( 674 buffer.write(makeLink(
513 CACHE_ENTRY_PATH, 675 CACHE_ENTRY_PATH,
514 { 676 {
515 CONTEXT_QUERY_PARAM: folder.path, 677 CONTEXT_QUERY_PARAM: folder.path,
516 SOURCE_QUERY_PARAM: sourceUri 678 SOURCE_QUERY_PARAM: sourceUri
517 }, 679 },
518 HTML_ESCAPE.convert(folder.path))); 680 HTML_ESCAPE.convert(folder.path)));
519 } 681 }
520 CacheEntry sourceEntry = 682 if (entries == null) {
521 entries.firstWhere((CacheEntry entry) => entry.target is Source); 683 buffer.write(' (file does not exist)');
522 if (sourceEntry == null) {
523 buffer.write(' (missing source entry)');
524 } else if (sourceEntry.explicitlyAdded) {
525 buffer.write(' (explicit)');
526 } else { 684 } else {
527 buffer.write(' (implicit)'); 685 CacheEntry sourceEntry = entries
686 .firstWhere((CacheEntry entry) => entry.target is Source);
687 if (sourceEntry == null) {
688 buffer.write(' (missing source entry)');
689 } else if (sourceEntry.explicitlyAdded) {
690 buffer.write(' (explicit)');
691 } else {
692 buffer.write(' (implicit)');
693 }
528 } 694 }
529 }); 695 });
530 buffer.write('</p>'); 696 buffer.write('</p>');
531 697
532 if (entries == null) { 698 if (entries == null) {
533 buffer.write('<p>Not being analyzed in this context.</p>'); 699 buffer.write('<p>Not being analyzed in this context.</p>');
534 return; 700 return;
535 } 701 }
536 for (CacheEntry entry in entries) { 702 for (CacheEntry entry in entries) {
537 Map<String, String> linkParameters = <String, String>{ 703 Map<String, String> linkParameters = <String, String>{
538 CONTEXT_QUERY_PARAM: folder.path, 704 CONTEXT_QUERY_PARAM: folder.path,
539 SOURCE_QUERY_PARAM: sourceUri 705 SOURCE_QUERY_PARAM: sourceUri
540 }; 706 };
541 List<ResultDescriptor> results = entry.nonInvalidResults; 707 List<ResultDescriptor> results = _getExpectedResults(entry);
542 results.sort((ResultDescriptor first, ResultDescriptor second) => 708 results.sort((ResultDescriptor first, ResultDescriptor second) =>
543 first.toString().compareTo(second.toString())); 709 first.toString().compareTo(second.toString()));
544 710
545 buffer.write('<h3>'); 711 buffer.write('<h3>');
546 buffer.write(HTML_ESCAPE.convert(entry.target.toString())); 712 buffer.write(HTML_ESCAPE.convert(entry.target.toString()));
547 buffer.write('</h3>'); 713 buffer.write('</h3>');
548 buffer.write('<dl>'); 714 buffer.write('<dl>');
549 buffer.write('<dt>time</dt><dd>'); 715 buffer.write('<dt>time</dt><dd>');
550 buffer.write(entry.modificationTime); 716 buffer.write(entry.modificationTime);
551 buffer.write('</dd>'); 717 buffer.write('</dd>');
552 for (ResultDescriptor result in results) { 718 for (ResultDescriptor result in results) {
553 ResultData data = entry.getResultData(result); 719 CacheState state = entry.getState(result);
554 String descriptorName = HTML_ESCAPE.convert(result.toString()); 720 String descriptorName = HTML_ESCAPE.convert(result.toString());
555 String descriptorState = HTML_ESCAPE.convert(data.state.toString()); 721 String descriptorState = HTML_ESCAPE.convert(state.toString());
556 buffer.write('<dt>$descriptorName ($descriptorState)</dt><dd>'); 722 buffer.write('<dt>$descriptorName ($descriptorState)</dt><dd>');
557 try { 723 if (state == CacheState.VALID) {
558 _writeValueAsHtml(buffer, data.value, linkParameters); 724 try {
559 } catch (exception) { 725 _writeValueAsHtml(buffer, entry.getValue(result), linkParameters );
560 buffer.write('(${HTML_ESCAPE.convert(exception.toString())})'); 726 } catch (exception) {
727 buffer.write('(${HTML_ESCAPE.convert(exception.toString())})');
728 }
561 } 729 }
562 buffer.write('</dd>'); 730 buffer.write('</dd>');
563 } 731 }
564 if (entry.exception != null) { 732 if (entry.exception != null) {
565 buffer.write('<dt>exception</dt><dd>'); 733 buffer.write('<dt>exception</dt><dd>');
566 _writeException(buffer, entry.exception); 734 _writeException(buffer, entry.exception);
567 buffer.write('</dd>'); 735 buffer.write('</dd>');
568 } 736 }
569 buffer.write('</dl>'); 737 buffer.write('</dl>');
570 } 738 }
(...skipping 1068 matching lines...) Expand 10 before | Expand all | Expand 10 after
1639 */ 1807 */
1640 static String makeLink( 1808 static String makeLink(
1641 String path, Map<String, String> params, String innerHtml, 1809 String path, Map<String, String> params, String innerHtml,
1642 [bool hasError = false]) { 1810 [bool hasError = false]) {
1643 Uri uri = new Uri(path: path, queryParameters: params); 1811 Uri uri = new Uri(path: path, queryParameters: params);
1644 String href = HTML_ESCAPE.convert(uri.toString()); 1812 String href = HTML_ESCAPE.convert(uri.toString());
1645 String classAttribute = hasError ? ' class="error"' : ''; 1813 String classAttribute = hasError ? ' class="error"' : '';
1646 return '<a href="$href"$classAttribute>$innerHtml</a>'; 1814 return '<a href="$href"$classAttribute>$innerHtml</a>';
1647 } 1815 }
1648 } 1816 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698