| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. 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:typed_data' show Uint8List; |
| |
| import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' |
| show LanguageVersionToken, Scanner, ScannerConfiguration, scan; |
| |
| import 'package:kernel/ast.dart' show Version; |
| export 'package:kernel/ast.dart' show Version; |
| |
| import 'package:package_config/package_config.dart' |
| show InvalidLanguageVersion, Package; |
| |
| import '../base/processed_options.dart' show ProcessedOptions; |
| |
| import '../fasta/compiler_context.dart' show CompilerContext; |
| |
| import '../fasta/source/source_library_builder.dart' show SourceLibraryBuilder; |
| |
| import '../fasta/uri_translator.dart' show UriTranslator; |
| |
| import 'compiler_options.dart' show CompilerOptions; |
| |
| import 'experimental_flags.dart' show ExperimentalFlag; |
| import 'file_system.dart' show FileSystem, FileSystemException; |
| |
| /// Gets the language version for a specific URI. |
| /// |
| /// Note that this returning some language version, doesn't mean there aren't |
| /// errors associated with the language version specified (e.g. that the file |
| /// specifies a language version that's too high). |
| /// |
| /// The language version returned is valid though. |
| Future<VersionAndPackageUri> languageVersionForUri( |
| Uri uri, CompilerOptions options) async { |
| return await CompilerContext.runWithOptions( |
| new ProcessedOptions(options: options, inputs: [uri]), (context) async { |
| // Get largest valid version / default version. |
| String currentSdkVersion = context.options.currentSdkVersion; |
| bool good = false; |
| late final int currentSdkVersionMajor; |
| late final int currentSdkVersionMinor; |
| // ignore: unnecessary_null_comparison |
| if (currentSdkVersion != null) { |
| List<String> dotSeparatedParts = currentSdkVersion.split("."); |
| if (dotSeparatedParts.length >= 2) { |
| currentSdkVersionMajor = int.parse(dotSeparatedParts[0]); |
| currentSdkVersionMinor = int.parse(dotSeparatedParts[1]); |
| good = true; |
| } |
| } |
| if (!good) { |
| throw new StateError("Unparsable sdk version given: $currentSdkVersion"); |
| } |
| |
| // Get file uri. |
| UriTranslator uriTranslator = await context.options.getUriTranslator(); |
| Uri? fileUri; |
| Package? package; |
| if (uri.scheme == "package") { |
| fileUri = uriTranslator.translate(uri); |
| package = uriTranslator.getPackage(uri); |
| } else { |
| fileUri = uri; |
| package = uriTranslator.packages.packageOf(uri); |
| } |
| Uri packageUri = uri; |
| if (packageUri.scheme != 'dart' && |
| packageUri.scheme != 'package' && |
| package != null && |
| // ignore: unnecessary_null_comparison |
| package.name != null) { |
| packageUri = new Uri(scheme: 'package', path: package.name); |
| } |
| |
| // Check file content for @dart annotation. |
| int? major; |
| int? minor; |
| if (fileUri != null) { |
| List<int>? rawBytes; |
| try { |
| FileSystem fileSystem = context.options.fileSystem; |
| rawBytes = await fileSystem.entityForUri(fileUri).readAsBytes(); |
| } on FileSystemException catch (_) { |
| rawBytes = null; |
| } |
| if (rawBytes != null) { |
| Uint8List zeroTerminatedBytes = new Uint8List(rawBytes.length + 1); |
| zeroTerminatedBytes.setRange(0, rawBytes.length, rawBytes); |
| |
| scan(zeroTerminatedBytes, |
| includeComments: false, |
| configuration: new ScannerConfiguration(), languageVersionChanged: |
| (Scanner scanner, LanguageVersionToken version) { |
| if (major != null || minor != null) return; |
| major = version.major; |
| minor = version.minor; |
| }); |
| } |
| } |
| |
| if (major != null && minor != null) { |
| // Verify OK. |
| if (major! > currentSdkVersionMajor || |
| (major == currentSdkVersionMajor && |
| minor! > currentSdkVersionMinor)) { |
| major = null; |
| minor = null; |
| } |
| } |
| if (major != null && minor != null) { |
| // The file decided. Return result. |
| return new VersionAndPackageUri(new Version(major!, minor!), packageUri); |
| } |
| |
| // Check package. |
| if (package != null && |
| package.languageVersion != null && |
| package.languageVersion is! InvalidLanguageVersion) { |
| major = package.languageVersion!.major; |
| minor = package.languageVersion!.minor; |
| if (major! > currentSdkVersionMajor || |
| (major == currentSdkVersionMajor && |
| minor! > currentSdkVersionMinor)) { |
| major = null; |
| minor = null; |
| } |
| } |
| if (major != null && minor != null) { |
| // The package decided. Return result. |
| return new VersionAndPackageUri(new Version(major!, minor!), packageUri); |
| } |
| |
| // Return default. |
| return new VersionAndPackageUri( |
| new Version(currentSdkVersionMajor, currentSdkVersionMinor), |
| packageUri); |
| }); |
| } |
| |
| /// Returns `true` if the language version of [uri] does not support null |
| /// safety. |
| Future<bool> uriUsesLegacyLanguageVersion( |
| Uri uri, CompilerOptions options) async { |
| // This method is here in order to use the opt out hack here for test |
| // sources. |
| if (SourceLibraryBuilder.isOptOutTest(uri)) return true; |
| VersionAndPackageUri versionAndLibraryUri = |
| await languageVersionForUri(uri, options); |
| return !options.isExperimentEnabledInLibraryByVersion( |
| ExperimentalFlag.nonNullable, |
| versionAndLibraryUri.packageUri, |
| versionAndLibraryUri.version); |
| } |
| |
| class VersionAndPackageUri { |
| final Version version; |
| final Uri packageUri; |
| |
| VersionAndPackageUri(this.version, this.packageUri); |
| } |