Unverified Commit 3fb389c7 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[null-safety] implement null-safe autodetection for the web (#70126)

Fixes #69416
Fixes #70121
parent ff953e4f
...@@ -95,10 +95,10 @@ class WebEntrypointTarget extends Target { ...@@ -95,10 +95,10 @@ class WebEntrypointTarget extends Target {
logger: environment.logger, logger: environment.logger,
); );
final FlutterProject flutterProject = FlutterProject.current(); final FlutterProject flutterProject = FlutterProject.current();
final String languageVersion = determineLanguageVersion( final LanguageVersion languageVersion = determineLanguageVersion(
environment.fileSystem.file(targetFile), environment.fileSystem.file(targetFile),
packageConfig[flutterProject.manifest.appName], packageConfig[flutterProject.manifest.appName],
) ?? ''; );
// Use the PackageConfig to find the correct package-scheme import path // Use the PackageConfig to find the correct package-scheme import path
// for the user application. If the application has a mix of package-scheme // for the user application. If the application has a mix of package-scheme
...@@ -122,7 +122,7 @@ class WebEntrypointTarget extends Target { ...@@ -122,7 +122,7 @@ class WebEntrypointTarget extends Target {
final String generatedImport = packageConfig.toPackageUri(generatedUri)?.toString() final String generatedImport = packageConfig.toPackageUri(generatedUri)?.toString()
?? generatedUri.toString(); ?? generatedUri.toString();
contents = ''' contents = '''
$languageVersion // @dart=${languageVersion.major}.${languageVersion.minor}
import 'dart:ui' as ui; import 'dart:ui' as ui;
...@@ -139,7 +139,7 @@ Future<void> main() async { ...@@ -139,7 +139,7 @@ Future<void> main() async {
'''; ''';
} else { } else {
contents = ''' contents = '''
$languageVersion // @dart=${languageVersion.major}.${languageVersion.minor}
import 'dart:ui' as ui; import 'dart:ui' as ui;
......
...@@ -5,22 +5,24 @@ ...@@ -5,22 +5,24 @@
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:package_config/package_config.dart'; import 'package:package_config/package_config.dart';
final RegExp _languageVersion = RegExp(r'\/\/\s*@dart'); final RegExp _languageVersion = RegExp(r'\/\/\s*@dart\s*=\s*([0-9])\.([0-9]+)');
final RegExp _declarationEnd = RegExp('(import)|(library)|(part)'); final RegExp _declarationEnd = RegExp('(import)|(library)|(part)');
const String _blockCommentStart = '/*'; const String _blockCommentStart = '/*';
const String _blockCommentEnd = '*/'; const String _blockCommentEnd = '*/';
/// Attempts to read the language version of a dart [file], returning /// The first language version where null safety was available by default.
/// the entire comment. final LanguageVersion nullSafeVersion = LanguageVersion(2, 12);
/// Attempts to read the language version of a dart [file].
/// ///
/// If this is not present, falls back to the language version defined in /// If this is not present, falls back to the language version defined in
/// [package]. If [package] is not provided and there is no /// [package]. If [package] is not provided and there is no
/// language version header, returns `null`. This does not specifically check /// language version header, returns 2.12. This does not specifically check
/// for language declarations other than library, part, or import. /// for language declarations other than library, part, or import.
/// ///
/// The specification for the language version tag is defined at: /// 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 /// https://github.com/dart-lang/language/blob/master/accepted/future-releases/language-versioning/feature-specification.md#individual-library-language-version-override
String determineLanguageVersion(File file, Package package) { LanguageVersion determineLanguageVersion(File file, Package package) {
int blockCommentDepth = 0; int blockCommentDepth = 0;
for (final String line in file.readAsLinesSync()) { for (final String line in file.readAsLinesSync()) {
final String trimmedLine = line.trim(); final String trimmedLine = line.trim();
...@@ -50,7 +52,17 @@ String determineLanguageVersion(File file, Package package) { ...@@ -50,7 +52,17 @@ String determineLanguageVersion(File file, Package package) {
// Check for a match with the language version. // Check for a match with the language version.
final Match match = _languageVersion.matchAsPrefix(trimmedLine); final Match match = _languageVersion.matchAsPrefix(trimmedLine);
if (match != null) { if (match != null) {
return trimmedLine; 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 // Check for a declaration which ends the search for a language
...@@ -62,7 +74,8 @@ String determineLanguageVersion(File file, Package package) { ...@@ -62,7 +74,8 @@ String determineLanguageVersion(File file, Package package) {
// If the language version cannot be found, use the package version. // If the language version cannot be found, use the package version.
if (package != null) { if (package != null) {
return '// @dart = ${package.languageVersion}'; return package.languageVersion;
} }
return null; // Default to 2.12
return nullSafeVersion;
} }
...@@ -669,12 +669,13 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -669,12 +669,13 @@ class _ResidentWebRunner extends ResidentWebRunner {
path: '/' + mainUri.pathSegments.last, path: '/' + mainUri.pathSegments.last,
); );
} }
final LanguageVersion languageVersion = determineLanguageVersion(
globals.fs.file(mainUri),
packageConfig[flutterProject.manifest.appName],
);
final String entrypoint = <String>[ final String entrypoint = <String>[
determineLanguageVersion( '// @dart=${languageVersion.major}.${languageVersion.minor}',
globals.fs.file(mainUri),
packageConfig[flutterProject.manifest.appName],
),
'// Flutter web bootstrap script for $importedEntrypoint.', '// Flutter web bootstrap script for $importedEntrypoint.',
'', '',
"import 'dart:ui' as ui;", "import 'dart:ui' as ui;",
......
...@@ -27,6 +27,8 @@ import 'build_system/targets/localizations.dart'; ...@@ -27,6 +27,8 @@ import 'build_system/targets/localizations.dart';
import 'bundle.dart'; import 'bundle.dart';
import 'cache.dart'; import 'cache.dart';
import 'compile.dart'; import 'compile.dart';
import 'dart/language_version.dart';
import 'dart/package_map.dart';
import 'devfs.dart'; import 'devfs.dart';
import 'device.dart'; import 'device.dart';
import 'features.dart'; import 'features.dart';
...@@ -101,14 +103,28 @@ class FlutterDevice { ...@@ -101,14 +103,28 @@ class FlutterDevice {
platformDillArtifact = Artifact.webPlatformSoundKernelDill; platformDillArtifact = Artifact.webPlatformSoundKernelDill;
extraFrontEndOptions = buildInfo.extraFrontEndOptions; extraFrontEndOptions = buildInfo.extraFrontEndOptions;
} else { } else {
// TODO(jonahwilliams): null-safe auto detection does not currently final PackageConfig packageConfig = await loadPackageConfigWithLogging(
// work on the web. Always opt out of null safety if it was not globals.fs.file(buildInfo.packagesPath),
// specifically requested. logger: globals.logger,
platformDillArtifact = Artifact.webPlatformKernelDill; );
extraFrontEndOptions = <String>[ final File entrypointFile = globals.fs.file(target);
...?buildInfo.extraFrontEndOptions, final LanguageVersion languageVersion = determineLanguageVersion(
'--no-sound-null-safety', entrypointFile,
]; packageConfig.packageOf(entrypointFile.absolute.uri),
);
if (languageVersion.major >= nullSafeVersion.major && languageVersion.minor >= nullSafeVersion.minor) {
platformDillArtifact = Artifact.webPlatformSoundKernelDill;
extraFrontEndOptions = <String>[
...?buildInfo.extraFrontEndOptions,
'--sound-null-safety',
];
} else {
platformDillArtifact = Artifact.webPlatformKernelDill;
extraFrontEndOptions = <String>[
...?buildInfo.extraFrontEndOptions,
'--no-sound-null-safety',
];
}
} }
generator = ResidentCompiler( generator = ResidentCompiler(
......
...@@ -713,19 +713,19 @@ class FlutterPlatform extends PlatformPlugin { ...@@ -713,19 +713,19 @@ class FlutterPlatform extends PlatformPlugin {
}) { }) {
assert(testUrl.scheme == 'file'); assert(testUrl.scheme == 'file');
final File file = globals.fs.file(testUrl); final File file = globals.fs.file(testUrl);
final LanguageVersion languageVersion = determineLanguageVersion(
file,
_packageConfig[flutterProject?.manifest?.appName],
);
return generateTestBootstrap( return generateTestBootstrap(
testUrl: testUrl, testUrl: testUrl,
testConfigFile: findTestConfigFile(globals.fs.file(testUrl)), testConfigFile: findTestConfigFile(globals.fs.file(testUrl)),
host: host, host: host,
updateGoldens: updateGoldens, updateGoldens: updateGoldens,
languageVersionHeader: determineLanguageVersion( languageVersionHeader: '// @dart=${languageVersion.major}.${languageVersion.minor}'
file,
_packageConfig[flutterProject?.manifest?.appName],
),
); );
} }
File _cachedFontConfig; File _cachedFontConfig;
@override @override
......
...@@ -238,7 +238,7 @@ void main() { ...@@ -238,7 +238,7 @@ void main() {
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
// Language version // Language version
expect(generated, contains('// @dart = 2.7')); expect(generated, contains('// @dart=2.7'));
})); }));
test('WebEntrypointTarget generates an entrypoint without plugins and without init platform', () => testbed.run(() async { test('WebEntrypointTarget generates an entrypoint without plugins and without init platform', () => testbed.run(() async {
......
...@@ -19,10 +19,34 @@ void main() { ...@@ -19,10 +19,34 @@ void main() {
// @dart = 2.9 // @dart = 2.9
'''); ''');
expect(determineLanguageVersion(file, null), '// @dart = 2.9'); expect(determineLanguageVersion(file, null), LanguageVersion(2, 9));
}); });
testWithoutContext('detects technically invalid language version', () { testWithoutContext('detects language version in comment without spacing', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final File file = fileSystem.file('example.dart')
..writeAsStringSync('''
// Some license
// @dart=2.9
''');
expect(determineLanguageVersion(file, null), LanguageVersion(2, 9));
});
testWithoutContext('detects language version in comment with more numbers', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final File file = fileSystem.file('example.dart')
..writeAsStringSync('''
// Some license
// @dart=2.12
''');
expect(determineLanguageVersion(file, null), LanguageVersion(2, 12));
});
testWithoutContext('does not detect invalid language version', () {
final FileSystem fileSystem = MemoryFileSystem.test(); final FileSystem fileSystem = MemoryFileSystem.test();
final File file = fileSystem.file('example.dart') final File file = fileSystem.file('example.dart')
..writeAsStringSync(''' ..writeAsStringSync('''
...@@ -31,7 +55,7 @@ void main() { ...@@ -31,7 +55,7 @@ void main() {
// @dart // @dart
'''); ''');
expect(determineLanguageVersion(file, null), '// @dart'); expect(determineLanguageVersion(file, null), LanguageVersion(2, 12));
}); });
testWithoutContext('detects language version with leading whitespace', () { testWithoutContext('detects language version with leading whitespace', () {
...@@ -43,7 +67,7 @@ void main() { ...@@ -43,7 +67,7 @@ void main() {
// @dart = 2.9 // @dart = 2.9
'''); ''');
expect(determineLanguageVersion(file, null), '// @dart = 2.9'); expect(determineLanguageVersion(file, null), LanguageVersion(2, 9));
}); });
testWithoutContext('detects language version with tabs', () { testWithoutContext('detects language version with tabs', () {
...@@ -55,7 +79,7 @@ void main() { ...@@ -55,7 +79,7 @@ void main() {
//\t@dart = 2.9 //\t@dart = 2.9
'''); ''');
expect(determineLanguageVersion(file, null), '//\t@dart = 2.9'); expect(determineLanguageVersion(file, null), LanguageVersion(2, 9));
}); });
testWithoutContext('detects language version with tons of whitespace', () { testWithoutContext('detects language version with tons of whitespace', () {
...@@ -64,10 +88,10 @@ void main() { ...@@ -64,10 +88,10 @@ void main() {
..writeAsStringSync(''' ..writeAsStringSync('''
// Some license // Some license
// @dart = 23 // @dart = 2.23
'''); ''');
expect(determineLanguageVersion(file, null), '// @dart = 23'); expect(determineLanguageVersion(file, null), LanguageVersion(2, 23));
}); });
testWithoutContext('does not detect language version in dartdoc', () { testWithoutContext('does not detect language version in dartdoc', () {
...@@ -79,7 +103,7 @@ void main() { ...@@ -79,7 +103,7 @@ void main() {
/// @dart = 2.9 /// @dart = 2.9
'''); ''');
expect(determineLanguageVersion(file, null), null); expect(determineLanguageVersion(file, null), LanguageVersion(2, 12));
}); });
testWithoutContext('does not detect language version in block comment', () { testWithoutContext('does not detect language version in block comment', () {
...@@ -93,7 +117,7 @@ void main() { ...@@ -93,7 +117,7 @@ void main() {
*/ */
'''); ''');
expect(determineLanguageVersion(file, null), null); expect(determineLanguageVersion(file, null), LanguageVersion(2, 12));
}); });
testWithoutContext('does not detect language version in nested block comment', () { testWithoutContext('does not detect language version in nested block comment', () {
...@@ -109,7 +133,7 @@ void main() { ...@@ -109,7 +133,7 @@ void main() {
*/ */
'''); ''');
expect(determineLanguageVersion(file, null), null); expect(determineLanguageVersion(file, null), LanguageVersion(2, 12));
}); });
testWithoutContext('detects language version after nested block comment', () { testWithoutContext('detects language version after nested block comment', () {
...@@ -124,7 +148,7 @@ void main() { ...@@ -124,7 +148,7 @@ void main() {
// @dart = 2.9 // @dart = 2.9
'''); ''');
expect(determineLanguageVersion(file, null), '// @dart = 2.9'); expect(determineLanguageVersion(file, null), LanguageVersion(2, 9));
}); });
testWithoutContext('does not crash with unbalanced opening block comments', () { testWithoutContext('does not crash with unbalanced opening block comments', () {
...@@ -139,7 +163,7 @@ void main() { ...@@ -139,7 +163,7 @@ void main() {
// @dart = 2.9 // @dart = 2.9
'''); ''');
expect(determineLanguageVersion(file, null), null); expect(determineLanguageVersion(file, null), LanguageVersion(2, 12));
}); });
testWithoutContext('does not crash with unbalanced closing block comments', () { testWithoutContext('does not crash with unbalanced closing block comments', () {
...@@ -154,7 +178,7 @@ void main() { ...@@ -154,7 +178,7 @@ void main() {
// @dart = 2.9 // @dart = 2.9
'''); ''');
expect(determineLanguageVersion(file, null), null); expect(determineLanguageVersion(file, null), LanguageVersion(2, 12));
}); });
testWithoutContext('does not detect language version in single line block comment', () { testWithoutContext('does not detect language version in single line block comment', () {
...@@ -166,7 +190,7 @@ void main() { ...@@ -166,7 +190,7 @@ void main() {
/* // @dart = 2.9 */ /* // @dart = 2.9 */
'''); ''');
expect(determineLanguageVersion(file, null), null); expect(determineLanguageVersion(file, null), LanguageVersion(2, 12));
}); });
testWithoutContext('does not detect language version after import declaration', () { testWithoutContext('does not detect language version after import declaration', () {
...@@ -180,7 +204,7 @@ import 'dart:ui' as ui; ...@@ -180,7 +204,7 @@ import 'dart:ui' as ui;
// @dart = 2.9 // @dart = 2.9
'''); ''');
expect(determineLanguageVersion(file, null), null); expect(determineLanguageVersion(file, null), LanguageVersion(2, 12));
}); });
testWithoutContext('does not detect language version after part declaration', () { testWithoutContext('does not detect language version after part declaration', () {
...@@ -194,7 +218,7 @@ part of 'foo.dart'; ...@@ -194,7 +218,7 @@ part of 'foo.dart';
// @dart = 2.9 // @dart = 2.9
'''); ''');
expect(determineLanguageVersion(file, null), null); expect(determineLanguageVersion(file, null), LanguageVersion(2, 12));
}); });
testWithoutContext('does not detect language version after library declaration', () { testWithoutContext('does not detect language version after library declaration', () {
...@@ -208,7 +232,7 @@ library funstuff; ...@@ -208,7 +232,7 @@ library funstuff;
// @dart = 2.9 // @dart = 2.9
'''); ''');
expect(determineLanguageVersion(file, null), null); expect(determineLanguageVersion(file, null), LanguageVersion(2, 12));
}); });
testWithoutContext('looks up language version from package if not found in file', () { testWithoutContext('looks up language version from package if not found in file', () {
...@@ -223,6 +247,6 @@ library funstuff; ...@@ -223,6 +247,6 @@ library funstuff;
languageVersion: LanguageVersion(2, 7), languageVersion: LanguageVersion(2, 7),
); );
expect(determineLanguageVersion(file, package), '// @dart = 2.7'); expect(determineLanguageVersion(file, package), LanguageVersion(2, 7));
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment