| 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 test.context.directory.manager; | 5 library test.context.directory.manager; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | |
| 9 | 8 |
| 10 import 'package:analysis_server/src/context_manager.dart'; | 9 import 'package:analysis_server/src/context_manager.dart'; |
| 11 import 'package:analysis_server/src/utilities/null_string_sink.dart'; | 10 import 'package:analysis_server/src/utilities/null_string_sink.dart'; |
| 12 import 'package:analyzer/context/context_root.dart'; | 11 import 'package:analyzer/context/context_root.dart'; |
| 13 import 'package:analyzer/error/error.dart'; | 12 import 'package:analyzer/error/error.dart'; |
| 14 import 'package:analyzer/file_system/file_system.dart'; | 13 import 'package:analyzer/file_system/file_system.dart'; |
| 15 import 'package:analyzer/file_system/memory_file_system.dart'; | 14 import 'package:analyzer/file_system/memory_file_system.dart'; |
| 16 import 'package:analyzer/instrumentation/instrumentation.dart'; | 15 import 'package:analyzer/instrumentation/instrumentation.dart'; |
| 17 import 'package:analyzer/source/error_processor.dart'; | 16 import 'package:analyzer/source/error_processor.dart'; |
| 18 import 'package:analyzer/src/context/builder.dart'; | 17 import 'package:analyzer/src/context/builder.dart'; |
| (...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 160 // Setup .packages file | 159 // Setup .packages file |
| 161 newFile( | 160 newFile( |
| 162 [projPath, '.packages'], | 161 [projPath, '.packages'], |
| 163 r''' | 162 r''' |
| 164 test_pack:lib/'''); | 163 test_pack:lib/'''); |
| 165 // Setup context. | 164 // Setup context. |
| 166 | 165 |
| 167 manager.setRoots(<String>[projPath], <String>[], <String, String>{}); | 166 manager.setRoots(<String>[projPath], <String>[], <String, String>{}); |
| 168 await pumpEventQueue(); | 167 await pumpEventQueue(); |
| 169 // Confirm that one context was created. | 168 // Confirm that one context was created. |
| 170 var contexts = | 169 int count = manager |
| 171 manager.contextsInAnalysisRoot(resourceProvider.newFolder(projPath)); | 170 .numberOfContextsInAnalysisRoot(resourceProvider.newFolder(projPath)); |
| 172 expect(contexts, isNotNull); | 171 expect(count, equals(1)); |
| 173 expect(contexts.length, equals(1)); | |
| 174 var source = sourceFactory.forUri('dart:foobar'); | 172 var source = sourceFactory.forUri('dart:foobar'); |
| 175 expect(source, isNotNull); | 173 expect(source, isNotNull); |
| 176 expect(source.fullName, '/my/proj/sdk_ext/entry.dart'); | 174 expect(source.fullName, '/my/proj/sdk_ext/entry.dart'); |
| 177 // We can't find dart:core because we didn't list it in our | 175 // We can't find dart:core because we didn't list it in our |
| 178 // embedded_libs map. | 176 // embedded_libs map. |
| 179 expect(sourceFactory.forUri('dart:core'), isNull); | 177 expect(sourceFactory.forUri('dart:core'), isNull); |
| 180 // We can find dart:typed_data because we listed it in our | 178 // We can find dart:typed_data because we listed it in our |
| 181 // embedded_libs map. | 179 // embedded_libs map. |
| 182 expect(sourceFactory.forUri('dart:typed_data'), isNotNull); | 180 expect(sourceFactory.forUri('dart:typed_data'), isNotNull); |
| 183 } | 181 } |
| (...skipping 209 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 393 } | 391 } |
| 394 '''); | 392 '''); |
| 395 // Setup .packages file | 393 // Setup .packages file |
| 396 newFile( | 394 newFile( |
| 397 [projPath, '.packages'], | 395 [projPath, '.packages'], |
| 398 r''' | 396 r''' |
| 399 test_pack:lib/'''); | 397 test_pack:lib/'''); |
| 400 // Setup context. | 398 // Setup context. |
| 401 manager.setRoots(<String>[projPath], <String>[], <String, String>{}); | 399 manager.setRoots(<String>[projPath], <String>[], <String, String>{}); |
| 402 // Confirm that one context was created. | 400 // Confirm that one context was created. |
| 403 var contexts = | 401 int count = manager |
| 404 manager.contextsInAnalysisRoot(resourceProvider.newFolder(projPath)); | 402 .numberOfContextsInAnalysisRoot(resourceProvider.newFolder(projPath)); |
| 405 expect(contexts, isNotNull); | 403 expect(count, equals(1)); |
| 406 expect(contexts.length, equals(1)); | |
| 407 var source = sourceFactory.forUri('dart:foobar'); | 404 var source = sourceFactory.forUri('dart:foobar'); |
| 408 expect(source.fullName, equals('/my/proj/sdk_ext/entry.dart')); | 405 expect(source.fullName, equals('/my/proj/sdk_ext/entry.dart')); |
| 409 } | 406 } |
| 410 | 407 |
| 411 void test_setRoots_addFolderWithDartFile() { | 408 void test_setRoots_addFolderWithDartFile() { |
| 412 String filePath = path.posix.join(projPath, 'foo.dart'); | 409 String filePath = path.posix.join(projPath, 'foo.dart'); |
| 413 resourceProvider.newFile(filePath, 'contents'); | 410 resourceProvider.newFile(filePath, 'contents'); |
| 414 manager.setRoots(<String>[projPath], <String>[], <String, String>{}); | 411 manager.setRoots(<String>[projPath], <String>[], <String, String>{}); |
| 415 // verify | 412 // verify |
| 416 Iterable<String> filePaths = callbacks.currentFilePaths; | 413 Iterable<String> filePaths = callbacks.currentFilePaths; |
| (...skipping 1336 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1753 | 1750 |
| 1754 void deleteFile(List<String> pathComponents) { | 1751 void deleteFile(List<String> pathComponents) { |
| 1755 String filePath = path.posix.joinAll(pathComponents); | 1752 String filePath = path.posix.joinAll(pathComponents); |
| 1756 resourceProvider.deleteFile(filePath); | 1753 resourceProvider.deleteFile(filePath); |
| 1757 } | 1754 } |
| 1758 | 1755 |
| 1759 /** | 1756 /** |
| 1760 * TODO(brianwilkerson) This doesn't add the strong mode processor when using | 1757 * TODO(brianwilkerson) This doesn't add the strong mode processor when using |
| 1761 * the new analysis driver. | 1758 * the new analysis driver. |
| 1762 */ | 1759 */ |
| 1763 ErrorProcessor getProcessor(AnalysisError error) => | 1760 ErrorProcessor getProcessor(AnalysisError error) => errorProcessors |
| 1764 callbacks.currentDriver == null | 1761 .firstWhere((ErrorProcessor p) => p.appliesTo(error), orElse: () => null); |
| 1765 ? ErrorProcessor.getProcessor( | |
| 1766 callbacks.currentContext.analysisOptions, error) | |
| 1767 : errorProcessors.firstWhere((ErrorProcessor p) => p.appliesTo(error), | |
| 1768 orElse: () => null); | |
| 1769 | 1762 |
| 1770 String newFile(List<String> pathComponents, [String content = '']) { | 1763 String newFile(List<String> pathComponents, [String content = '']) { |
| 1771 String filePath = path.posix.joinAll(pathComponents); | 1764 String filePath = path.posix.joinAll(pathComponents); |
| 1772 resourceProvider.newFile(filePath, content); | 1765 resourceProvider.newFile(filePath, content); |
| 1773 return filePath; | 1766 return filePath; |
| 1774 } | 1767 } |
| 1775 | 1768 |
| 1776 String newFileFromBytes(List<String> pathComponents, List<int> bytes) { | 1769 String newFileFromBytes(List<String> pathComponents, List<int> bytes) { |
| 1777 String filePath = path.posix.joinAll(pathComponents); | 1770 String filePath = path.posix.joinAll(pathComponents); |
| 1778 resourceProvider.newFileWithBytes(filePath, bytes); | 1771 resourceProvider.newFileWithBytes(filePath, bytes); |
| (...skipping 326 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2105 linter: | 2098 linter: |
| 2106 rules: | 2099 rules: |
| 2107 - camel_case_types | 2100 - camel_case_types |
| 2108 '''); | 2101 '''); |
| 2109 | 2102 |
| 2110 // Setup context. | 2103 // Setup context. |
| 2111 manager.setRoots(<String>[projPath], <String>[], <String, String>{}); | 2104 manager.setRoots(<String>[projPath], <String>[], <String, String>{}); |
| 2112 await pumpEventQueue(); | 2105 await pumpEventQueue(); |
| 2113 | 2106 |
| 2114 // Confirm that one context was created. | 2107 // Confirm that one context was created. |
| 2115 var contexts = | 2108 int count = manager |
| 2116 manager.contextsInAnalysisRoot(resourceProvider.newFolder(projPath)); | 2109 .numberOfContextsInAnalysisRoot(resourceProvider.newFolder(projPath)); |
| 2117 expect(contexts, isNotNull); | 2110 expect(count, equals(1)); |
| 2118 expect(contexts, hasLength(1)); | |
| 2119 | 2111 |
| 2120 // Verify options. | 2112 // Verify options. |
| 2121 // * from `_embedder.yaml`: | 2113 // * from `_embedder.yaml`: |
| 2122 expect(analysisOptions.strongMode, isTrue); | 2114 expect(analysisOptions.strongMode, isTrue); |
| 2123 expect(analysisOptions.enableSuperMixins, isTrue); | 2115 expect(analysisOptions.enableSuperMixins, isTrue); |
| 2124 // * from analysis options: | 2116 // * from analysis options: |
| 2125 expect(analysisOptions.enableStrictCallChecks, isTrue); | 2117 expect(analysisOptions.enableStrictCallChecks, isTrue); |
| 2126 | 2118 |
| 2127 // * verify tests are excluded | 2119 // * verify tests are excluded |
| 2128 expect( | 2120 expect( |
| (...skipping 389 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2518 } | 2510 } |
| 2519 } | 2511 } |
| 2520 | 2512 |
| 2521 class TestContextManagerCallbacks extends ContextManagerCallbacks { | 2513 class TestContextManagerCallbacks extends ContextManagerCallbacks { |
| 2522 /** | 2514 /** |
| 2523 * Source of timestamps stored in [currentContextFilePaths]. | 2515 * Source of timestamps stored in [currentContextFilePaths]. |
| 2524 */ | 2516 */ |
| 2525 int now = 0; | 2517 int now = 0; |
| 2526 | 2518 |
| 2527 /** | 2519 /** |
| 2528 * The analysis context that was created. | |
| 2529 */ | |
| 2530 AnalysisContext currentContext; | |
| 2531 | |
| 2532 /** | |
| 2533 * The analysis driver that was created. | 2520 * The analysis driver that was created. |
| 2534 */ | 2521 */ |
| 2535 AnalysisDriver currentDriver; | 2522 AnalysisDriver currentDriver; |
| 2536 | 2523 |
| 2537 /** | 2524 /** |
| 2538 * A table mapping paths to the analysis driver associated with that path. | 2525 * A table mapping paths to the analysis driver associated with that path. |
| 2539 */ | 2526 */ |
| 2540 Map<String, AnalysisDriver> driverMap = <String, AnalysisDriver>{}; | 2527 Map<String, AnalysisDriver> driverMap = <String, AnalysisDriver>{}; |
| 2541 | 2528 |
| 2542 /** | 2529 /** |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2586 * The watch events that have been broadcast. | 2573 * The watch events that have been broadcast. |
| 2587 */ | 2574 */ |
| 2588 List<WatchEvent> watchEvents = <WatchEvent>[]; | 2575 List<WatchEvent> watchEvents = <WatchEvent>[]; |
| 2589 | 2576 |
| 2590 TestContextManagerCallbacks( | 2577 TestContextManagerCallbacks( |
| 2591 this.resourceProvider, this.sdkManager, this.logger, this.scheduler); | 2578 this.resourceProvider, this.sdkManager, this.logger, this.scheduler); |
| 2592 | 2579 |
| 2593 /** | 2580 /** |
| 2594 * Return the current set of analysis options. | 2581 * Return the current set of analysis options. |
| 2595 */ | 2582 */ |
| 2596 AnalysisOptions get analysisOptions => currentDriver == null | 2583 AnalysisOptions get analysisOptions => currentDriver?.analysisOptions; |
| 2597 ? currentContext.analysisOptions | |
| 2598 : currentDriver.analysisOptions; | |
| 2599 | 2584 |
| 2600 /** | 2585 /** |
| 2601 * Return the paths to the context roots that currently exist. | 2586 * Return the paths to the context roots that currently exist. |
| 2602 */ | 2587 */ |
| 2603 Iterable<String> get currentContextRoots { | 2588 Iterable<String> get currentContextRoots { |
| 2604 return currentContextTimestamps.keys; | 2589 return currentContextTimestamps.keys; |
| 2605 } | 2590 } |
| 2606 | 2591 |
| 2607 /** | 2592 /** |
| 2608 * Return the paths to the files being analyzed in the current context root. | 2593 * Return the paths to the files being analyzed in the current context root. |
| 2609 */ | 2594 */ |
| 2610 Iterable<String> get currentFilePaths { | 2595 Iterable<String> get currentFilePaths { |
| 2611 if (currentDriver == null) { | 2596 if (currentDriver == null) { |
| 2612 if (currentContext == null) { | 2597 return <String>[]; |
| 2613 return <String>[]; | |
| 2614 } | |
| 2615 Map<String, int> fileMap = currentContextFilePaths[currentContext.name]; | |
| 2616 if (fileMap == null) { | |
| 2617 return <String>[]; | |
| 2618 } | |
| 2619 return fileMap.keys; | |
| 2620 } | 2598 } |
| 2621 return currentDriver.addedFiles; | 2599 return currentDriver.addedFiles; |
| 2622 } | 2600 } |
| 2623 | 2601 |
| 2624 /** | 2602 /** |
| 2625 * Return the current source factory. | 2603 * Return the current source factory. |
| 2626 */ | 2604 */ |
| 2627 SourceFactory get sourceFactory => currentDriver == null | 2605 SourceFactory get sourceFactory => currentDriver?.sourceFactory; |
| 2628 ? currentContext.sourceFactory | |
| 2629 : currentDriver.sourceFactory; | |
| 2630 | 2606 |
| 2631 @override | 2607 @override |
| 2632 AnalysisDriver addAnalysisDriver( | 2608 AnalysisDriver addAnalysisDriver( |
| 2633 Folder folder, ContextRoot contextRoot, AnalysisOptions options) { | 2609 Folder folder, ContextRoot contextRoot, AnalysisOptions options) { |
| 2634 String path = folder.path; | 2610 String path = folder.path; |
| 2635 expect(currentContextRoots, isNot(contains(path))); | 2611 expect(currentContextRoots, isNot(contains(path))); |
| 2636 expect(contextRoot, isNotNull); | 2612 expect(contextRoot, isNotNull); |
| 2637 expect(contextRoot.root, path); | 2613 expect(contextRoot.root, path); |
| 2638 currentContextTimestamps[path] = now; | 2614 currentContextTimestamps[path] = now; |
| 2639 | 2615 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 2655 analysisOptions); | 2631 analysisOptions); |
| 2656 driverMap[path] = currentDriver; | 2632 driverMap[path] = currentDriver; |
| 2657 currentDriver.exceptions.listen((ExceptionResult result) { | 2633 currentDriver.exceptions.listen((ExceptionResult result) { |
| 2658 AnalysisEngine.instance.logger | 2634 AnalysisEngine.instance.logger |
| 2659 .logError('Analysis failed: ${result.path}', result.exception); | 2635 .logError('Analysis failed: ${result.path}', result.exception); |
| 2660 }); | 2636 }); |
| 2661 return currentDriver; | 2637 return currentDriver; |
| 2662 } | 2638 } |
| 2663 | 2639 |
| 2664 @override | 2640 @override |
| 2665 AnalysisContext addContext(Folder folder, AnalysisOptions options) { | |
| 2666 String path = folder.path; | |
| 2667 expect(currentContextRoots, isNot(contains(path))); | |
| 2668 currentContextTimestamps[path] = now; | |
| 2669 currentContextFilePaths[path] = <String, int>{}; | |
| 2670 currentContextSources[path] = new HashSet<Source>(); | |
| 2671 | |
| 2672 ContextBuilder builder = createContextBuilder(folder, options); | |
| 2673 currentContext = builder.buildContext(path); | |
| 2674 currentContext.name = path; | |
| 2675 return currentContext; | |
| 2676 } | |
| 2677 | |
| 2678 @override | |
| 2679 void applyChangesToContext(Folder contextFolder, ChangeSet changeSet) { | 2641 void applyChangesToContext(Folder contextFolder, ChangeSet changeSet) { |
| 2680 AnalysisDriver driver = driverMap[contextFolder.path]; | 2642 AnalysisDriver driver = driverMap[contextFolder.path]; |
| 2681 if (driver != null) { | 2643 if (driver != null) { |
| 2682 changeSet.addedSources.forEach((source) { | 2644 changeSet.addedSources.forEach((source) { |
| 2683 driver.addFile(source.fullName); | 2645 driver.addFile(source.fullName); |
| 2684 }); | 2646 }); |
| 2685 changeSet.changedSources.forEach((source) { | 2647 changeSet.changedSources.forEach((source) { |
| 2686 driver.changeFile(source.fullName); | 2648 driver.changeFile(source.fullName); |
| 2687 }); | 2649 }); |
| 2688 changeSet.removedSources.forEach((source) { | 2650 changeSet.removedSources.forEach((source) { |
| 2689 driver.removeFile(source.fullName); | 2651 driver.removeFile(source.fullName); |
| 2690 }); | 2652 }); |
| 2691 } else { | |
| 2692 Map<String, int> filePaths = currentContextFilePaths[contextFolder.path]; | |
| 2693 Set<Source> sources = currentContextSources[contextFolder.path]; | |
| 2694 | |
| 2695 for (Source source in changeSet.addedSources) { | |
| 2696 expect(filePaths, isNot(contains(source.fullName))); | |
| 2697 filePaths[source.fullName] = now; | |
| 2698 sources.add(source); | |
| 2699 } | |
| 2700 for (Source source in changeSet.removedSources) { | |
| 2701 expect(filePaths, contains(source.fullName)); | |
| 2702 filePaths.remove(source.fullName); | |
| 2703 sources.remove(source); | |
| 2704 } | |
| 2705 for (Source source in changeSet.changedSources) { | |
| 2706 expect(filePaths, contains(source.fullName)); | |
| 2707 filePaths[source.fullName] = now; | |
| 2708 } | |
| 2709 | |
| 2710 currentContext.applyChanges(changeSet); | |
| 2711 } | 2653 } |
| 2712 } | 2654 } |
| 2713 | 2655 |
| 2714 @override | 2656 @override |
| 2715 void applyFileRemoved(AnalysisDriver driver, String file) { | 2657 void applyFileRemoved(AnalysisDriver driver, String file) { |
| 2716 driver.removeFile(file); | 2658 driver.removeFile(file); |
| 2717 } | 2659 } |
| 2718 | 2660 |
| 2719 void assertContextFiles(String contextPath, List<String> expectedFiles) { | 2661 void assertContextFiles(String contextPath, List<String> expectedFiles) { |
| 2720 expect(getCurrentFilePaths(contextPath), unorderedEquals(expectedFiles)); | 2662 expect(getCurrentFilePaths(contextPath), unorderedEquals(expectedFiles)); |
| (...skipping 22 matching lines...) Expand all Loading... |
| 2743 resourceProvider, sdkManager, new ContentCache(), | 2685 resourceProvider, sdkManager, new ContentCache(), |
| 2744 options: builderOptions); | 2686 options: builderOptions); |
| 2745 return builder; | 2687 return builder; |
| 2746 } | 2688 } |
| 2747 | 2689 |
| 2748 /** | 2690 /** |
| 2749 * Return the paths to the files being analyzed in the current context root. | 2691 * Return the paths to the files being analyzed in the current context root. |
| 2750 */ | 2692 */ |
| 2751 Iterable<Source> currentFileSources(String contextPath) { | 2693 Iterable<Source> currentFileSources(String contextPath) { |
| 2752 if (currentDriver == null) { | 2694 if (currentDriver == null) { |
| 2753 if (currentContext == null) { | 2695 return <Source>[]; |
| 2754 return <Source>[]; | |
| 2755 } | |
| 2756 Set<Source> sources = currentContextSources[contextPath]; | |
| 2757 return sources ?? <Source>[]; | |
| 2758 } | 2696 } |
| 2759 AnalysisDriver driver = driverMap[contextPath]; | 2697 AnalysisDriver driver = driverMap[contextPath]; |
| 2760 SourceFactory sourceFactory = driver.sourceFactory; | 2698 SourceFactory sourceFactory = driver.sourceFactory; |
| 2761 return driver.addedFiles.map((String path) { | 2699 return driver.addedFiles.map((String path) { |
| 2762 File file = resourceProvider.getFile(path); | 2700 File file = resourceProvider.getFile(path); |
| 2763 Source source = file.createSource(); | 2701 Source source = file.createSource(); |
| 2764 Uri uri = sourceFactory.restoreUri(source); | 2702 Uri uri = sourceFactory.restoreUri(source); |
| 2765 return file.createSource(uri); | 2703 return file.createSource(uri); |
| 2766 }); | 2704 }); |
| 2767 } | 2705 } |
| 2768 | 2706 |
| 2769 /** | 2707 /** |
| 2770 * Return the paths to the files being analyzed in the current context root. | 2708 * Return the paths to the files being analyzed in the current context root. |
| 2771 */ | 2709 */ |
| 2772 Iterable<String> getCurrentFilePaths(String contextPath) { | 2710 Iterable<String> getCurrentFilePaths(String contextPath) { |
| 2773 if (currentDriver == null) { | 2711 if (currentDriver == null) { |
| 2774 if (currentContext == null) { | 2712 return <String>[]; |
| 2775 return <String>[]; | |
| 2776 } | |
| 2777 Map<String, int> fileMap = currentContextFilePaths[contextPath]; | |
| 2778 if (fileMap == null) { | |
| 2779 return <String>[]; | |
| 2780 } | |
| 2781 return fileMap.keys; | |
| 2782 } | 2713 } |
| 2783 return driverMap[contextPath].addedFiles; | 2714 return driverMap[contextPath].addedFiles; |
| 2784 } | 2715 } |
| 2785 | 2716 |
| 2786 @override | 2717 @override |
| 2787 void moveContext(Folder from, Folder to) { | 2718 void moveContext(Folder from, Folder to) { |
| 2788 String path = from.path; | 2719 String path = from.path; |
| 2789 String path2 = to.path; | 2720 String path2 = to.path; |
| 2790 expect(currentContextFilePaths, contains(path)); | 2721 expect(currentContextFilePaths, contains(path)); |
| 2791 expect(currentContextTimestamps, contains(path)); | 2722 expect(currentContextTimestamps, contains(path)); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2827 class TestUriResolver extends UriResolver { | 2758 class TestUriResolver extends UriResolver { |
| 2828 Map<Uri, Source> uriMap; | 2759 Map<Uri, Source> uriMap; |
| 2829 | 2760 |
| 2830 TestUriResolver(this.uriMap); | 2761 TestUriResolver(this.uriMap); |
| 2831 | 2762 |
| 2832 @override | 2763 @override |
| 2833 Source resolveAbsolute(Uri uri, [Uri actualUri]) { | 2764 Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| 2834 return uriMap[uri]; | 2765 return uriMap[uri]; |
| 2835 } | 2766 } |
| 2836 } | 2767 } |
| OLD | NEW |