| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'package:file/file.dart'; | |
| import 'package:package_config/package_config.dart'; | |
| import '../base/version.dart'; | |
| final _languageVersion = RegExp(r'\/\/\s*@dart\s*=\s*([0-9])\.([0-9]+)'); | |
| final _declarationEnd = RegExp('(import)|(library)|(part)'); | |
| const _blockCommentStart = '/*'; | |
| const _blockCommentEnd = '*/'; | |
| LanguageVersion? _currentLanguageVersion; | |
| /// Lookup the current Dart language version. | |
| LanguageVersion currentLanguageVersion(FileSystem fileSystem, String flutterRoot) { | |
| if (_currentLanguageVersion != null) { | |
| return _currentLanguageVersion!; | |
| } | |
| // Either reading the file or parsing the version could fail on a corrupt Dart SDK. | |
| // let it crash so it shows up in crash logging. | |
| final File versionFile = fileSystem.file( | |
| fileSystem.path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'version'), | |
| ); | |
| if (!versionFile.existsSync() && _inUnitTest()) { | |
| return LanguageVersion(2, 12); | |
| } | |
| final Version version = Version.parse(versionFile.readAsStringSync())!; | |
| return _currentLanguageVersion = LanguageVersion(version.major, version.minor); | |
| } | |
| // Whether the tool is executing in a unit test. | |
| bool _inUnitTest() { | |
| return Zone.current[#test.declarer] != null; | |
| } | |
| /// Attempts to read the language version of a dart [file]. | |
| /// | |
| /// If this is not present, falls back to the language version defined in | |
| /// [package]. If [package] is not provided and there is no | |
| /// language version header, returns 2.12. This does not specifically check | |
| /// for language declarations other than library, part, or import. | |
| /// | |
| /// The specification for the language version tag is defined at: | |
| /// https://github.com/dart-lang/language/blob/main/accepted/2.8/language-versioning/feature-specification.md#individual-library-language-version-override | |
| LanguageVersion determineLanguageVersion(File file, Package? package, String flutterRoot) { | |
| var blockCommentDepth = 0; | |
| // If reading the file fails, default to a null-safe version. The | |
| // command will likely fail later in the process with a better error | |
| // message. | |
| List<String> lines; | |
| // If the file is missing, check existsSync() defensively first. Calling | |
| // readAsLinesSync on a missing file inside our wrapped ErrorHandlingFileSystem | |
| // throws a fatal ToolExit which would escape the FileSystemException catch | |
| // block and crash the process prematurely. | |
| if (!file.existsSync()) { | |
| return currentLanguageVersion(file.fileSystem, flutterRoot); | |
| } | |
| try { | |
| lines = file.readAsLinesSync(); | |
| } on FileSystemException { | |
| return currentLanguageVersion(file.fileSystem, flutterRoot); | |
| } | |
| for (final line in lines) { | |
| final String trimmedLine = line.trim(); | |
| if (trimmedLine.isEmpty) { | |
| continue; | |
| } | |
| // Check for the start or end of a block comment. Within a block | |
| // comment, all language version declarations are ignored. Block | |
| // comments can be nested, and the start or end may occur on | |
| // the same line. This does not handle the case of invalid | |
| // block comment combinations like `*/ /*` since that will cause | |
| // a compilation error anyway. | |
| var sawBlockComment = false; | |
| final int startMatches = _blockCommentStart.allMatches(trimmedLine).length; | |
| final int endMatches = _blockCommentEnd.allMatches(trimmedLine).length; | |
| if (startMatches > 0) { | |
| blockCommentDepth += startMatches; | |
| sawBlockComment = true; | |
| } | |
| if (endMatches > 0) { | |
| blockCommentDepth -= endMatches; | |
| sawBlockComment = true; | |
| } | |
| if (blockCommentDepth != 0 || sawBlockComment) { | |
| continue; | |
| } | |
| // Check for a match with the language version. | |
| final Match? match = _languageVersion.matchAsPrefix(trimmedLine); | |
| if (match != null) { | |
| final String rawMajor = match.group(1) ?? ''; | |
| final String rawMinor = match.group(2) ?? ''; | |
| try { | |
| final int major = int.parse(rawMajor); | |
| final int minor = int.parse(rawMinor); | |
| return LanguageVersion(major, minor); | |
| } on FormatException { | |
| // Language comment was invalid in a way that the regexp did not | |
| // anticipate. | |
| break; | |
| } | |
| } | |
| // Check for a declaration which ends the search for a language | |
| // version. | |
| if (_declarationEnd.matchAsPrefix(trimmedLine) != null) { | |
| break; | |
| } | |
| } | |
| // If the language version cannot be found, use the package version. | |
| if (package != null) { | |
| return package.languageVersion ?? currentLanguageVersion(file.fileSystem, flutterRoot); | |
| } | |
| // Default to current version. | |
| return currentLanguageVersion(file.fileSystem, flutterRoot); | |
| } |