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

Side by Side Diff: pkg/analyzer/lib/src/dart/analysis/driver.dart

Issue 2954153002: Add AnalysisSession to analyzer (Closed)
Patch Set: Created 3 years, 6 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
OLDNEW
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2016, 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 import 'dart:async'; 5 import 'dart:async';
6 import 'dart:collection'; 6 import 'dart:collection';
7 import 'dart:typed_data'; 7 import 'dart:typed_data';
8 8
9 import 'package:analyzer/context/context_root.dart'; 9 import 'package:analyzer/context/context_root.dart';
10 import 'package:analyzer/context/declared_variables.dart'; 10 import 'package:analyzer/context/declared_variables.dart';
11 import 'package:analyzer/dart/analysis/results.dart' as results; 11 import 'package:analyzer/dart/analysis/results.dart' as results;
12 import 'package:analyzer/dart/analysis/session.dart';
12 import 'package:analyzer/dart/ast/ast.dart'; 13 import 'package:analyzer/dart/ast/ast.dart';
13 import 'package:analyzer/dart/element/element.dart' 14 import 'package:analyzer/dart/element/element.dart'
14 show CompilationUnitElement, LibraryElement; 15 show CompilationUnitElement, LibraryElement;
15 import 'package:analyzer/error/error.dart'; 16 import 'package:analyzer/error/error.dart';
16 import 'package:analyzer/error/listener.dart'; 17 import 'package:analyzer/error/listener.dart';
17 import 'package:analyzer/exception/exception.dart'; 18 import 'package:analyzer/exception/exception.dart';
18 import 'package:analyzer/file_system/file_system.dart'; 19 import 'package:analyzer/file_system/file_system.dart';
19 import 'package:analyzer/src/dart/analysis/file_state.dart'; 20 import 'package:analyzer/src/dart/analysis/file_state.dart';
20 import 'package:analyzer/src/dart/analysis/file_tracker.dart'; 21 import 'package:analyzer/src/dart/analysis/file_tracker.dart';
21 import 'package:analyzer/src/dart/analysis/index.dart'; 22 import 'package:analyzer/src/dart/analysis/index.dart';
22 import 'package:analyzer/src/dart/analysis/library_analyzer.dart'; 23 import 'package:analyzer/src/dart/analysis/library_analyzer.dart';
23 import 'package:analyzer/src/dart/analysis/library_context.dart'; 24 import 'package:analyzer/src/dart/analysis/library_context.dart';
24 import 'package:analyzer/src/dart/analysis/search.dart'; 25 import 'package:analyzer/src/dart/analysis/search.dart';
26 import 'package:analyzer/src/dart/analysis/session.dart';
25 import 'package:analyzer/src/dart/analysis/status.dart'; 27 import 'package:analyzer/src/dart/analysis/status.dart';
26 import 'package:analyzer/src/dart/analysis/top_level_declaration.dart'; 28 import 'package:analyzer/src/dart/analysis/top_level_declaration.dart';
27 import 'package:analyzer/src/generated/engine.dart' 29 import 'package:analyzer/src/generated/engine.dart'
28 show 30 show
29 AnalysisContext, 31 AnalysisContext,
30 AnalysisEngine, 32 AnalysisEngine,
31 AnalysisOptions, 33 AnalysisOptions,
32 PerformanceStatistics; 34 PerformanceStatistics;
33 import 'package:analyzer/src/generated/resolver.dart'; 35 import 'package:analyzer/src/generated/resolver.dart';
34 import 'package:analyzer/src/generated/source.dart'; 36 import 'package:analyzer/src/generated/source.dart';
(...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after
273 * once and cache all the resolved units. 275 * once and cache all the resolved units.
274 */ 276 */
275 final bool disableChangesAndCacheAllResults; 277 final bool disableChangesAndCacheAllResults;
276 278
277 /** 279 /**
278 * The cache to use with [disableChangesAndCacheAllResults]. 280 * The cache to use with [disableChangesAndCacheAllResults].
279 */ 281 */
280 final Map<String, AnalysisResult> _allCachedResults = {}; 282 final Map<String, AnalysisResult> _allCachedResults = {};
281 283
282 /** 284 /**
285 * The current analysis session.
286 *
287 * TODO(brianwilkerson) Create a new session when the current session might
288 * produce inconsistent results.
289 */
290 AnalysisSession _currentSession;
291
292 /**
283 * Create a new instance of [AnalysisDriver]. 293 * Create a new instance of [AnalysisDriver].
284 * 294 *
285 * The given [SourceFactory] is cloned to ensure that it does not contain a 295 * The given [SourceFactory] is cloned to ensure that it does not contain a
286 * reference to a [AnalysisContext] in which it could have been used. 296 * reference to a [AnalysisContext] in which it could have been used.
287 */ 297 */
288 AnalysisDriver( 298 AnalysisDriver(
289 this._scheduler, 299 this._scheduler,
290 PerformanceLog logger, 300 PerformanceLog logger,
291 this._resourceProvider, 301 this._resourceProvider,
292 this._byteStore, 302 this._byteStore,
293 this._contentOverlay, 303 this._contentOverlay,
294 this.contextRoot, 304 this.contextRoot,
295 SourceFactory sourceFactory, 305 SourceFactory sourceFactory,
296 this._analysisOptions, 306 this._analysisOptions,
297 {PackageBundle sdkBundle, 307 {PackageBundle sdkBundle,
298 this.disableChangesAndCacheAllResults: false, 308 this.disableChangesAndCacheAllResults: false,
299 SummaryDataStore externalSummaries}) 309 SummaryDataStore externalSummaries})
300 : _logger = logger, 310 : _logger = logger,
301 _sourceFactory = sourceFactory.clone(), 311 _sourceFactory = sourceFactory.clone(),
302 _sdkBundle = sdkBundle, 312 _sdkBundle = sdkBundle,
303 _externalSummaries = externalSummaries { 313 _externalSummaries = externalSummaries {
314 _currentSession = new DriverBasedAnalysisSession(this);
304 _onResults = _resultController.stream.asBroadcastStream(); 315 _onResults = _resultController.stream.asBroadcastStream();
305 _testView = new AnalysisDriverTestView(this); 316 _testView = new AnalysisDriverTestView(this);
306 _createFileTracker(); 317 _createFileTracker();
307 _scheduler.add(this); 318 _scheduler.add(this);
308 _search = new Search(this); 319 _search = new Search(this);
309 } 320 }
310 321
311 /** 322 /**
312 * Return the set of files explicitly added to analysis using [addFile]. 323 * Return the set of files explicitly added to analysis using [addFile].
313 */ 324 */
314 Set<String> get addedFiles => _fileTracker.addedFiles; 325 Set<String> get addedFiles => _fileTracker.addedFiles;
315 326
316 /** 327 /**
317 * Return the analysis options used to control analysis. 328 * Return the analysis options used to control analysis.
318 */ 329 */
319 AnalysisOptions get analysisOptions => _analysisOptions; 330 AnalysisOptions get analysisOptions => _analysisOptions;
320 331
321 /** 332 /**
333 * Return the current analysis session.
334 */
335 AnalysisSession get currentSession => _currentSession;
336
337 /**
322 * Return the stream that produces [ExceptionResult]s. 338 * Return the stream that produces [ExceptionResult]s.
323 */ 339 */
324 Stream<ExceptionResult> get exceptions => _exceptionController.stream; 340 Stream<ExceptionResult> get exceptions => _exceptionController.stream;
325 341
326 /** 342 /**
327 * The current file system state. 343 * The current file system state.
328 */ 344 */
329 FileSystemState get fsState => _fsState; 345 FileSystemState get fsState => _fsState;
330 346
331 @override 347 @override
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after
531 // If no cached analysis result, it will be computed. 547 // If no cached analysis result, it will be computed.
532 AnalysisResult analysisResult = _computeAnalysisResult(path); 548 AnalysisResult analysisResult = _computeAnalysisResult(path);
533 549
534 // If not computed yet, because a part file without a known library, 550 // If not computed yet, because a part file without a known library,
535 // we have to compute the full analysis result, with the unit. 551 // we have to compute the full analysis result, with the unit.
536 analysisResult ??= await getResult(path); 552 analysisResult ??= await getResult(path);
537 if (analysisResult == null) { 553 if (analysisResult == null) {
538 return null; 554 return null;
539 } 555 }
540 556
541 return new ErrorsResult(path, analysisResult.uri, analysisResult.lineInfo, 557 return new ErrorsResult(currentSession, path, analysisResult.uri,
542 analysisResult.errors); 558 analysisResult.lineInfo, analysisResult.errors);
543 } 559 }
544 560
545 /** 561 /**
546 * Return a [Future] that completes with the list of added files that 562 * Return a [Future] that completes with the list of added files that
547 * define a class member with the given [name]. 563 * define a class member with the given [name].
548 */ 564 */
549 Future<List<String>> getFilesDefiningClassMemberName(String name) { 565 Future<List<String>> getFilesDefiningClassMemberName(String name) {
550 var task = new _FilesDefiningClassMemberNameTask(this, name); 566 var task = new _FilesDefiningClassMemberNameTask(this, name);
551 _definingClassMemberNameTasks.add(task); 567 _definingClassMemberNameTasks.add(task);
552 _scheduler.notify(this); 568 _scheduler.notify(this);
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after
726 * The [path] can be any file - explicitly or implicitly analyzed, or neither. 742 * The [path] can be any file - explicitly or implicitly analyzed, or neither.
727 * 743 *
728 * The parsing is performed in the method itself, and the result is not 744 * The parsing is performed in the method itself, and the result is not
729 * produced through the [results] stream (just because it is not a fully 745 * produced through the [results] stream (just because it is not a fully
730 * resolved unit). 746 * resolved unit).
731 */ 747 */
732 Future<ParseResult> parseFile(String path) async { 748 Future<ParseResult> parseFile(String path) async {
733 FileState file = _fileTracker.verifyApiSignature(path); 749 FileState file = _fileTracker.verifyApiSignature(path);
734 RecordingErrorListener listener = new RecordingErrorListener(); 750 RecordingErrorListener listener = new RecordingErrorListener();
735 CompilationUnit unit = file.parse(listener); 751 CompilationUnit unit = file.parse(listener);
736 return new ParseResult(file.path, file.uri, file.content, unit.lineInfo, 752 return new ParseResult(currentSession, file.path, file.uri, file.content,
737 unit, listener.errors); 753 unit.lineInfo, unit, listener.errors);
738 } 754 }
739 755
740 @override 756 @override
741 Future<Null> performWork() async { 757 Future<Null> performWork() async {
742 if (_fileTracker.verifyChangedFilesIfNeeded()) { 758 if (_fileTracker.verifyChangedFilesIfNeeded()) {
743 return; 759 return;
744 } 760 }
745 761
746 // Analyze a requested file. 762 // Analyze a requested file.
747 if (_requestedFiles.isNotEmpty) { 763 if (_requestedFiles.isNotEmpty) {
(...skipping 312 matching lines...) Expand 10 before | Expand all | Expand 10 after
1060 FileState library = file.library ?? file; 1076 FileState library = file.library ?? file;
1061 1077
1062 // Create the AnalysisContext to resynthesize elements in. 1078 // Create the AnalysisContext to resynthesize elements in.
1063 LibraryContext libraryContext = _createLibraryContext(library); 1079 LibraryContext libraryContext = _createLibraryContext(library);
1064 1080
1065 // Resynthesize the CompilationUnitElement in the context. 1081 // Resynthesize the CompilationUnitElement in the context.
1066 try { 1082 try {
1067 CompilationUnitElement element = 1083 CompilationUnitElement element =
1068 libraryContext.computeUnitElement(library.source, file.source); 1084 libraryContext.computeUnitElement(library.source, file.source);
1069 String signature = library.transitiveSignature; 1085 String signature = library.transitiveSignature;
1070 return new UnitElementResult(path, file.uri, signature, element); 1086 return new UnitElementResult(
1087 currentSession, path, file.uri, signature, element);
1071 } finally { 1088 } finally {
1072 libraryContext.dispose(); 1089 libraryContext.dispose();
1073 } 1090 }
1074 } 1091 }
1075 1092
1076 String _computeUnitElementSignature(String path) { 1093 String _computeUnitElementSignature(String path) {
1077 FileState file = _fsState.getFileForPath(path); 1094 FileState file = _fsState.getFileForPath(path);
1078 FileState library = file.library ?? file; 1095 FileState library = file.library ?? file;
1079 return library.transitiveSignature; 1096 return library.transitiveSignature;
1080 } 1097 }
(...skipping 511 matching lines...) Expand 10 before | Expand all | Expand 10 after
1592 * The result of analyzing of a single file. 1609 * The result of analyzing of a single file.
1593 * 1610 *
1594 * These results are self-consistent, i.e. [content], [contentHash], the 1611 * These results are self-consistent, i.e. [content], [contentHash], the
1595 * resolved [unit] correspond to each other. All referenced elements, even 1612 * resolved [unit] correspond to each other. All referenced elements, even
1596 * external ones, are also self-consistent. But none of the results is 1613 * external ones, are also self-consistent. But none of the results is
1597 * guaranteed to be consistent with the state of the files. 1614 * guaranteed to be consistent with the state of the files.
1598 * 1615 *
1599 * Every result is independent, and is not guaranteed to be consistent with 1616 * Every result is independent, and is not guaranteed to be consistent with
1600 * any previously returned result, even inside of the same library. 1617 * any previously returned result, even inside of the same library.
1601 */ 1618 */
1602 class AnalysisResult implements results.ResolveResult { 1619 class AnalysisResult extends BaseAnalysisResult
1620 implements results.ResolveResult {
1603 static final _UNCHANGED = new AnalysisResult( 1621 static final _UNCHANGED = new AnalysisResult(
1604 null, null, null, null, null, null, null, null, null, null, null); 1622 null, null, null, null, null, null, null, null, null, null, null);
1605 1623
1606 /** 1624 /**
1607 * The [AnalysisDriver] that produced this result. 1625 * The [AnalysisDriver] that produced this result.
1608 */ 1626 */
1609 final AnalysisDriver driver; 1627 final AnalysisDriver driver;
1610 1628
1611 /** 1629 /**
1612 * The [SourceFactory] with which the file was analyzed. 1630 * The [SourceFactory] with which the file was analyzed.
1613 */ 1631 */
1614 final SourceFactory sourceFactory; 1632 final SourceFactory sourceFactory;
1615 1633
1616 @override
1617 final String path;
1618
1619 @override
1620 final Uri uri;
1621
1622 /** 1634 /**
1623 * Return `true` if the file exists. 1635 * Return `true` if the file exists.
1624 */ 1636 */
1625 final bool exists; 1637 final bool exists;
1626 1638
1627 @override 1639 @override
1628 final String content; 1640 final String content;
1629 1641
1630 @override 1642 @override
1631 final LineInfo lineInfo; 1643 final LineInfo lineInfo;
(...skipping 12 matching lines...) Expand all
1644 final List<AnalysisError> errors; 1656 final List<AnalysisError> errors;
1645 1657
1646 /** 1658 /**
1647 * The index of the unit. 1659 * The index of the unit.
1648 */ 1660 */
1649 final AnalysisDriverUnitIndex _index; 1661 final AnalysisDriverUnitIndex _index;
1650 1662
1651 AnalysisResult( 1663 AnalysisResult(
1652 this.driver, 1664 this.driver,
1653 this.sourceFactory, 1665 this.sourceFactory,
1654 this.path, 1666 String path,
1655 this.uri, 1667 Uri uri,
1656 this.exists, 1668 this.exists,
1657 this.content, 1669 this.content,
1658 this.lineInfo, 1670 this.lineInfo,
1659 this._signature, 1671 this._signature,
1660 this.unit, 1672 this.unit,
1661 this.errors, 1673 this.errors,
1662 this._index); 1674 this._index)
1675 : super(driver?.currentSession, path, uri);
1663 1676
1664 @override 1677 @override
1665 LibraryElement get libraryElement => unit.element.library; 1678 LibraryElement get libraryElement => unit.element.library;
1666 1679
1667 @override 1680 @override
1668 results.ResultState get state => 1681 results.ResultState get state =>
1669 exists ? results.ResultState.VALID : results.ResultState.NOT_A_FILE; 1682 exists ? results.ResultState.VALID : results.ResultState.NOT_A_FILE;
1670 1683
1671 @override 1684 @override
1672 TypeProvider get typeProvider => unit.element.context.typeProvider; 1685 TypeProvider get typeProvider => unit.element.context.typeProvider;
1673 } 1686 }
1674 1687
1688 abstract class BaseAnalysisResult implements results.AnalysisResult {
1689 @override
1690 final String path;
1691
1692 @override
1693 final AnalysisSession session;
scheglov 2017/06/26 15:30:03 This field should be the first.
Brian Wilkerson 2017/06/26 16:06:19 Done
1694
1695 @override
1696 final Uri uri;
1697
1698 BaseAnalysisResult(this.session, this.path, this.uri);
1699 }
1700
1675 class DriverPerformance { 1701 class DriverPerformance {
1676 static final PerformanceTag driver = 1702 static final PerformanceTag driver =
1677 PerformanceStatistics.analyzer.createChild('driver'); 1703 PerformanceStatistics.analyzer.createChild('driver');
1678 1704
1679 static final PerformanceTag cache = driver.createChild('cache'); 1705 static final PerformanceTag cache = driver.createChild('cache');
1680 } 1706 }
1681 1707
1682 /** 1708 /**
1683 * An object that watches for the creation and removal of analysis drivers. 1709 * An object that watches for the creation and removal of analysis drivers.
1684 * 1710 *
(...skipping 12 matching lines...) Expand all
1697 void removedDriver(AnalysisDriver driver); 1723 void removedDriver(AnalysisDriver driver);
1698 } 1724 }
1699 1725
1700 /** 1726 /**
1701 * The errors in a single file. 1727 * The errors in a single file.
1702 * 1728 *
1703 * These results are self-consistent, i.e. [content], [contentHash], [errors] 1729 * These results are self-consistent, i.e. [content], [contentHash], [errors]
1704 * correspond to each other. But none of the results is guaranteed to be 1730 * correspond to each other. But none of the results is guaranteed to be
1705 * consistent with the state of the files. 1731 * consistent with the state of the files.
1706 */ 1732 */
1707 class ErrorsResult implements results.ErrorsResult { 1733 class ErrorsResult extends BaseAnalysisResult implements results.ErrorsResult {
1708 @override
1709 final String path;
1710
1711 @override
1712 final Uri uri;
1713
1714 @override 1734 @override
1715 final LineInfo lineInfo; 1735 final LineInfo lineInfo;
1716 1736
1717 @override 1737 @override
1718 final List<AnalysisError> errors; 1738 final List<AnalysisError> errors;
1719 1739
1720 ErrorsResult(this.path, this.uri, this.lineInfo, this.errors); 1740 ErrorsResult(
1741 AnalysisSession session, String path, Uri uri, this.lineInfo, this.errors)
1742 : super(session, path, uri);
1721 1743
1722 @override 1744 @override
1723 results.ResultState get state => results.ResultState.VALID; 1745 results.ResultState get state => results.ResultState.VALID;
1724 } 1746 }
1725 1747
1726 /** 1748 /**
1727 * Exception that happened during analysis. 1749 * Exception that happened during analysis.
1728 */ 1750 */
1729 class ExceptionResult { 1751 class ExceptionResult {
1730 /** 1752 /**
(...skipping 19 matching lines...) Expand all
1750 ExceptionResult(this.path, this.exception, this.contextKey); 1772 ExceptionResult(this.path, this.exception, this.contextKey);
1751 } 1773 }
1752 1774
1753 /** 1775 /**
1754 * The result of parsing of a single file. 1776 * The result of parsing of a single file.
1755 * 1777 *
1756 * These results are self-consistent, i.e. [content], [contentHash], the 1778 * These results are self-consistent, i.e. [content], [contentHash], the
1757 * resolved [unit] correspond to each other. But none of the results is 1779 * resolved [unit] correspond to each other. But none of the results is
1758 * guaranteed to be consistent with the state of the files. 1780 * guaranteed to be consistent with the state of the files.
1759 */ 1781 */
1760 class ParseResult implements results.ParseResult { 1782 class ParseResult extends BaseAnalysisResult implements results.ParseResult {
1761 @override
1762 final String path;
1763
1764 @override
1765 final Uri uri;
1766
1767 @override 1783 @override
1768 final String content; 1784 final String content;
1769 1785
1770 @override 1786 @override
1771 final LineInfo lineInfo; 1787 final LineInfo lineInfo;
1772 1788
1773 @override 1789 @override
1774 final CompilationUnit unit; 1790 final CompilationUnit unit;
1775 1791
1776 @override 1792 @override
1777 final List<AnalysisError> errors; 1793 final List<AnalysisError> errors;
1778 1794
1779 ParseResult( 1795 ParseResult(AnalysisSession session, String path, Uri uri, this.content,
1780 this.path, this.uri, this.content, this.lineInfo, this.unit, this.errors); 1796 this.lineInfo, this.unit, this.errors)
1797 : super(session, path, uri);
1781 1798
1782 @override 1799 @override
1783 results.ResultState get state => results.ResultState.VALID; 1800 results.ResultState get state => results.ResultState.VALID;
1784 } 1801 }
1785 1802
1786 /** 1803 /**
1787 * The result with the [CompilationUnitElement] of a single file. 1804 * The result with the [CompilationUnitElement] of a single file.
1788 * 1805 *
1789 * These results are self-consistent, i.e. all elements and types accessible 1806 * These results are self-consistent, i.e. all elements and types accessible
1790 * through [element], including defined in other files, correspond to each 1807 * through [element], including defined in other files, correspond to each
1791 * other. But none of the results is guaranteed to be consistent with the state 1808 * other. But none of the results is guaranteed to be consistent with the state
1792 * of the files. 1809 * of the files.
1793 * 1810 *
1794 * Every result is independent, and is not guaranteed to be consistent with 1811 * Every result is independent, and is not guaranteed to be consistent with
1795 * any previously returned result, even inside of the same library. 1812 * any previously returned result, even inside of the same library.
1796 */ 1813 */
1797 class UnitElementResult implements results.UnitElementResult { 1814 class UnitElementResult extends BaseAnalysisResult
1798 @override 1815 implements results.UnitElementResult {
1799 final String path;
1800
1801 @override
1802 final Uri uri;
1803
1804 /** 1816 /**
1805 * The signature of the [element] is based the APIs of the files of the 1817 * The signature of the [element] is based the APIs of the files of the
1806 * library (including the file itself) of the requested file and the 1818 * library (including the file itself) of the requested file and the
1807 * transitive closure of files imported and exported by the library. 1819 * transitive closure of files imported and exported by the library.
1808 */ 1820 */
1809 final String signature; 1821 final String signature;
1810 1822
1811 /** 1823 /**
1812 * The element of the file. 1824 * The element of the file.
1813 */ 1825 */
1814 final CompilationUnitElement element; 1826 final CompilationUnitElement element;
1815 1827
1816 UnitElementResult(this.path, this.uri, this.signature, this.element); 1828 UnitElementResult(AnalysisSession session, String path, Uri uri,
1829 this.signature, this.element)
1830 : super(session, path, uri);
1817 1831
1818 @override 1832 @override
1819 results.ResultState get state => results.ResultState.VALID; 1833 results.ResultState get state => results.ResultState.VALID;
1820 } 1834 }
1821 1835
1822 /** 1836 /**
1823 * Information about an exception and its context. 1837 * Information about an exception and its context.
1824 */ 1838 */
1825 class _ExceptionState { 1839 class _ExceptionState {
1826 final exception; 1840 final exception;
(...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after
2004 libraryDeclarations.add(new TopLevelDeclarationInSource( 2018 libraryDeclarations.add(new TopLevelDeclarationInSource(
2005 file.source, declaration, isExported)); 2019 file.source, declaration, isExported));
2006 } 2020 }
2007 } 2021 }
2008 } 2022 }
2009 2023
2010 // We're not done yet. 2024 // We're not done yet.
2011 return false; 2025 return false;
2012 } 2026 }
2013 } 2027 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698