// 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 RegExp _languageVersion = RegExp(r'\/\/\s*@dart\s*=\s*([0-9])\.([0-9]+)'); final RegExp _declarationEnd = RegExp('(import)|(library)|(part)'); const String _blockCommentStart = '/*'; const String _blockCommentEnd = '*/'; /// The first language version where null safety was available by default. final LanguageVersion nullSafeVersion = LanguageVersion(2, 12); 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/master/accepted/future-releases/language-versioning/feature-specification.md#individual-library-language-version-override LanguageVersion determineLanguageVersion(File file, Package? package, String flutterRoot) { int 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; try { lines = file.readAsLinesSync(); } on FileSystemException { return currentLanguageVersion(file.fileSystem, flutterRoot); } for (final String 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. bool 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); }