| OLD | NEW |
| 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.get_handler; | 5 library analysis_server.src.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'; |
| 11 import 'dart:math'; | 11 import 'dart:math'; |
| 12 | 12 |
| 13 import 'package:analyzer/plugin/plugin.dart'; |
| 13 import 'package:analysis_server/src/analysis_server.dart'; | 14 import 'package:analysis_server/src/analysis_server.dart'; |
| 14 import 'package:analysis_server/src/domain_completion.dart'; | 15 import 'package:analysis_server/src/domain_completion.dart'; |
| 15 import 'package:analysis_server/src/domain_execution.dart'; | 16 import 'package:analysis_server/src/domain_execution.dart'; |
| 16 import 'package:analysis_server/src/operation/operation.dart'; | 17 import 'package:analysis_server/src/operation/operation.dart'; |
| 17 import 'package:analysis_server/src/operation/operation_analysis.dart'; | 18 import 'package:analysis_server/src/operation/operation_analysis.dart'; |
| 18 import 'package:analysis_server/src/operation/operation_queue.dart'; | 19 import 'package:analysis_server/src/operation/operation_queue.dart'; |
| 19 import 'package:analysis_server/src/protocol.dart' hide Element; | 20 import 'package:analysis_server/src/protocol.dart' hide Element; |
| 20 import 'package:analysis_server/src/services/index/index.dart'; | 21 import 'package:analysis_server/src/services/index/index.dart'; |
| 21 import 'package:analysis_server/src/services/index/local_index.dart'; | 22 import 'package:analysis_server/src/services/index/local_index.dart'; |
| 22 import 'package:analysis_server/src/services/index/store/split_store.dart'; | 23 import 'package:analysis_server/src/services/index/store/split_store.dart'; |
| (...skipping 372 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 395 return _returnFailure(request, 'Invalid context: $contextFilter'); | 396 return _returnFailure(request, 'Invalid context: $contextFilter'); |
| 396 } | 397 } |
| 397 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; | 398 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; |
| 398 if (sourceUri == null) { | 399 if (sourceUri == null) { |
| 399 return _returnFailure( | 400 return _returnFailure( |
| 400 request, 'Query parameter $SOURCE_QUERY_PARAM required'); | 401 request, 'Query parameter $SOURCE_QUERY_PARAM required'); |
| 401 } | 402 } |
| 402 | 403 |
| 403 List<Folder> allContexts = <Folder>[]; | 404 List<Folder> allContexts = <Folder>[]; |
| 404 Map<Folder, SourceEntry> entryMap = new HashMap<Folder, SourceEntry>(); | 405 Map<Folder, SourceEntry> entryMap = new HashMap<Folder, SourceEntry>(); |
| 405 analysisServer.folderMap.forEach( | 406 analysisServer.folderMap |
| 406 (Folder folder, AnalysisContextImpl context) { | 407 .forEach((Folder folder, AnalysisContextImpl context) { |
| 407 Source source = context.sourceFactory.forUri(sourceUri); | 408 Source source = context.sourceFactory.forUri(sourceUri); |
| 408 if (source != null) { | 409 if (source != null) { |
| 409 SourceEntry entry = context.getReadableSourceEntryOrNull(source); | 410 SourceEntry entry = context.getReadableSourceEntryOrNull(source); |
| 410 if (entry != null) { | 411 if (entry != null) { |
| 411 allContexts.add(folder); | 412 allContexts.add(folder); |
| 412 entryMap[folder] = entry; | 413 entryMap[folder] = entry; |
| 413 } | 414 } |
| 414 } | 415 } |
| 415 }); | 416 }); |
| 416 allContexts.sort((Folder firstFolder, Folder secondFolder) => | 417 allContexts.sort((Folder firstFolder, Folder secondFolder) => |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 452 if (entry == null) { | 453 if (entry == null) { |
| 453 buffer.write('<p>Not being analyzed in this context.</p>'); | 454 buffer.write('<p>Not being analyzed in this context.</p>'); |
| 454 return; | 455 return; |
| 455 } | 456 } |
| 456 Map<String, String> linkParameters = <String, String>{ | 457 Map<String, String> linkParameters = <String, String>{ |
| 457 CONTEXT_QUERY_PARAM: folder.path, | 458 CONTEXT_QUERY_PARAM: folder.path, |
| 458 SOURCE_QUERY_PARAM: sourceUri | 459 SOURCE_QUERY_PARAM: sourceUri |
| 459 }; | 460 }; |
| 460 | 461 |
| 461 buffer.write('<h3>Library Independent</h3>'); | 462 buffer.write('<h3>Library Independent</h3>'); |
| 462 _writeDescriptorTable(buffer, | 463 _writeDescriptorTable(buffer, entry.descriptors, entry.getState, |
| 463 entry.descriptors, entry.getState, entry.getValue, linkParameters); | 464 entry.getValue, linkParameters); |
| 464 if (entry is DartEntry) { | 465 if (entry is DartEntry) { |
| 465 for (Source librarySource in entry.containingLibraries) { | 466 for (Source librarySource in entry.containingLibraries) { |
| 466 String libraryName = HTML_ESCAPE.convert(librarySource.fullName); | 467 String libraryName = HTML_ESCAPE.convert(librarySource.fullName); |
| 467 buffer.write('<h3>In library $libraryName:</h3>'); | 468 buffer.write('<h3>In library $libraryName:</h3>'); |
| 468 _writeDescriptorTable(buffer, entry.libraryDescriptors, | 469 _writeDescriptorTable(buffer, entry.libraryDescriptors, |
| 469 (DataDescriptor descriptor) => | 470 (DataDescriptor descriptor) => |
| 470 entry.getStateInLibrary(descriptor, librarySource), | 471 entry.getStateInLibrary(descriptor, librarySource), |
| 471 (DataDescriptor descriptor) => | 472 (DataDescriptor descriptor) => |
| 472 entry.getValueInLibrary(descriptor, librarySource), | 473 entry.getValueInLibrary(descriptor, librarySource), |
| 473 linkParameters); | 474 linkParameters); |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 568 int requestCount = perf.requestCount; | 569 int requestCount = perf.requestCount; |
| 569 num averageLatency = requestCount > 0 | 570 num averageLatency = requestCount > 0 |
| 570 ? (perf.requestLatency / requestCount).round() | 571 ? (perf.requestLatency / requestCount).round() |
| 571 : 0; | 572 : 0; |
| 572 int maximumLatency = perf.maxLatency; | 573 int maximumLatency = perf.maxLatency; |
| 573 num slowRequestPercent = requestCount > 0 | 574 num slowRequestPercent = requestCount > 0 |
| 574 ? (perf.slowRequestCount * 100 / requestCount).round() | 575 ? (perf.slowRequestCount * 100 / requestCount).round() |
| 575 : 0; | 576 : 0; |
| 576 buffer.write('<h4>Startup</h4>'); | 577 buffer.write('<h4>Startup</h4>'); |
| 577 buffer.write('<table>'); | 578 buffer.write('<table>'); |
| 578 _writeRow( | 579 _writeRow(buffer, [requestCount, 'requests'], |
| 579 buffer, [requestCount, 'requests'], classes: ["right", null]); | 580 classes: ["right", null]); |
| 580 _writeRow(buffer, [averageLatency, 'ms average latency'], | 581 _writeRow(buffer, [averageLatency, 'ms average latency'], |
| 581 classes: ["right", null]); | 582 classes: ["right", null]); |
| 582 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], | 583 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], |
| 583 classes: ["right", null]); | 584 classes: ["right", null]); |
| 584 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], | 585 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], |
| 585 classes: ["right", null]); | 586 classes: ["right", null]); |
| 586 if (analysisServer.performanceAfterStartup != null) { | 587 if (analysisServer.performanceAfterStartup != null) { |
| 587 int startupTime = analysisServer.performanceAfterStartup.startTime - | 588 int startupTime = analysisServer.performanceAfterStartup.startTime - |
| 588 perf.startTime; | 589 perf.startTime; |
| 589 _writeRow( | 590 _writeRow( |
| 590 buffer, [startupTime, 'ms for initial analysis to complete']); | 591 buffer, [startupTime, 'ms for initial analysis to complete']); |
| 591 } | 592 } |
| 592 buffer.write('</table>'); | 593 buffer.write('</table>'); |
| 593 }, (StringBuffer buffer) { | 594 }, (StringBuffer buffer) { |
| 594 ServerPerformance perf = analysisServer.performanceAfterStartup; | 595 ServerPerformance perf = analysisServer.performanceAfterStartup; |
| 595 if (perf == null) { | 596 if (perf == null) { |
| 596 return; | 597 return; |
| 597 } | 598 } |
| 598 int requestCount = perf.requestCount; | 599 int requestCount = perf.requestCount; |
| 599 num averageLatency = requestCount > 0 | 600 num averageLatency = requestCount > 0 |
| 600 ? (perf.requestLatency * 10 / requestCount).round() / 10 | 601 ? (perf.requestLatency * 10 / requestCount).round() / 10 |
| 601 : 0; | 602 : 0; |
| 602 int maximumLatency = perf.maxLatency; | 603 int maximumLatency = perf.maxLatency; |
| 603 num slowRequestPercent = requestCount > 0 | 604 num slowRequestPercent = requestCount > 0 |
| 604 ? (perf.slowRequestCount * 100 / requestCount).round() | 605 ? (perf.slowRequestCount * 100 / requestCount).round() |
| 605 : 0; | 606 : 0; |
| 606 buffer.write('<h4>Current</h4>'); | 607 buffer.write('<h4>Current</h4>'); |
| 607 buffer.write('<table>'); | 608 buffer.write('<table>'); |
| 608 _writeRow( | 609 _writeRow(buffer, [requestCount, 'requests'], |
| 609 buffer, [requestCount, 'requests'], classes: ["right", null]); | 610 classes: ["right", null]); |
| 610 _writeRow(buffer, [averageLatency, 'ms average latency'], | 611 _writeRow(buffer, [averageLatency, 'ms average latency'], |
| 611 classes: ["right", null]); | 612 classes: ["right", null]); |
| 612 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], | 613 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], |
| 613 classes: ["right", null]); | 614 classes: ["right", null]); |
| 614 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], | 615 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], |
| 615 classes: ["right", null]); | 616 classes: ["right", null]); |
| 616 buffer.write('</table>'); | 617 buffer.write('</table>'); |
| 617 }); | 618 }); |
| 618 }); | 619 }); |
| 619 }); | 620 }); |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 705 buffer.write( | 706 buffer.write( |
| 706 makeLink(OVERLAY_PATH, {PATH_PARAM: fileName}, 'overlay')); | 707 makeLink(OVERLAY_PATH, {PATH_PARAM: fileName}, 'overlay')); |
| 707 } | 708 } |
| 708 buffer.write('</td></tr>'); | 709 buffer.write('</td></tr>'); |
| 709 } | 710 } |
| 710 buffer.write('</table>'); | 711 buffer.write('</table>'); |
| 711 } | 712 } |
| 712 } | 713 } |
| 713 | 714 |
| 714 _writeResponse(request, (StringBuffer buffer) { | 715 _writeResponse(request, (StringBuffer buffer) { |
| 715 _writePage(buffer, 'Analysis Server - Context', [ | 716 _writePage(buffer, 'Analysis Server - Context', |
| 716 'Context: $contextFilter' | 717 ['Context: $contextFilter'], (StringBuffer buffer) { |
| 717 ], (StringBuffer buffer) { | |
| 718 List headerRowText = ['Context']; | 718 List headerRowText = ['Context']; |
| 719 headerRowText.addAll(CacheState.values); | 719 headerRowText.addAll(CacheState.values); |
| 720 buffer.write('<h3>Summary</h3>'); | 720 buffer.write('<h3>Summary</h3>'); |
| 721 buffer.write('<table>'); | 721 buffer.write('<table>'); |
| 722 _writeRow(buffer, headerRowText, header: true); | 722 _writeRow(buffer, headerRowText, header: true); |
| 723 AnalysisContextStatistics statistics = context.statistics; | 723 AnalysisContextStatistics statistics = context.statistics; |
| 724 statistics.cacheRows.forEach((AnalysisContextStatistics_CacheRow row) { | 724 statistics.cacheRows.forEach((AnalysisContextStatistics_CacheRow row) { |
| 725 List rowText = [row.name]; | 725 List rowText = [row.name]; |
| 726 for (CacheState state in CacheState.values) { | 726 for (CacheState state in CacheState.values) { |
| 727 String text = row.getCount(state).toString(); | 727 String text = row.getCount(state).toString(); |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 826 } | 826 } |
| 827 String name = request.uri.queryParameters[INDEX_ELEMENT_NAME]; | 827 String name = request.uri.queryParameters[INDEX_ELEMENT_NAME]; |
| 828 if (name == null) { | 828 if (name == null) { |
| 829 return _returnFailure( | 829 return _returnFailure( |
| 830 request, 'Query parameter $INDEX_ELEMENT_NAME required'); | 830 request, 'Query parameter $INDEX_ELEMENT_NAME required'); |
| 831 } | 831 } |
| 832 if (index is LocalIndex) { | 832 if (index is LocalIndex) { |
| 833 Map<List<String>, List<InspectLocation>> relations = | 833 Map<List<String>, List<InspectLocation>> relations = |
| 834 await index.findElementsByName(name); | 834 await index.findElementsByName(name); |
| 835 _writeResponse(request, (StringBuffer buffer) { | 835 _writeResponse(request, (StringBuffer buffer) { |
| 836 _writePage(buffer, 'Analysis Server - Index Elements', [ | 836 _writePage(buffer, 'Analysis Server - Index Elements', ['Name: $name'], |
| 837 'Name: $name' | 837 (StringBuffer buffer) { |
| 838 ], (StringBuffer buffer) { | |
| 839 buffer.write('<table border="1">'); | 838 buffer.write('<table border="1">'); |
| 840 _writeRow(buffer, ['Element', 'Relationship', 'Location'], | 839 _writeRow(buffer, ['Element', 'Relationship', 'Location'], |
| 841 header: true); | 840 header: true); |
| 842 relations.forEach( | 841 relations.forEach((List<String> elementPath, |
| 843 (List<String> elementPath, List<InspectLocation> relations) { | 842 List<InspectLocation> relations) { |
| 844 String elementLocation = elementPath.join(' '); | 843 String elementLocation = elementPath.join(' '); |
| 845 relations.forEach((InspectLocation location) { | 844 relations.forEach((InspectLocation location) { |
| 846 var relString = location.relationship.identifier; | 845 var relString = location.relationship.identifier; |
| 847 var locString = '${location.path} offset=${location.offset} ' | 846 var locString = '${location.path} offset=${location.offset} ' |
| 848 'length=${location.length} flags=${location.flags}'; | 847 'length=${location.length} flags=${location.flags}'; |
| 849 _writeRow(buffer, [elementLocation, relString, locString]); | 848 _writeRow(buffer, [elementLocation, relString, locString]); |
| 850 }); | 849 }); |
| 851 }); | 850 }); |
| 852 buffer.write('</table>'); | 851 buffer.write('</table>'); |
| 853 }); | 852 }); |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 905 /** | 904 /** |
| 906 * Return a response indicating the status of the analysis server. | 905 * Return a response indicating the status of the analysis server. |
| 907 */ | 906 */ |
| 908 void _returnServerStatus(HttpRequest request) { | 907 void _returnServerStatus(HttpRequest request) { |
| 909 _writeResponse(request, (StringBuffer buffer) { | 908 _writeResponse(request, (StringBuffer buffer) { |
| 910 _writePage(buffer, 'Analysis Server - Status', [], (StringBuffer buffer) { | 909 _writePage(buffer, 'Analysis Server - Status', [], (StringBuffer buffer) { |
| 911 if (_writeServerStatus(buffer)) { | 910 if (_writeServerStatus(buffer)) { |
| 912 _writeAnalysisStatus(buffer); | 911 _writeAnalysisStatus(buffer); |
| 913 _writeEditStatus(buffer); | 912 _writeEditStatus(buffer); |
| 914 _writeExecutionStatus(buffer); | 913 _writeExecutionStatus(buffer); |
| 914 _writePluginStatus(buffer); |
| 915 _writeRecentOutput(buffer); | 915 _writeRecentOutput(buffer); |
| 916 } | 916 } |
| 917 }); | 917 }); |
| 918 }); | 918 }); |
| 919 } | 919 } |
| 920 | 920 |
| 921 /** | 921 /** |
| 922 * Return an error in response to an unrecognized request received by the HTTP | 922 * Return an error in response to an unrecognized request received by the HTTP |
| 923 * server. | 923 * server. |
| 924 */ | 924 */ |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1011 buffer, 'Enable strict call checks', options.enableStrictCallChecks); | 1011 buffer, 'Enable strict call checks', options.enableStrictCallChecks); |
| 1012 _writeOption(buffer, 'Generate hints', options.hint); | 1012 _writeOption(buffer, 'Generate hints', options.hint); |
| 1013 _writeOption(buffer, 'Generate dart2js hints', options.dart2jsHint); | 1013 _writeOption(buffer, 'Generate dart2js hints', options.dart2jsHint); |
| 1014 _writeOption(buffer, 'Generate errors in implicit files', | 1014 _writeOption(buffer, 'Generate errors in implicit files', |
| 1015 options.generateImplicitErrors); | 1015 options.generateImplicitErrors); |
| 1016 _writeOption( | 1016 _writeOption( |
| 1017 buffer, 'Generate errors in SDK files', options.generateSdkErrors); | 1017 buffer, 'Generate errors in SDK files', options.generateSdkErrors); |
| 1018 _writeOption(buffer, 'Incremental resolution', options.incremental); | 1018 _writeOption(buffer, 'Incremental resolution', options.incremental); |
| 1019 _writeOption(buffer, 'Incremental resolution with API changes', | 1019 _writeOption(buffer, 'Incremental resolution with API changes', |
| 1020 options.incrementalApi); | 1020 options.incrementalApi); |
| 1021 _writeOption( | 1021 _writeOption(buffer, 'Preserve comments', options.preserveComments, |
| 1022 buffer, 'Preserve comments', options.preserveComments, last: true); | 1022 last: true); |
| 1023 buffer.write('</p>'); | 1023 buffer.write('</p>'); |
| 1024 int freq = AnalysisServer.performOperationDelayFreqency; | 1024 int freq = AnalysisServer.performOperationDelayFreqency; |
| 1025 String delay = freq > 0 ? '1 ms every $freq ms' : 'off'; | 1025 String delay = freq > 0 ? '1 ms every $freq ms' : 'off'; |
| 1026 buffer.write('<p><b>perform operation delay:</b> $delay</p>'); | 1026 buffer.write('<p><b>perform operation delay:</b> $delay</p>'); |
| 1027 | 1027 |
| 1028 buffer.write('<p><b>Performance Data</b></p>'); | 1028 buffer.write('<p><b>Performance Data</b></p>'); |
| 1029 buffer.write('<p>'); | 1029 buffer.write('<p>'); |
| 1030 buffer.write(makeLink(ANALYSIS_PERFORMANCE_PATH, {}, 'Task data')); | 1030 buffer.write(makeLink(ANALYSIS_PERFORMANCE_PATH, {}, 'Task data')); |
| 1031 buffer.write('</p>'); | 1031 buffer.write('</p>'); |
| 1032 }, (StringBuffer buffer) { | 1032 }, (StringBuffer buffer) { |
| (...skipping 275 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1308 _writeException(buffer, new CaughtException(exception, stackTrace)); | 1308 _writeException(buffer, new CaughtException(exception, stackTrace)); |
| 1309 } | 1309 } |
| 1310 buffer.write('</body>'); | 1310 buffer.write('</body>'); |
| 1311 buffer.write('</html>'); | 1311 buffer.write('</html>'); |
| 1312 } | 1312 } |
| 1313 | 1313 |
| 1314 /** | 1314 /** |
| 1315 * Write the recent output section (on the main status page) to the given | 1315 * Write the recent output section (on the main status page) to the given |
| 1316 * [buffer] object. | 1316 * [buffer] object. |
| 1317 */ | 1317 */ |
| 1318 void _writePluginStatus(StringBuffer buffer) { |
| 1319 void writePlugin(Plugin plugin) { |
| 1320 buffer.write(_server.serverPlugin.uniqueIdentifier); |
| 1321 buffer.write(' ('); |
| 1322 buffer.write(_server.serverPlugin.runtimeType); |
| 1323 buffer.write(')<br>'); |
| 1324 } |
| 1325 buffer.write('<h3>Plugin Status</h3><p>'); |
| 1326 writePlugin(AnalysisEngine.instance.enginePlugin); |
| 1327 writePlugin(_server.serverPlugin); |
| 1328 for (Plugin plugin in _server.analysisServer.userDefinedPlugins) { |
| 1329 writePlugin(plugin); |
| 1330 } |
| 1331 buffer.write('<p>'); |
| 1332 } |
| 1333 |
| 1334 /** |
| 1335 * Write the recent output section (on the main status page) to the given |
| 1336 * [buffer] object. |
| 1337 */ |
| 1318 void _writeRecentOutput(StringBuffer buffer) { | 1338 void _writeRecentOutput(StringBuffer buffer) { |
| 1319 buffer.write('<h3>Recent Output</h3>'); | 1339 buffer.write('<h3>Recent Output</h3>'); |
| 1320 String output = HTML_ESCAPE.convert(_printBuffer.join('\n')); | 1340 String output = HTML_ESCAPE.convert(_printBuffer.join('\n')); |
| 1321 if (output.isEmpty) { | 1341 if (output.isEmpty) { |
| 1322 buffer.write('<i>none</i>'); | 1342 buffer.write('<i>none</i>'); |
| 1323 } else { | 1343 } else { |
| 1324 buffer.write('<pre>'); | 1344 buffer.write('<pre>'); |
| 1325 buffer.write(output); | 1345 buffer.write(output); |
| 1326 buffer.write('</pre>'); | 1346 buffer.write('</pre>'); |
| 1327 } | 1347 } |
| (...skipping 230 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1558 */ | 1578 */ |
| 1559 static String makeLink( | 1579 static String makeLink( |
| 1560 String path, Map<String, String> params, String innerHtml, | 1580 String path, Map<String, String> params, String innerHtml, |
| 1561 [bool hasError = false]) { | 1581 [bool hasError = false]) { |
| 1562 Uri uri = new Uri(path: path, queryParameters: params); | 1582 Uri uri = new Uri(path: path, queryParameters: params); |
| 1563 String href = HTML_ESCAPE.convert(uri.toString()); | 1583 String href = HTML_ESCAPE.convert(uri.toString()); |
| 1564 String classAttribute = hasError ? ' class="error"' : ''; | 1584 String classAttribute = hasError ? ' class="error"' : ''; |
| 1565 return '<a href="$href"$classAttribute>$innerHtml</a>'; | 1585 return '<a href="$href"$classAttribute>$innerHtml</a>'; |
| 1566 } | 1586 } |
| 1567 } | 1587 } |
| OLD | NEW |