Null safe and unsafe in a single codebase (#689)
diff --git a/benchmark/bench.dart b/benchmark/bench.dart
index 06af555..3a7d6c6 100644
--- a/benchmark/bench.dart
+++ b/benchmark/bench.dart
@@ -15,12 +15,12 @@
import 'package:dart_services/src/sdk.dart';
import 'package:logging/logging.dart';
+const nullSafe = false;
+
void main(List<String> args) async {
final json = args.contains('--json');
-
final harness = BenchmarkHarness(asJson: json);
-
- final compiler = Compiler(Sdk());
+ final compiler = Compiler(Sdk(), nullSafe);
Logger.root.level = Level.WARNING;
Logger.root.onRecord.listen((LogRecord record) {
@@ -59,7 +59,7 @@
String name,
this.source,
) : super('analyzer.$name') {
- analysisServer = DartAnalysisServerWrapper();
+ analysisServer = DartAnalysisServerWrapper(nullSafe);
}
@override
@@ -107,7 +107,7 @@
final AnalysisServerWrapper analysisServer;
AnalysisServerBenchmark(String name, this.source)
- : analysisServer = DartAnalysisServerWrapper(),
+ : analysisServer = DartAnalysisServerWrapper(nullSafe),
super('completion.$name');
@override
diff --git a/cloud_run_null_safety.Dockerfile b/cloud_run_null_safety.Dockerfile
new file mode 100644
index 0000000..c4205d0
--- /dev/null
+++ b/cloud_run_null_safety.Dockerfile
@@ -0,0 +1,38 @@
+FROM google/dart:2.12.2
+
+# We install unzip and remove the apt-index again to keep the
+# docker image diff small.
+RUN apt-get update && \
+ apt-get install -y unzip && \
+ rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+RUN groupadd --system dart && \
+ useradd --no-log-init --system --home /home/dart --create-home -g dart dart
+RUN chown dart:dart /app
+
+# Switch to a new, non-root user to use the flutter tool.
+# The Flutter tool won't perform its actions when run as root.
+USER dart
+
+COPY --chown=dart:dart tool/dart_cloud_run.sh /dart_runtime/
+RUN chmod a+x /dart_runtime/dart_cloud_run.sh
+COPY --chown=dart:dart pubspec.* /app/
+RUN pub get
+COPY --chown=dart:dart . /app
+RUN pub get --offline
+
+ENV PATH="/home/dart/.pub-cache/bin:${PATH}"
+
+# Set the Flutter SDK up for web compilation.
+RUN dart pub run grinder setup-flutter-sdk
+
+# Build the dill file
+RUN dart pub run grinder build-storage-artifacts validate-storage-artifacts
+
+# Clear out any arguments the base images might have set and ensure we start
+# the Dart app using custom script enabling debug modes.
+CMD []
+
+ENTRYPOINT ["/dart_runtime/dart_cloud_run.sh", "--port", "${PORT}", \
+ "--redis-url", "redis://10.0.0.4:6379", "--null-safety"]
diff --git a/lib/services_cloud_run.dart b/lib/services_cloud_run.dart
index 060c018..edac169 100644
--- a/lib/services_cloud_run.dart
+++ b/lib/services_cloud_run.dart
@@ -25,7 +25,8 @@
Future<void> main(List<String> args) async {
final parser = ArgParser()
..addOption('port', abbr: 'p')
- ..addOption('redis-url');
+ ..addOption('redis-url')
+ ..addFlag('null-safety');
final results = parser.parse(args);
// Cloud Run supplies the port to bind to in the environment.
@@ -39,6 +40,7 @@
}
final redisServerUri = results['redis-url'] as String;
+ final nullSafety = results['null-safety'] as bool;
Logger.root.level = Level.FINER;
Logger.root.onRecord.listen((LogRecord record) {
@@ -55,16 +57,18 @@
port: $port
sdkPath: ${Sdk.sdkPath}
redisServerUri: $redisServerUri
+ nullSafety: $nullSafety
Cloud Run Environment variables:
$cloudRunEnvVars''');
- final server = await EndpointsServer.serve(port, redisServerUri);
+ final server = await EndpointsServer.serve(port, redisServerUri, nullSafety);
_logger.info('Listening on port ${server.port}');
}
class EndpointsServer {
- static Future<EndpointsServer> serve(int port, String redisServerUri) async {
- final endpointsServer = EndpointsServer._(port, redisServerUri);
+ static Future<EndpointsServer> serve(
+ int port, String redisServerUri, bool nullSafety) async {
+ final endpointsServer = EndpointsServer._(port, redisServerUri, nullSafety);
await endpointsServer.init();
endpointsServer.server = await shelf.serve(
@@ -86,7 +90,7 @@
CommonServerImpl _commonServerImpl;
FlutterWebManager flutterWebManager;
- EndpointsServer._(this.port, this.redisServerUri) {
+ EndpointsServer._(this.port, this.redisServerUri, bool nullSafety) {
_commonServerImpl = CommonServerImpl(
_ServerContainer(),
redisServerUri == null
@@ -97,6 +101,7 @@
// https://cloud.google.com/run/docs/reference/container-contract#env-vars
Platform.environment['K_REVISION'],
),
+ nullSafety,
);
commonServerApi = CommonServerApi(_commonServerImpl);
diff --git a/lib/services_dev.dart b/lib/services_dev.dart
index 317aa9e..e78b9bb 100644
--- a/lib/services_dev.dart
+++ b/lib/services_dev.dart
@@ -26,8 +26,7 @@
parser
..addOption('port', abbr: 'p', defaultsTo: '8080')
..addOption('server-url', defaultsTo: 'http://localhost')
- ..addOption('proxy-target',
- help: 'URL base to proxy compilation requests to');
+ ..addFlag('null-safety');
final result = parser.parse(args);
final port = int.tryParse(result['port'] as String);
@@ -43,15 +42,15 @@
if (record.stackTrace != null) print(record.stackTrace);
});
- EndpointsServer.serve(port, result['proxy-target'] as String)
+ EndpointsServer.serve(port, result['null-safety'] as bool)
.then((EndpointsServer server) {
_logger.info('Listening on port ${server.port}');
});
}
class EndpointsServer {
- static Future<EndpointsServer> serve(int port, String proxyTarget) {
- final endpointsServer = EndpointsServer._(port, proxyTarget);
+ static Future<EndpointsServer> serve(int port, bool nullSafety) {
+ final endpointsServer = EndpointsServer._(port, nullSafety);
return shelf
.serve(endpointsServer.handler, InternetAddress.anyIPv4, port)
@@ -63,7 +62,6 @@
final int port;
HttpServer server;
- final String proxyTarget;
Pipeline pipeline;
Handler handler;
@@ -71,13 +69,12 @@
CommonServerApi commonServerApi;
FlutterWebManager flutterWebManager;
- EndpointsServer._(this.port, this.proxyTarget) {
- final commonServerImpl = (proxyTarget != null && proxyTarget.isNotEmpty)
- ? CommonServerImplProxy(proxyTarget)
- : CommonServerImpl(
- _ServerContainer(),
- _Cache(),
- );
+ EndpointsServer._(this.port, bool nullSafety) {
+ final commonServerImpl = CommonServerImpl(
+ _ServerContainer(),
+ _Cache(),
+ nullSafety,
+ );
commonServerApi = CommonServerApi(commonServerImpl);
commonServerImpl.init();
diff --git a/lib/src/analysis_server.dart b/lib/src/analysis_server.dart
index 7b23cd7..9443dcb 100644
--- a/lib/src/analysis_server.dart
+++ b/lib/src/analysis_server.dart
@@ -35,17 +35,21 @@
const Duration _ANALYSIS_SERVER_TIMEOUT = Duration(seconds: 35);
class DartAnalysisServerWrapper extends AnalysisServerWrapper {
- DartAnalysisServerWrapper() : super(Sdk.sdkPath);
+ DartAnalysisServerWrapper(this._nullSafety) : super(Sdk.sdkPath);
+ final bool _nullSafety;
@override
- String get _sourceDirPath => FlutterWebManager.dartTemplateProject.path;
+ String get _sourceDirPath =>
+ FlutterWebManager.dartTemplateProject(_nullSafety).path;
}
class FlutterAnalysisServerWrapper extends AnalysisServerWrapper {
- FlutterAnalysisServerWrapper() : super(Sdk.sdkPath);
+ FlutterAnalysisServerWrapper(this._nullSafety) : super(Sdk.sdkPath);
+ final bool _nullSafety;
@override
- String get _sourceDirPath => FlutterWebManager.flutterTemplateProject.path;
+ String get _sourceDirPath =>
+ FlutterWebManager.flutterTemplateProject(_nullSafety).path;
}
abstract class AnalysisServerWrapper {
diff --git a/lib/src/analysis_servers.dart b/lib/src/analysis_servers.dart
index 0c8d063..79ed436 100644
--- a/lib/src/analysis_servers.dart
+++ b/lib/src/analysis_servers.dart
@@ -20,7 +20,8 @@
final Logger _logger = Logger('analysis_servers');
class AnalysisServersWrapper {
- AnalysisServersWrapper();
+ AnalysisServersWrapper(this._nullSafety);
+ final bool _nullSafety;
FlutterWebManager _flutterWebManager;
DartAnalysisServerWrapper _dartAnalysisServer;
@@ -44,9 +45,9 @@
Future<void> warmup() async {
_logger.info('Beginning AnalysisServersWrapper init().');
- _dartAnalysisServer = DartAnalysisServerWrapper();
+ _dartAnalysisServer = DartAnalysisServerWrapper(_nullSafety);
_flutterWebManager = FlutterWebManager();
- _flutterAnalysisServer = FlutterAnalysisServerWrapper();
+ _flutterAnalysisServer = FlutterAnalysisServerWrapper(_nullSafety);
await _dartAnalysisServer.init();
_logger.info('Dart analysis server initialized.');
diff --git a/lib/src/common.dart b/lib/src/common.dart
index 6efcb02..07880bd 100644
--- a/lib/src/common.dart
+++ b/lib/src/common.dart
@@ -42,6 +42,15 @@
}
""";
+const sampleCodeWebNullSafe = """
+import 'dart:html';
+
+void main() {
+ print("hello");
+ querySelector('#foo')?.text = 'bar';
+}
+""";
+
const sampleCodeFlutter = '''
import 'package:flutter/material.dart';
@@ -132,6 +141,74 @@
}
''';
+const sampleCodeFlutterCounterNullSafe = r'''
+import 'package:flutter/material.dart';
+
+void main() => runApp(MyApp());
+
+class MyApp extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Flutter Demo',
+ debugShowCheckedModeBanner: false,
+ theme: ThemeData(
+ primarySwatch: Colors.blue,
+ ),
+ home: MyHomePage(title: 'Flutter Demo Home Page'),
+ );
+ }
+}
+
+class MyHomePage extends StatefulWidget {
+ final String title;
+ MyHomePage({Key? key, required this.title}) : super(key: key);
+
+
+
+ @override
+ _MyHomePageState createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State<MyHomePage> {
+ int _counter = 0;
+
+ void _incrementCounter() {
+ setState(() {
+ _counter++;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(widget.title),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ Text(
+ 'You have pushed the button this many times:',
+ ),
+ Text(
+ '$_counter',
+ style: Theme.of(context).textTheme.headline4,
+ ),
+ ],
+ ),
+ ),
+ floatingActionButton: FloatingActionButton(
+ onPressed: _incrementCounter,
+ tooltip: 'Increment',
+ child: Icon(Icons.add),
+ ),
+ );
+ }
+}
+''';
+
// From https://gist.github.com/johnpryan/b3ccb26497ac84895540185935ed5825
const sampleCodeFlutterSunflower = '''
import 'package:flutter/material.dart';
@@ -272,6 +349,145 @@
}
''';
+const sampleCodeFlutterSunflowerNullSafe = r'''
+import 'dart:math' as math;
+import 'package:flutter/material.dart';
+
+final Color primaryColor = Colors.orange;
+final TargetPlatform platform = TargetPlatform.android;
+
+void main() {
+ runApp(Sunflower());
+}
+
+class SunflowerPainter extends CustomPainter {
+ static const seedRadius = 2.0;
+ static const scaleFactor = 4;
+ static const tau = math.pi * 2;
+
+ static final phi = (math.sqrt(5) + 1) / 2;
+
+ final int seeds;
+
+ SunflowerPainter(this.seeds);
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ final center = size.width / 2;
+
+ for (var i = 0; i < seeds; i++) {
+ final theta = i * tau / phi;
+ final r = math.sqrt(i) * scaleFactor;
+ final x = center + r * math.cos(theta);
+ final y = center - r * math.sin(theta);
+ final offset = Offset(x, y);
+ if (!size.contains(offset)) {
+ continue;
+ }
+ drawSeed(canvas, x, y);
+ }
+ }
+
+ @override
+ bool shouldRepaint(SunflowerPainter oldDelegate) {
+ return oldDelegate.seeds != this.seeds;
+ }
+
+ // Draw a small circle representing a seed centered at (x,y).
+ void drawSeed(Canvas canvas, double x, double y) {
+ final paint = Paint()
+ ..strokeWidth = 2
+ ..style = PaintingStyle.fill
+ ..color = primaryColor;
+ canvas.drawCircle(Offset(x, y), seedRadius, paint);
+ }
+}
+
+class Sunflower extends StatefulWidget {
+ @override
+ State<StatefulWidget> createState() {
+ return _SunflowerState();
+ }
+}
+
+class _SunflowerState extends State<Sunflower> {
+ double seeds = 100.0;
+
+ int get seedCount => seeds.floor();
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ debugShowCheckedModeBanner: false,
+ theme: ThemeData().copyWith(
+ platform: platform,
+ brightness: Brightness.dark,
+ sliderTheme: SliderThemeData.fromPrimaryColors(
+ primaryColor: primaryColor,
+ primaryColorLight: primaryColor,
+ primaryColorDark: primaryColor,
+ valueIndicatorTextStyle: DefaultTextStyle.fallback().style,
+ ),
+ ),
+ home: Scaffold(
+ appBar: AppBar(title: Text("Sunflower")),
+ drawer: Drawer(
+ child: ListView(
+ children: [
+ DrawerHeader(
+ child: Center(
+ child: Container(
+ child: Text(
+ "Sunflower 🌻",
+ style: TextStyle(fontSize: 32),
+ ),
+ ),
+ ),
+ ),
+ ],
+ )),
+ body: Container(
+ constraints: BoxConstraints.expand(),
+ decoration:
+ BoxDecoration(border: Border.all(color: Colors.transparent)),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Container(
+ decoration: BoxDecoration(
+ border: Border.all(color: Colors.transparent)),
+ child: SizedBox(
+ width: 400,
+ height: 400,
+ child: CustomPaint(
+ painter: SunflowerPainter(seedCount),
+ ),
+ ),
+ ),
+ Text("Showing $seedCount seeds"),
+ ConstrainedBox(
+ constraints: BoxConstraints.tightFor(width: 300),
+ child: Slider.adaptive(
+ min: 20,
+ max: 2000,
+ value: seeds,
+ onChanged: (newValue) {
+ setState(() {
+ seeds = newValue;
+ });
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
+''';
+
// From https://gist.github.com/RedBrogdon/ecb28c29c646b7f38139b1e7f44129b7
const sampleCodeFlutterDraggableCard = '''
import 'package:flutter/material.dart';
@@ -387,6 +603,120 @@
}
''';
+const sampleCodeFlutterDraggableCardNullSafe = '''
+import 'package:flutter/material.dart';
+import 'package:flutter/physics.dart';
+
+main() {
+ runApp(
+ MaterialApp(
+ debugShowCheckedModeBanner: false,
+ home: PhysicsCardDragDemo(),
+ ),
+ );
+}
+
+class PhysicsCardDragDemo extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('A draggable card!'),
+ ),
+ body: DraggableCard(
+ child: FlutterLogo(
+ size: 128,
+ ),
+ ),
+ );
+ }
+}
+
+class DraggableCard extends StatefulWidget {
+ final Widget child;
+ DraggableCard({required this.child});
+
+ @override
+ _DraggableCardState createState() => _DraggableCardState();
+}
+
+class _DraggableCardState extends State<DraggableCard>
+ with SingleTickerProviderStateMixin {
+ AnimationController? _controller;
+ Alignment _dragAlignment = Alignment.center;
+ Animation<Alignment>? _animation;
+
+ void _runAnimation(Offset pixelsPerSecond, Size size) {
+ _animation = _controller!.drive(
+ AlignmentTween(
+ begin: _dragAlignment,
+ end: Alignment.center,
+ ),
+ );
+
+ final unitsPerSecondX = pixelsPerSecond.dx / size.width;
+ final unitsPerSecondY = pixelsPerSecond.dy / size.height;
+ final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
+ final unitVelocity = unitsPerSecond.distance;
+
+ const spring = SpringDescription(
+ mass: 30,
+ stiffness: 1,
+ damping: 1,
+ );
+
+ final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
+
+ _controller!.animateWith(simulation);
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ _controller = AnimationController(vsync: this);
+
+ _controller!.addListener(() {
+ setState(() {
+ _dragAlignment = _animation!.value;
+ });
+ });
+ }
+
+ @override
+ void dispose() {
+ _controller!.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final size = MediaQuery.of(context).size;
+ return GestureDetector(
+ onPanDown: (details) {
+ _controller!.stop();
+ },
+ onPanUpdate: (details) {
+ setState(() {
+ _dragAlignment += Alignment(
+ details.delta.dx / (size.width / 2),
+ details.delta.dy / (size.height / 2),
+ );
+ });
+ },
+ onPanEnd: (details) {
+ _runAnimation(details.velocity.pixelsPerSecond, size);
+ },
+ child: Align(
+ alignment: _dragAlignment,
+ child: Card(
+ child: widget.child,
+ ),
+ ),
+ );
+ }
+}
+''';
+
// From https://gist.github.com/RedBrogdon/40308e0a5f47acba46ba62f4d8be2bf4
const sampleCodeFlutterImplicitAnimations = '''
import 'package:flutter/material.dart';
@@ -493,6 +823,111 @@
}
''';
+const sampleCodeFlutterImplicitAnimationsNullSafe = '''
+import 'dart:math';
+import 'package:flutter/material.dart';
+
+
+class DiscData {
+ static final _rng = Random();
+
+ final double size;
+ final Color color;
+ final Alignment alignment;
+
+ DiscData()
+ : size = _rng.nextDouble() * 40 + 10,
+ color = Color.fromARGB(
+ _rng.nextInt(200),
+ _rng.nextInt(255),
+ _rng.nextInt(255),
+ _rng.nextInt(255),
+ ),
+ alignment = Alignment(
+ _rng.nextDouble() * 2 - 1,
+ _rng.nextDouble() * 2 - 1,
+ );
+}
+
+void main() async {
+ runApp(
+ MaterialApp(
+ debugShowCheckedModeBanner: false,
+ home: Scaffold(
+ body: Container(
+ color: Color(0xFF15202D),
+ child: SizedBox.expand(
+ child: VariousDiscs(50),
+ ),
+ ),
+ ),
+ ),
+ );
+}
+
+class VariousDiscs extends StatefulWidget {
+ final int numberOfDiscs;
+
+ VariousDiscs(this.numberOfDiscs);
+
+ @override
+ _VariousDiscsState createState() => _VariousDiscsState();
+}
+
+class _VariousDiscsState extends State<VariousDiscs> {
+ final _discs = <DiscData>[];
+
+ @override
+ void initState() {
+ super.initState();
+ _makeDiscs();
+ }
+
+ void _makeDiscs() {
+ _discs.clear();
+ for (int i = 0; i < widget.numberOfDiscs; i++) {
+ _discs.add(DiscData());
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: () => setState(() {
+ _makeDiscs();
+ }),
+ child: Stack(
+ children: [
+ Center(
+ child: Text(
+ 'Click a disc!',
+ style: TextStyle(color: Colors.white, fontSize: 50),
+ ),
+ ),
+ for (final disc in _discs)
+ Positioned.fill(
+ child: AnimatedAlign(
+ duration: Duration(milliseconds: 500),
+ curve: Curves.easeInOut,
+ alignment: disc.alignment,
+ child: AnimatedContainer(
+ duration: Duration(milliseconds: 500),
+ decoration: BoxDecoration(
+ color: disc.color,
+ shape: BoxShape.circle,
+ ),
+ height: disc.size,
+ width: disc.size,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
+''';
+
const sampleCodeMultiFoo = """
import 'bar.dart';
@@ -518,6 +953,17 @@
}
""";
+const sampleCodeAsyncNullSafe = """
+import 'dart:html';
+
+void main() async {
+ print("hello");
+ querySelector('#foo')?.text = 'bar';
+ var foo = await HttpRequest.getString('http://www.google.com');
+ print(foo);
+}
+""";
+
const sampleCodeError = '''
void main() {
print("hello")
diff --git a/lib/src/common_server_impl.dart b/lib/src/common_server_impl.dart
index c826d2e..214d5a7 100644
--- a/lib/src/common_server_impl.dart
+++ b/lib/src/common_server_impl.dart
@@ -37,6 +37,7 @@
class CommonServerImplProxy implements CommonServerImpl {
const CommonServerImplProxy(this._proxyTarget);
final String _proxyTarget;
+ final bool _nullSafety = false;
Future<R> _postToProxy<R extends $pb.GeneratedMessage>(
String url, $pb.GeneratedMessage request, R responseProto) async {
@@ -163,6 +164,7 @@
class CommonServerImpl {
final ServerContainer _container;
final ServerCache _cache;
+ final bool _nullSafety;
Compiler _compiler;
AnalysisServersWrapper _analysisServers;
@@ -175,6 +177,7 @@
CommonServerImpl(
this._container,
this._cache,
+ this._nullSafety,
) {
hierarchicalLoggingEnabled = true;
log.level = Level.ALL;
@@ -182,8 +185,8 @@
Future<void> init() async {
log.info('Beginning CommonServer init().');
- _analysisServers = AnalysisServersWrapper();
- _compiler = Compiler(Sdk());
+ _analysisServers = AnalysisServersWrapper(_nullSafety);
+ _compiler = Compiler(Sdk(), _nullSafety);
await _compiler.warmup();
await _analysisServers.warmup();
diff --git a/lib/src/compiler.dart b/lib/src/compiler.dart
index f7bc73d..44bdb84 100644
--- a/lib/src/compiler.dart
+++ b/lib/src/compiler.dart
@@ -26,8 +26,9 @@
final FlutterWebManager _flutterWebManager;
final String _dartdevcPath;
final BazelWorkerDriver _ddcDriver;
+ final bool _nullSafety;
- Compiler(this._sdk)
+ Compiler(this._sdk, this._nullSafety)
: _dartdevcPath = path.join(Sdk.sdkPath, 'bin', 'dartdevc'),
_ddcDriver = BazelWorkerDriver(
() => Process.start(
@@ -63,7 +64,8 @@
_logger.info('Temp directory created: ${temp.path}');
try {
- await copyPath(FlutterWebManager.dartTemplateProject.path, temp.path);
+ await copyPath(
+ FlutterWebManager.dartTemplateProject(_nullSafety).path, temp.path);
await Directory(path.join(temp.path, 'lib')).create(recursive: true);
final arguments = <String>[
@@ -71,6 +73,10 @@
'--terse',
if (!returnSourceMap) '--no-source-maps',
'--packages=${path.join('.dart_tool', 'package_config.json')}',
+ if (_nullSafety) ...[
+ '--sound-null-safety',
+ '--enable-experiment=non-nullable',
+ ],
...['-o', '$kMainDart.js'],
path.join('lib', kMainDart),
];
@@ -131,9 +137,11 @@
final usingFlutter = _flutterWebManager.usesFlutterWeb(imports);
if (usingFlutter) {
await copyPath(
- FlutterWebManager.flutterTemplateProject.path, temp.path);
+ FlutterWebManager.flutterTemplateProject(_nullSafety).path,
+ temp.path);
} else {
- await copyPath(FlutterWebManager.dartTemplateProject.path, temp.path);
+ await copyPath(
+ FlutterWebManager.dartTemplateProject(_nullSafety).path, temp.path);
}
await Directory(path.join(temp.path, 'lib')).create(recursive: true);
@@ -150,12 +158,19 @@
'--modules=amd',
if (usingFlutter) ...[
'-s',
- _flutterWebManager.summaryFilePath,
+ _flutterWebManager.summaryFilePath(_nullSafety),
'-s',
- '${Sdk.flutterBinPath}/cache/flutter_web_sdk/flutter_web_sdk/kernel/flutter_ddc_sdk.dill'
+ '${Sdk.flutterBinPath}/cache/flutter_web_sdk/flutter_web_sdk/kernel/' +
+ (_nullSafety
+ ? 'flutter_ddc_sdk_sound.dill'
+ : 'flutter_ddc_sdk.dill'),
],
...['-o', path.join(temp.path, '$kMainDart.js')],
...['--module-name', 'dartpad_main'],
+ if (_nullSafety) ...[
+ '--sound-null-safety',
+ '--enable-experiment=non-nullable',
+ ],
bootstrapPath,
'--packages=${path.join(temp.path, '.dart_tool', 'package_config.json')}',
];
@@ -184,7 +199,8 @@
final results = DDCCompilationResults(
compiledJS: processedJs,
modulesBaseUrl: 'https://storage.googleapis.com/'
- 'compilation_artifacts/${_sdk.versionFull}/',
+ '${_nullSafety ? 'nnbd_artifacts' : 'compilation_artifacts'}'
+ '/${_sdk.versionFull}/',
);
return results;
}
diff --git a/lib/src/flutter_web.dart b/lib/src/flutter_web.dart
index 0f0bf2c..99048ae 100644
--- a/lib/src/flutter_web.dart
+++ b/lib/src/flutter_web.dart
@@ -8,16 +8,29 @@
/// Support for handling Flutter web snippets.
class FlutterWebManager {
- static final Directory flutterTemplateProject = Directory(path.join(
- Directory.current.path, 'project_templates', 'flutter_project'));
+ static Directory flutterTemplateProject(bool nullSafety) =>
+ Directory(path.join(
+ Directory.current.path,
+ 'project_templates',
+ nullSafety ? 'null-safe' : 'null-unsafe',
+ 'flutter_project',
+ ));
- static final Directory dartTemplateProject = Directory(
- path.join(Directory.current.path, 'project_templates', 'dart_project'));
+ static Directory dartTemplateProject(bool nullSafety) => Directory(path.join(
+ Directory.current.path,
+ 'project_templates',
+ nullSafety ? 'null-safe' : 'null-unsafe',
+ 'dart_project',
+ ));
FlutterWebManager();
- String get summaryFilePath {
- return path.join('artifacts', 'flutter_web.dill');
+ String summaryFilePath(bool nullSafety) {
+ return path.join(
+ 'artifacts',
+ nullSafety ? 'null-safe' : 'null-unsafe',
+ 'flutter_web.dill',
+ );
}
static const Set<String> _flutterWebImportPrefixes = {
diff --git a/test/analysis_server_test.dart b/test/analysis_server_test.dart
index a2021cb..fbd0b0c 100644
--- a/test/analysis_server_test.dart
+++ b/test/analysis_server_test.dart
@@ -61,166 +61,173 @@
void defineTests() {
AnalysisServerWrapper analysisServer;
- group('Platform SDK analysis_server', () {
- setUp(() async {
- analysisServer = DartAnalysisServerWrapper();
- await analysisServer.init();
- });
+ for (final nullSafety in [false, true]) {
+ group('Null ${nullSafety ? 'Safe' : 'Unsafe'} Platform SDK analysis_server',
+ () {
+ setUp(() async {
+ analysisServer = DartAnalysisServerWrapper(nullSafety);
+ await analysisServer.init();
+ });
- tearDown(() => analysisServer.shutdown());
+ tearDown(() => analysisServer.shutdown());
- test('simple_completion', () async {
- // Just after i.
- final results = await analysisServer.complete(completionCode, 32);
- expect(results.replacementLength, 0);
- expect(results.replacementOffset, 32);
- expectCompletionsContains(results, 'abs');
- expect(completionsContains(results, 'codeUnitAt'), false);
- });
+ test('simple_completion', () async {
+ // Just after i.
+ final results = await analysisServer.complete(completionCode, 32);
+ expect(results.replacementLength, 0);
+ expect(results.replacementOffset, 32);
+ expectCompletionsContains(results, 'abs');
+ expect(completionsContains(results, 'codeUnitAt'), false);
+ });
- test('repro #126 - completions polluted on second request', () async {
- // https://github.com/dart-lang/dart-services/issues/126
- return analysisServer.complete(completionFilterCode, 17).then((results) {
+ test('repro #126 - completions polluted on second request', () async {
+ // https://github.com/dart-lang/dart-services/issues/126
return analysisServer
.complete(completionFilterCode, 17)
.then((results) {
- expect(results.replacementLength, 2);
- expect(results.replacementOffset, 16);
- expect(completionsContains(results, 'print'), true);
- expect(completionsContains(results, 'pow'), false);
+ return analysisServer
+ .complete(completionFilterCode, 17)
+ .then((results) {
+ expect(results.replacementLength, 2);
+ expect(results.replacementOffset, 16);
+ expect(completionsContains(results, 'print'), true);
+ expect(completionsContains(results, 'pow'), false);
+ });
});
});
+
+ test('import_test', () async {
+ // We're testing here that we don't have any path imports - we don't want
+ // to enable browsing the file system.
+ final testCode = "import '/'; main() { int a = 0; a. }";
+ final results = await analysisServer.complete(testCode, 9);
+ final completions = results.completions;
+
+ if (completions.isNotEmpty) {
+ expect(completions.every((completion) {
+ return completion.completion['completion'].startsWith('dart:');
+ }), true);
+ }
+ });
+
+ test('import_dart_core_test', () async {
+ // Ensure we can import dart: imports.
+ final testCode = "import 'dart:c'; main() { int a = 0; a. }";
+
+ final results = await analysisServer.complete(testCode, 14);
+ final completions = results.completions;
+
+ expect(
+ completions.every((completion) =>
+ completion.completion['completion'].startsWith('dart:')),
+ true,
+ );
+ expect(
+ completions.any((completion) =>
+ completion.completion['completion'].startsWith('dart:')),
+ true,
+ );
+ });
+
+ test('import_and_other_test', () async {
+ final testCode = "import '/'; main() { int a = 0; a. }";
+ final results = await analysisServer.complete(testCode, 34);
+
+ expect(completionsContains(results, 'abs'), true);
+ });
+
+ test('simple_quickFix', () async {
+ final results = await analysisServer.getFixes(quickFixesCode, 25);
+
+ expect(results.fixes.length, 2);
+
+ // Fixes are not guaranteed to arrive in a particular order.
+ results.fixes.sort((a, b) => a.offset.compareTo(b.offset));
+
+ expect(results.fixes[0].offset, 20);
+ expect(results.fixes[0].length, 1); // We need an insertion.
+
+ expect(results.fixes[1].offset, 24);
+ expect(results.fixes[1].length, 1); // We need an insertion.
+
+ expect(results.fixes[1].fixes.length, 1);
+
+ final candidateFix = results.fixes[1].fixes[0];
+
+ expect(candidateFix.message.contains(';'), true);
+ expect(candidateFix.edits[0].length, 0);
+ expect(candidateFix.edits[0].offset, 25);
+ expect(candidateFix.edits[0].replacement, ';');
+ });
+
+ test('simple_format', () async {
+ final results = await analysisServer.format(badFormatCode, 0);
+ expect(results.newString, formattedCode);
+ });
+
+ test('format good code', () async {
+ final results =
+ await analysisServer.format(formattedCode.replaceAll('\n', ' '), 0);
+ expect(results.newString, formattedCode);
+ });
+
+ test('format with issues', () async {
+ final results = await analysisServer.format(formatWithIssues, 0);
+ expect(results.newString, formatWithIssues);
+ });
+
+ test('analyze', () async {
+ final results = await analysisServer.analyze(sampleCode);
+ expect(results.issues, isEmpty);
+ });
+
+ test('analyze with errors', () async {
+ final results = await analysisServer.analyze(sampleCodeError);
+ expect(results.issues, hasLength(1));
+ });
+
+ test('analyze strong', () async {
+ final results = await analysisServer.analyze(sampleStrongError);
+ expect(results.issues, hasLength(1));
+ final issue = results.issues.first;
+ expect(issue.kind, 'error');
+ });
+
+ test('filter completions', () async {
+ // just after A
+ final idx = 61;
+ expect(completionLargeNamespaces.substring(idx - 1, idx), 'A');
+ final results =
+ await analysisServer.complete(completionLargeNamespaces, 61);
+ expect(completionsContains(results, 'A'), true);
+ expect(completionsContains(results, 'AB'), true);
+ expect(completionsContains(results, 'ABC'), true);
+ expect(completionsContains(results, 'a'), true);
+ expect(completionsContains(results, 'ZZ'), false);
+ });
});
- test('import_test', () async {
- // We're testing here that we don't have any path imports - we don't want
- // to enable browsing the file system.
- final testCode = "import '/'; main() { int a = 0; a. }";
- final results = await analysisServer.complete(testCode, 9);
- final completions = results.completions;
+ group(
+ 'Null ${nullSafety ? 'Safe' : 'Unsafe'} Flutter cached SDK analysis_server',
+ () {
+ setUp(() async {
+ analysisServer = FlutterAnalysisServerWrapper(nullSafety);
+ await analysisServer.init();
+ });
- if (completions.isNotEmpty) {
- expect(completions.every((completion) {
- return completion.completion['completion'].startsWith('dart:');
- }), true);
- }
+ tearDown(() => analysisServer.shutdown());
+
+ test('analyze working Dart code', () async {
+ final results = await analysisServer.analyze(sampleCode);
+ expect(results.issues, isEmpty);
+ });
+
+ test('analyze working Flutter code', () async {
+ final results = await analysisServer.analyze(sampleCode);
+ expect(results.issues, isEmpty);
+ });
});
-
- test('import_dart_core_test', () async {
- // Ensure we can import dart: imports.
- final testCode = "import 'dart:c'; main() { int a = 0; a. }";
-
- final results = await analysisServer.complete(testCode, 14);
- final completions = results.completions;
-
- expect(
- completions.every((completion) =>
- completion.completion['completion'].startsWith('dart:')),
- true,
- );
- expect(
- completions.any((completion) =>
- completion.completion['completion'].startsWith('dart:')),
- true,
- );
- });
-
- test('import_and_other_test', () async {
- final testCode = "import '/'; main() { int a = 0; a. }";
- final results = await analysisServer.complete(testCode, 34);
-
- expect(completionsContains(results, 'abs'), true);
- });
-
- test('simple_quickFix', () async {
- final results = await analysisServer.getFixes(quickFixesCode, 25);
-
- expect(results.fixes.length, 2);
-
- // Fixes are not guaranteed to arrive in a particular order.
- results.fixes.sort((a, b) => a.offset.compareTo(b.offset));
-
- expect(results.fixes[0].offset, 20);
- expect(results.fixes[0].length, 1); // We need an insertion.
-
- expect(results.fixes[1].offset, 24);
- expect(results.fixes[1].length, 1); // We need an insertion.
-
- expect(results.fixes[1].fixes.length, 1);
-
- final candidateFix = results.fixes[1].fixes[0];
-
- expect(candidateFix.message.contains(';'), true);
- expect(candidateFix.edits[0].length, 0);
- expect(candidateFix.edits[0].offset, 25);
- expect(candidateFix.edits[0].replacement, ';');
- });
-
- test('simple_format', () async {
- final results = await analysisServer.format(badFormatCode, 0);
- expect(results.newString, formattedCode);
- });
-
- test('format good code', () async {
- final results =
- await analysisServer.format(formattedCode.replaceAll('\n', ' '), 0);
- expect(results.newString, formattedCode);
- });
-
- test('format with issues', () async {
- final results = await analysisServer.format(formatWithIssues, 0);
- expect(results.newString, formatWithIssues);
- });
-
- test('analyze', () async {
- final results = await analysisServer.analyze(sampleCode);
- expect(results.issues, isEmpty);
- });
-
- test('analyze with errors', () async {
- final results = await analysisServer.analyze(sampleCodeError);
- expect(results.issues, hasLength(1));
- });
-
- test('analyze strong', () async {
- final results = await analysisServer.analyze(sampleStrongError);
- expect(results.issues, hasLength(1));
- final issue = results.issues.first;
- expect(issue.kind, 'error');
- });
-
- test('filter completions', () async {
- // just after A
- final idx = 61;
- expect(completionLargeNamespaces.substring(idx - 1, idx), 'A');
- final results =
- await analysisServer.complete(completionLargeNamespaces, 61);
- expect(completionsContains(results, 'A'), true);
- expect(completionsContains(results, 'AB'), true);
- expect(completionsContains(results, 'ABC'), true);
- expect(completionsContains(results, 'a'), true);
- expect(completionsContains(results, 'ZZ'), false);
- });
- });
-
- group('Flutter cached SDK analysis_server', () {
- setUp(() async {
- analysisServer = FlutterAnalysisServerWrapper();
- await analysisServer.init();
- });
-
- tearDown(() => analysisServer.shutdown());
-
- test('analyze working Dart code', () async {
- final results = await analysisServer.analyze(sampleCode);
- expect(results.issues, isEmpty);
- });
-
- test('analyze working Flutter code', () async {
- final results = await analysisServer.analyze(sampleCode);
- expect(results.issues, isEmpty);
- });
- });
+ }
}
bool completionsContains(proto.CompleteResponse response, String expected) =>
diff --git a/test/common_server_api_protobuf_test.dart b/test/common_server_api_protobuf_test.dart
index 52c86e1..ed0b8c4 100644
--- a/test/common_server_api_protobuf_test.dart
+++ b/test/common_server_api_protobuf_test.dart
@@ -90,7 +90,7 @@
setUpAll(() async {
container = MockContainer();
cache = MockCache();
- commonServerImpl = CommonServerImpl(container, cache);
+ commonServerImpl = CommonServerImpl(container, cache, false);
commonServerApi = CommonServerApi(commonServerImpl);
await commonServerImpl.init();
diff --git a/test/common_server_api_test.dart b/test/common_server_api_test.dart
index 50a935e..1071556 100644
--- a/test/common_server_api_test.dart
+++ b/test/common_server_api_test.dart
@@ -275,7 +275,7 @@
setUpAll(() async {
container = MockContainer();
cache = MockCache();
- commonServerImpl = CommonServerImpl(container, cache);
+ commonServerImpl = CommonServerImpl(container, cache, false);
commonServerApi = CommonServerApi(commonServerImpl);
await commonServerImpl.init();
diff --git a/test/compiler_test.dart b/test/compiler_test.dart
index c89fcad..2cc737e 100644
--- a/test/compiler_test.dart
+++ b/test/compiler_test.dart
@@ -14,130 +14,143 @@
void defineTests() {
Compiler compiler;
- group('compiler', () {
- setUpAll(() async {
- compiler = Compiler(Sdk());
- await compiler.warmup();
- });
+ for (final nullSafety in [false, true]) {
+ group('Null ${nullSafety ? 'Safe' : 'Unsafe'} Compiler', () {
+ setUpAll(() async {
+ compiler = Compiler(Sdk(), nullSafety);
+ await compiler.warmup();
+ });
- tearDownAll(() async {
- await compiler.dispose();
- });
+ tearDownAll(() async {
+ await compiler.dispose();
+ });
- test('simple', () async {
- final result = await compiler.compile(sampleCode);
- print(result.problems);
+ test('simple', () async {
+ final result = await compiler.compile(sampleCode);
- expect(result.success, true);
- expect(result.compiledJS, isNotEmpty);
- expect(result.sourceMap, isNull);
- });
+ expect(result.success, true);
+ expect(result.compiledJS, isNotEmpty);
+ expect(result.sourceMap, isNull);
+ });
- Future<void> Function() _generateCompilerDDCTest(String sample) =>
- () async {
- final result = await compiler.compileDDC(sample);
- print(result.problems);
+ Future<void> Function() _generateCompilerDDCTest(String sample) =>
+ () async {
+ final result = await compiler.compileDDC(sample);
- expect(result.success, true);
- expect(result.compiledJS, isNotEmpty);
- expect(result.modulesBaseUrl, isNotEmpty);
+ expect(result.success, true);
+ expect(result.compiledJS, isNotEmpty);
+ expect(result.modulesBaseUrl, isNotEmpty);
- expect(result.compiledJS, contains("define('dartpad_main', ["));
- };
+ expect(result.compiledJS, contains("define('dartpad_main', ["));
+ };
- test(
- 'compileDDC simple',
- _generateCompilerDDCTest(sampleCode),
- );
+ test(
+ 'compileDDC simple',
+ _generateCompilerDDCTest(sampleCode),
+ );
- test(
- 'compileDDC with web',
- _generateCompilerDDCTest(sampleCodeWeb),
- );
+ test(
+ 'compileDDC with web',
+ _generateCompilerDDCTest(
+ nullSafety ? sampleCodeWebNullSafe : sampleCodeWeb),
+ );
- test(
- 'compileDDC with Flutter',
- _generateCompilerDDCTest(sampleCodeFlutter),
- );
+ test(
+ 'compileDDC with Flutter',
+ _generateCompilerDDCTest(sampleCodeFlutter),
+ );
- test(
- 'compileDDC with Flutter Counter',
- _generateCompilerDDCTest(sampleCodeFlutterCounter),
- );
+ test(
+ 'compileDDC with Flutter Counter',
+ _generateCompilerDDCTest(nullSafety
+ ? sampleCodeFlutterCounterNullSafe
+ : sampleCodeFlutterCounter),
+ );
- test(
- 'compileDDC with Flutter Sunflower',
- _generateCompilerDDCTest(sampleCodeFlutterSunflower),
- );
+ test(
+ 'compileDDC with Flutter Sunflower',
+ _generateCompilerDDCTest(nullSafety
+ ? sampleCodeFlutterSunflowerNullSafe
+ : sampleCodeFlutterSunflower),
+ );
- test(
- 'compileDDC with Flutter Draggable Card',
- _generateCompilerDDCTest(sampleCodeFlutterDraggableCard),
- );
+ test(
+ 'compileDDC with Flutter Draggable Card',
+ _generateCompilerDDCTest(nullSafety
+ ? sampleCodeFlutterDraggableCardNullSafe
+ : sampleCodeFlutterDraggableCard),
+ );
- test(
- 'compileDDC with Flutter Implicit Animations',
- _generateCompilerDDCTest(sampleCodeFlutterImplicitAnimations),
- );
+ test(
+ 'compileDDC with Flutter Implicit Animations',
+ _generateCompilerDDCTest(nullSafety
+ ? sampleCodeFlutterImplicitAnimationsNullSafe
+ : sampleCodeFlutterImplicitAnimations),
+ );
- test(
- 'compileDDC with async',
- _generateCompilerDDCTest(sampleCodeAsync),
- );
+ test(
+ 'compileDDC with async',
+ _generateCompilerDDCTest(
+ nullSafety ? sampleCodeAsyncNullSafe : sampleCodeAsync),
+ );
- test('compileDDC with single error', () async {
- final result = await compiler.compileDDC(sampleCodeError);
- expect(result.success, false);
- expect(result.problems.length, 1);
- expect(result.problems[0].toString(),
- contains('Error: Expected \';\' after this.'));
- });
+ test('compileDDC with single error', () async {
+ final result = await compiler.compileDDC(sampleCodeError);
+ expect(result.success, false);
+ expect(result.problems.length, 1);
+ expect(result.problems[0].toString(),
+ contains('Error: Expected \';\' after this.'));
+ });
- test('compileDDC with multiple errors', () async {
- final result = await compiler.compileDDC(sampleCodeErrors);
- expect(result.success, false);
- expect(result.problems.length, 1);
- expect(result.problems[0].toString(),
- contains('Error: Method not found: \'print1\'.'));
- expect(result.problems[0].toString(),
- contains('Error: Method not found: \'print2\'.'));
- expect(result.problems[0].toString(),
- contains('Error: Method not found: \'print3\'.'));
- });
+ test('compileDDC with multiple errors', () async {
+ final result = await compiler.compileDDC(sampleCodeErrors);
+ expect(result.success, false);
+ expect(result.problems.length, 1);
+ expect(result.problems[0].toString(),
+ contains('Error: Method not found: \'print1\'.'));
+ expect(result.problems[0].toString(),
+ contains('Error: Method not found: \'print2\'.'));
+ expect(result.problems[0].toString(),
+ contains('Error: Method not found: \'print3\'.'));
+ });
- test('sourcemap', () async {
- final result = await compiler.compile(sampleCode, returnSourceMap: true);
- expect(result.success, true);
- expect(result.compiledJS, isNotEmpty);
- expect(result.sourceMap, isNotNull);
- expect(result.sourceMap, isNotEmpty);
- });
+ test('sourcemap', () async {
+ final result =
+ await compiler.compile(sampleCode, returnSourceMap: true);
+ expect(result.success, true);
+ expect(result.compiledJS, isNotEmpty);
+ expect(result.sourceMap, isNotNull);
+ expect(result.sourceMap, isNotEmpty);
+ });
- test('version', () async {
- final result = await compiler.compile(sampleCode, returnSourceMap: true);
- expect(result.sourceMap, isNotNull);
- expect(result.sourceMap, isNotEmpty);
- });
+ test('version', () async {
+ final result =
+ await compiler.compile(sampleCode, returnSourceMap: true);
+ expect(result.sourceMap, isNotNull);
+ expect(result.sourceMap, isNotEmpty);
+ });
- test('simple web', () async {
- final result = await compiler.compile(sampleCodeWeb);
- expect(result.success, true);
- });
+ test('simple web', () async {
+ final result = await compiler
+ .compile(nullSafety ? sampleCodeWebNullSafe : sampleCodeWeb);
+ expect(result.success, true);
+ });
- test('web async', () async {
- final result = await compiler.compile(sampleCodeAsync);
- expect(result.success, true);
- });
+ test('web async', () async {
+ final result = await compiler
+ .compile(nullSafety ? sampleCodeAsyncNullSafe : sampleCodeAsync);
+ expect(result.success, true);
+ });
- test('errors', () async {
- final result = await compiler.compile(sampleCodeError);
- expect(result.success, false);
- expect(result.problems.length, 1);
- expect(result.problems[0].toString(), contains('Error: Expected'));
- });
+ test('errors', () async {
+ final result = await compiler.compile(sampleCodeError);
+ expect(result.success, false);
+ expect(result.problems.length, 1);
+ expect(result.problems[0].toString(), contains('Error: Expected'));
+ });
- test('good import', () async {
- const code = '''
+ test('good import', () async {
+ const code = '''
import 'dart:html';
void main() {
@@ -146,47 +159,48 @@
}
''';
- final result = await compiler.compile(code);
- expect(result.problems.length, 0);
- });
+ final result = await compiler.compile(code);
+ expect(result.problems.length, 0);
+ });
- test('bad import - local', () async {
- const code = '''
+ test('bad import - local', () async {
+ const code = '''
import 'foo.dart';
void main() { missingMethod ('foo'); }
''';
- final result = await compiler.compile(code);
- expect(result.problems.first.message,
- equals('unsupported import: foo.dart'));
- });
+ final result = await compiler.compile(code);
+ expect(result.problems.first.message,
+ equals('unsupported import: foo.dart'));
+ });
- test('bad import - http', () async {
- const code = '''
+ test('bad import - http', () async {
+ const code = '''
import 'http://example.com';
void main() { missingMethod ('foo'); }
''';
- final result = await compiler.compile(code);
- expect(result.problems.first.message,
- equals('unsupported import: http://example.com'));
- });
+ final result = await compiler.compile(code);
+ expect(result.problems.first.message,
+ equals('unsupported import: http://example.com'));
+ });
- test('disallow compiler warnings', () async {
- final result = await compiler.compile(sampleCodeErrors);
- expect(result.success, false);
- });
+ test('disallow compiler warnings', () async {
+ final result = await compiler.compile(sampleCodeErrors);
+ expect(result.success, false);
+ });
- test('transitive errors', () async {
- const code = '''
+ test('transitive errors', () async {
+ const code = '''
import 'dart:foo';
void main() { print ('foo'); }
''';
- final result = await compiler.compile(code);
- expect(result.problems.length, 1);
- });
+ final result = await compiler.compile(code);
+ expect(result.problems.length, 1);
+ });
- test('errors for dart 2', () async {
- final result = await compiler.compile(sampleDart2Error);
- expect(result.problems.length, 1);
+ test('errors for dart 2', () async {
+ final result = await compiler.compile(sampleDart2Error);
+ expect(result.problems.length, 1);
+ });
});
- });
+ }
}
diff --git a/test/flutter_analysis_server_test.dart b/test/flutter_analysis_server_test.dart
index c0de77b..8c85674 100644
--- a/test/flutter_analysis_server_test.dart
+++ b/test/flutter_analysis_server_test.dart
@@ -13,271 +13,104 @@
import 'package:dart_services/src/server_cache.dart';
import 'package:test/test.dart';
-const counterApp = r'''
-// Copyright (c) 2019, 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 'package:flutter/material.dart';
-
-void main() => runApp(MyApp());
-
-class MyApp extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Flutter Demo',
- debugShowCheckedModeBanner: false,
- theme: ThemeData(
- primarySwatch: Colors.blue,
- ),
- home: MyHomePage(title: 'Flutter Demo Home Page'),
- );
- }
-}
-
-class MyHomePage extends StatefulWidget {
- MyHomePage({Key key, this.title}) : super(key: key);
-
- final String title;
-
- @override
- _MyHomePageState createState() => _MyHomePageState();
-}
-
-class _MyHomePageState extends State<MyHomePage> {
- int _counter = 0;
-
- void _incrementCounter() {
- setState(() {
- _counter++;
- });
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: Text(widget.title),
- ),
- body: Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: <Widget>[
- Text(
- 'You have pushed the button this many times:',
- ),
- Text(
- '$_counter',
- style: Theme.of(context).textTheme.headline4,
- ),
- ],
- ),
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: _incrementCounter,
- tooltip: 'Increment',
- child: Icon(Icons.add),
- ),
- );
- }
-}
-''';
-
-const draggableAndPhysicsApp = '''
-import 'package:flutter/material.dart';
-import 'package:flutter/physics.dart';
-
-main() {
- runApp(
- MaterialApp(
- debugShowCheckedModeBanner: false,
- home: PhysicsCardDragDemo(),
- ),
- );
-}
-
-class PhysicsCardDragDemo extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: Text('A draggable card!'),
- ),
- body: DraggableCard(
- child: FlutterLogo(
- size: 128,
- ),
- ),
- );
- }
-}
-
-class DraggableCard extends StatefulWidget {
- final Widget child;
- DraggableCard({this.child});
-
- @override
- _DraggableCardState createState() => _DraggableCardState();
-}
-
-class _DraggableCardState extends State<DraggableCard>
- with SingleTickerProviderStateMixin {
- AnimationController _controller;
- Alignment _dragAlignment = Alignment.center;
- Animation<Alignment> _animation;
-
- void _runAnimation(Offset pixelsPerSecond, Size size) {
- _animation = _controller.drive(
- AlignmentTween(
- begin: _dragAlignment,
- end: Alignment.center,
- ),
- );
-
- final unitsPerSecondX = pixelsPerSecond.dx / size.width;
- final unitsPerSecondY = pixelsPerSecond.dy / size.height;
- final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
- final unitVelocity = unitsPerSecond.distance;
-
- const spring = SpringDescription(
- mass: 30,
- stiffness: 1,
- damping: 1,
- );
-
- final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
-
- _controller.animateWith(simulation);
- }
-
- @override
- void initState() {
- super.initState();
- _controller = AnimationController(vsync: this);
-
- _controller.addListener(() {
- setState(() {
- _dragAlignment = _animation.value;
- });
- });
- }
-
- @override
- void dispose() {
- _controller.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- final size = MediaQuery.of(context).size;
- return GestureDetector(
- onPanDown: (details) {
- _controller.stop();
- },
- onPanUpdate: (details) {
- setState(() {
- _dragAlignment += Alignment(
- details.delta.dx / (size.width / 2),
- details.delta.dy / (size.height / 2),
- );
- });
- },
- onPanEnd: (details) {
- _runAnimation(details.velocity.pixelsPerSecond, size);
- },
- child: Align(
- alignment: _dragAlignment,
- child: Card(
- child: widget.child,
- ),
- ),
- );
- }
-}
-''';
-
void main() => defineTests();
void defineTests() {
- group('Flutter SDK analysis_server', () {
- AnalysisServerWrapper analysisServer;
+ for (final nullSafety in [false, true]) {
+ group('Null ${nullSafety ? 'Safe' : 'Unsafe'} Flutter SDK analysis_server',
+ () {
+ AnalysisServerWrapper analysisServer;
- setUp(() async {
- analysisServer = FlutterAnalysisServerWrapper();
- await analysisServer.init();
- await analysisServer.warmup();
+ setUp(() async {
+ analysisServer = FlutterAnalysisServerWrapper(nullSafety);
+ await analysisServer.init();
+ await analysisServer.warmup();
+ });
+
+ tearDown(() async {
+ await analysisServer.shutdown();
+ });
+
+ test('analyze counter app', () async {
+ final results = await analysisServer.analyze(nullSafety
+ ? sampleCodeFlutterCounterNullSafe
+ : sampleCodeFlutterCounter);
+ expect(results.issues, isEmpty);
+ });
+
+ test('analyze Draggable Physics sample', () async {
+ final results = await analysisServer.analyze(nullSafety
+ ? sampleCodeFlutterDraggableCardNullSafe
+ : sampleCodeFlutterDraggableCard);
+ expect(results.issues, isEmpty);
+ });
});
- tearDown(() async {
- await analysisServer.shutdown();
+ group(
+ 'Null ${nullSafety ? 'Safe' : 'Unsafe'} Flutter SDK analysis_server with analysis servers',
+ () {
+ AnalysisServersWrapper analysisServersWrapper;
+
+ setUp(() async {
+ analysisServersWrapper = AnalysisServersWrapper(nullSafety);
+ await analysisServersWrapper.warmup();
+ });
+
+ tearDown(() async {
+ await analysisServersWrapper.shutdown();
+ });
+
+ test('analyze counter app', () async {
+ final results = await analysisServersWrapper.analyze(nullSafety
+ ? sampleCodeFlutterCounterNullSafe
+ : sampleCodeFlutterCounter);
+ expect(results.issues, isEmpty);
+ });
+
+ test('analyze Draggable Physics sample', () async {
+ final results = await analysisServersWrapper.analyze(nullSafety
+ ? sampleCodeFlutterDraggableCardNullSafe
+ : sampleCodeFlutterDraggableCard);
+ expect(results.issues, isEmpty);
+ });
});
- test('analyze counter app', () async {
- final results = await analysisServer.analyze(counterApp);
- expect(results.issues, isEmpty);
+ group(
+ 'Null ${nullSafety ? 'Safe' : 'Unsafe'} CommonServerImpl flutter analyze',
+ () {
+ CommonServerImpl commonServerImpl;
+
+ _MockContainer container;
+ _MockCache cache;
+
+ setUp(() async {
+ container = _MockContainer();
+ cache = _MockCache();
+ commonServerImpl = CommonServerImpl(container, cache, nullSafety);
+ await commonServerImpl.init();
+ });
+
+ tearDown(() async {
+ await commonServerImpl.shutdown();
+ });
+
+ test('counter app', () async {
+ final results = await commonServerImpl.analyze(SourceRequest()
+ ..source = nullSafety
+ ? sampleCodeFlutterCounterNullSafe
+ : sampleCodeFlutterCounter);
+ expect(results.issues, isEmpty);
+ });
+
+ test('Draggable Physics sample', () async {
+ final results = await commonServerImpl.analyze(SourceRequest()
+ ..source = nullSafety
+ ? sampleCodeFlutterDraggableCardNullSafe
+ : sampleCodeFlutterDraggableCard);
+ expect(results.issues, isEmpty);
+ });
});
-
- test('analyze Draggable Physics sample', () async {
- final results = await analysisServer.analyze(draggableAndPhysicsApp);
- expect(results.issues, isEmpty);
- });
- });
-
- group('Flutter SDK analysis_server with analysis servers', () {
- AnalysisServersWrapper analysisServersWrapper;
-
- setUp(() async {
- analysisServersWrapper = AnalysisServersWrapper();
- await analysisServersWrapper.warmup();
- });
-
- tearDown(() async {
- await analysisServersWrapper.shutdown();
- });
-
- test('analyze counter app', () async {
- final results = await analysisServersWrapper.analyze(counterApp);
- expect(results.issues, isEmpty);
- });
-
- test('analyze Draggable Physics sample', () async {
- final results =
- await analysisServersWrapper.analyze(draggableAndPhysicsApp);
- expect(results.issues, isEmpty);
- });
- });
-
- group('CommonServerImpl flutter analyze', () {
- CommonServerImpl commonServerImpl;
-
- _MockContainer container;
- _MockCache cache;
-
- setUp(() async {
- container = _MockContainer();
- cache = _MockCache();
- commonServerImpl = CommonServerImpl(container, cache);
- await commonServerImpl.init();
- });
-
- tearDown(() async {
- await commonServerImpl.shutdown();
- });
-
- test('counter app', () async {
- final results =
- await commonServerImpl.analyze(SourceRequest()..source = counterApp);
- expect(results.issues, isEmpty);
- });
-
- test('Draggable Physics sample', () async {
- final results = await commonServerImpl
- .analyze(SourceRequest()..source = draggableAndPhysicsApp);
- expect(results.issues, isEmpty);
- });
- });
+ }
}
class _MockContainer implements ServerContainer {
diff --git a/test/flutter_web_test.dart b/test/flutter_web_test.dart
index c8058fa..45f080c 100644
--- a/test/flutter_web_test.dart
+++ b/test/flutter_web_test.dart
@@ -12,70 +12,77 @@
void main() => defineTests();
void defineTests() {
- group('FlutterWebManager', () {
- FlutterWebManager flutterWebManager;
+ for (final nullSafety in [false, true]) {
+ group('Null ${nullSafety ? 'Safe' : 'Unsafe'} FlutterWebManager', () {
+ FlutterWebManager flutterWebManager;
- setUp(() async {
- flutterWebManager = FlutterWebManager();
+ setUp(() async {
+ flutterWebManager = FlutterWebManager();
+ });
+
+ test('inited', () async {
+ expect(
+ await FlutterWebManager.flutterTemplateProject(nullSafety).exists(),
+ isTrue);
+ final file = File(path.join(
+ FlutterWebManager.flutterTemplateProject(nullSafety).path,
+ '.dart_tool',
+ 'package_config.json'));
+ expect(await file.exists(), isTrue);
+ });
+
+ test('usesFlutterWeb', () {
+ expect(flutterWebManager.usesFlutterWeb({''}), isFalse);
+ expect(flutterWebManager.usesFlutterWeb({'dart:html'}), isFalse);
+ expect(flutterWebManager.usesFlutterWeb({'dart:ui'}), isTrue);
+ expect(flutterWebManager.usesFlutterWeb({'package:flutter'}), isTrue);
+ expect(flutterWebManager.usesFlutterWeb({'package:flutter/'}), isTrue);
+ });
+
+ test('getUnsupportedImport', () {
+ expect(flutterWebManager.getUnsupportedImport({'dart:html'}), isNull);
+ expect(flutterWebManager.getUnsupportedImport({'dart:ui'}), isNull);
+ expect(flutterWebManager.getUnsupportedImport({'package:flutter/'}),
+ isNull);
+ expect(flutterWebManager.getUnsupportedImport({'package:path'}),
+ equals('package:path'));
+ expect(flutterWebManager.getUnsupportedImport({'foo.dart'}),
+ equals('foo.dart'));
+ // dart:io is an unsupported package
+ expect(flutterWebManager.getUnsupportedImport({'dart:io'}),
+ equals('dart:io'));
+ });
});
- test('inited', () async {
- expect(await FlutterWebManager.flutterTemplateProject.exists(), isTrue);
- final file = File(path.join(FlutterWebManager.flutterTemplateProject.path,
- '.dart_tool', 'package_config.json'));
- expect(await file.exists(), isTrue);
+ group('Null ${nullSafety ? 'Safe' : 'Unsafe'} FlutterWebManager inited',
+ () {
+ FlutterWebManager flutterWebManager;
+
+ setUpAll(() async {
+ flutterWebManager = FlutterWebManager();
+ });
+
+ test('packagesFilePath', () async {
+ final packageConfig = File(path.join(
+ FlutterWebManager.flutterTemplateProject(nullSafety).path,
+ '.dart_tool',
+ 'package_config.json'));
+ expect(await packageConfig.exists(), true);
+ final contents = jsonDecode(await packageConfig.readAsString());
+ expect(contents['packages'], isNotEmpty);
+ expect(
+ (contents['packages'] as List)
+ .where((element) => element['name'] == 'flutter'),
+ isNotEmpty);
+ });
+
+ test('summaryFilePath', () {
+ final summaryFilePath = flutterWebManager.summaryFilePath(nullSafety);
+ expect(summaryFilePath, isNotEmpty);
+
+ final file = File(summaryFilePath);
+ expect(file.existsSync(), isTrue);
+ });
});
-
- test('usesFlutterWeb', () {
- expect(flutterWebManager.usesFlutterWeb({''}), isFalse);
- expect(flutterWebManager.usesFlutterWeb({'dart:html'}), isFalse);
- expect(flutterWebManager.usesFlutterWeb({'dart:ui'}), isTrue);
- expect(flutterWebManager.usesFlutterWeb({'package:flutter'}), isTrue);
- expect(flutterWebManager.usesFlutterWeb({'package:flutter/'}), isTrue);
- });
-
- test('getUnsupportedImport', () {
- expect(flutterWebManager.getUnsupportedImport({'dart:html'}), isNull);
- expect(flutterWebManager.getUnsupportedImport({'dart:ui'}), isNull);
- expect(
- flutterWebManager.getUnsupportedImport({'package:flutter/'}), isNull);
- expect(flutterWebManager.getUnsupportedImport({'package:path'}),
- equals('package:path'));
- expect(flutterWebManager.getUnsupportedImport({'foo.dart'}),
- equals('foo.dart'));
- // dart:io is an unsupported package
- expect(flutterWebManager.getUnsupportedImport({'dart:io'}),
- equals('dart:io'));
- });
- });
-
- group('FlutterWebManager inited', () {
- FlutterWebManager flutterWebManager;
-
- setUpAll(() async {
- flutterWebManager = FlutterWebManager();
- });
-
- test('packagesFilePath', () async {
- final packageConfig = File(path.join(
- FlutterWebManager.flutterTemplateProject.path,
- '.dart_tool',
- 'package_config.json'));
- expect(await packageConfig.exists(), true);
- final contents = jsonDecode(await packageConfig.readAsString());
- expect(contents['packages'], isNotEmpty);
- expect(
- (contents['packages'] as List)
- .where((element) => element['name'] == 'flutter'),
- isNotEmpty);
- });
-
- test('summaryFilePath', () {
- final summaryFilePath = flutterWebManager.summaryFilePath;
- expect(summaryFilePath, isNotEmpty);
-
- final file = File(summaryFilePath);
- expect(file.existsSync(), isTrue);
- });
- });
+ }
}
diff --git a/tool/fuzz_driver.dart b/tool/fuzz_driver.dart
index ba87400..4f0a7fa 100644
--- a/tool/fuzz_driver.dart
+++ b/tool/fuzz_driver.dart
@@ -123,17 +123,17 @@
container = MockContainer();
cache = MockCache();
- commonServerImpl = CommonServerImpl(container, cache);
+ commonServerImpl = CommonServerImpl(container, cache, false);
await commonServerImpl.init();
- analysisServer = analysis_server.DartAnalysisServerWrapper();
+ analysisServer = analysis_server.DartAnalysisServerWrapper(false);
await analysisServer.init();
print('Warming up analysis server');
await analysisServer.warmup();
print('Warming up compiler');
- compiler = comp.Compiler(Sdk());
+ compiler = comp.Compiler(Sdk(), false);
await compiler.warmup();
print('SetupTools done');
}
diff --git a/tool/grind.dart b/tool/grind.dart
index d662c31..4162143 100644
--- a/tool/grind.dart
+++ b/tool/grind.dart
@@ -45,6 +45,13 @@
@Task()
@Depends(buildStorageArtifacts)
+Future<void> serveNullSafety() async {
+ await runWithLogging(Platform.executable,
+ arguments: ['bin/server_dev.dart', '--port', '8084', '--null-safety']);
+}
+
+@Task()
+@Depends(buildStorageArtifacts)
Future<void> serveWithProxyTarget() async {
await runWithLogging(Platform.executable, arguments: [
'bin/server_dev.dart',
@@ -85,10 +92,14 @@
void validateStorageArtifacts() async {
final version = Sdk().versionFull;
- const urlBase = 'https://storage.googleapis.com/compilation_artifacts/';
+ const nullUnsafeUrlBase =
+ 'https://storage.googleapis.com/compilation_artifacts/';
+ const nullSafeUrlBase = 'https://storage.googleapis.com/nnbd_artifacts/';
- for (final artifact in compilationArtifacts) {
- await _validateExists('$urlBase$version/$artifact');
+ for (final urlBase in [nullUnsafeUrlBase, nullSafeUrlBase]) {
+ for (final artifact in compilationArtifacts) {
+ await _validateExists('$urlBase$version/$artifact');
+ }
}
}
@@ -114,27 +125,29 @@
await templatesPath.delete(recursive: true);
}
- final dartProjectPath =
- Directory(path.join(templatesPath.path, 'dart_project'));
- final dartProjectDir = await dartProjectPath.create(recursive: true);
- joinFile(dartProjectDir, ['pubspec.yaml'])
- .writeAsStringSync(createPubspec(includeFlutterWeb: false));
- await _runDartPubGet(dartProjectDir);
- joinFile(dartProjectDir, ['analysis_options.yaml'])
- .writeAsStringSync(createDartAnalysisOptions());
+ for (final nullSafety in [true, false]) {
+ final dartProjectPath = Directory(path.join(templatesPath.path,
+ nullSafety ? 'null-safe' : 'null-unsafe', 'dart_project'));
+ final dartProjectDir = await dartProjectPath.create(recursive: true);
+ joinFile(dartProjectDir, ['pubspec.yaml']).writeAsStringSync(
+ createPubspec(includeFlutterWeb: false, nullSafety: nullSafety));
+ await _runDartPubGet(dartProjectDir);
+ joinFile(dartProjectDir, ['analysis_options.yaml'])
+ .writeAsStringSync(createDartAnalysisOptions());
- final flutterProjectPath =
- Directory(path.join(templatesPath.path, 'flutter_project'));
- final flutterProjectDir = await flutterProjectPath.create(recursive: true);
- joinFile(flutterProjectDir, ['pubspec.yaml'])
- .writeAsStringSync(createPubspec(includeFlutterWeb: true));
- await _runFlutterPubGet(flutterProjectDir);
- // TODO(gspencergoog): Convert this to use the flutter recommended lints as
- // soon as those are finalized (the current proposal is to leave the
- // analysis_options_user.yaml file as-is and replace it with a package, to
- // avoid massive breakage).
- joinFile(flutterProjectDir, ['analysis_options.yaml']).writeAsStringSync(
- 'include: package:flutter/analysis_options_user.yaml\n');
+ final flutterProjectPath = Directory(path.join(templatesPath.path,
+ nullSafety ? 'null-safe' : 'null-unsafe', 'flutter_project'));
+ final flutterProjectDir = await flutterProjectPath.create(recursive: true);
+ joinFile(flutterProjectDir, ['pubspec.yaml']).writeAsStringSync(
+ createPubspec(includeFlutterWeb: true, nullSafety: nullSafety));
+ await _runFlutterPubGet(flutterProjectDir);
+ // TODO(gspencergoog): Convert this to use the flutter recommended lints as
+ // soon as those are finalized (the current proposal is to leave the
+ // analysis_options_user.yaml file as-is and replace it with a package, to
+ // avoid massive breakage).
+ joinFile(flutterProjectDir, ['analysis_options.yaml']).writeAsStringSync(
+ 'include: package:flutter/analysis_options_user.yaml\n');
+ }
}
Future<void> _runDartPubGet(Directory dir) async {
@@ -160,18 +173,28 @@
@Task('build the sdk compilation artifacts for upload to google storage')
@Depends(sdkInit, buildProjectTemplates)
void buildStorageArtifacts() async {
- // build and copy dart_sdk.js, flutter_web.js, and flutter_web.dill
- final temp = Directory.systemTemp.createTempSync('flutter_web_sample');
+ delete(getDir('artifacts'));
+ final instructions = <String>[];
- try {
- await _buildStorageArtifacts(temp);
- } finally {
- temp.deleteSync(recursive: true);
+ for (final nullSafe in [false, true]) {
+ // build and copy dart_sdk.js, flutter_web.js, and flutter_web.dill
+ final temp = Directory.systemTemp.createTempSync('flutter_web_sample');
+
+ try {
+ instructions.add(await _buildStorageArtifacts(temp, nullSafe));
+ } finally {
+ temp.deleteSync(recursive: true);
+ }
+ }
+ log('\nFrom the dart-services project root dir, run:');
+ for (final instruction in instructions) {
+ log(instruction);
}
}
-Future<void> _buildStorageArtifacts(Directory dir) async {
- final pubspec = createPubspec(includeFlutterWeb: true);
+Future<String> _buildStorageArtifacts(Directory dir, bool nullSafety) async {
+ final pubspec =
+ createPubspec(includeFlutterWeb: true, nullSafety: nullSafety);
joinFile(dir, ['pubspec.yaml']).writeAsStringSync(pubspec);
// run flutter pub get
@@ -222,12 +245,23 @@
// --modules=amd package:flutter/animation.dart ...
final compilerPath = path.join(
Sdk.flutterSdkPath, 'bin', 'cache', 'dart-sdk', 'bin', 'dartdevc');
- final dillPath = path.join(Sdk.flutterSdkPath, 'bin', 'cache',
- 'flutter_web_sdk', 'flutter_web_sdk', 'kernel', 'flutter_ddc_sdk.dill');
+ final dillPath = path.join(
+ Sdk.flutterSdkPath,
+ 'bin',
+ 'cache',
+ 'flutter_web_sdk',
+ 'flutter_web_sdk',
+ 'kernel',
+ nullSafety ? 'flutter_ddc_sdk_sound.dill' : 'flutter_ddc_sdk.dill',
+ );
final args = <String>[
'-s',
dillPath,
+ if (nullSafety) ...[
+ '--sound-null-safety',
+ '--enable-experiment=non-nullable'
+ ],
'--modules=amd',
'-o',
'flutter_web.js',
@@ -241,12 +275,15 @@
);
// Copy both to the project directory.
- final artifactsDir = getDir('artifacts');
- delete(artifactsDir);
- artifactsDir.createSync();
+ final artifactsDir =
+ getDir(path.join('artifacts', nullSafety ? 'null-safe' : 'null-unsafe'));
+ artifactsDir.createSync(recursive: true);
- final sdkJsPath = path.join(Sdk.flutterSdkPath,
- 'bin/cache/flutter_web_sdk/flutter_web_sdk/kernel/amd-canvaskit-html/dart_sdk.js');
+ final sdkJsPath = path.join(
+ Sdk.flutterSdkPath,
+ nullSafety
+ ? 'bin/cache/flutter_web_sdk/flutter_web_sdk/kernel/amd-canvaskit-html-sound/dart_sdk.js'
+ : 'bin/cache/flutter_web_sdk/flutter_web_sdk/kernel/amd-canvaskit-html/dart_sdk.js');
copy(getFile(sdkJsPath), artifactsDir);
copy(joinFile(dir, ['flutter_web.js']), artifactsDir);
@@ -254,9 +291,8 @@
// Emit some good google storage upload instructions.
final version = Sdk().versionFull;
- log('\nFrom the dart-services project root dir, run:');
- log(' gsutil -h "Cache-Control:public, max-age=86400" cp -z js '
- 'artifacts/*.js gs://compilation_artifacts/$version/');
+ return (' gsutil -h "Cache-Control:public, max-age=86400" cp -z js ${artifactsDir.path}/*.js'
+ ' gs://${nullSafety ? 'nnbd_artifacts' : 'compilation_artifacts'}/$version/');
}
@Task('Reinitialize the Flutter submodule.')
@@ -371,12 +407,15 @@
const String _samplePackageName = 'dartpad_sample';
-String createPubspec({@required bool includeFlutterWeb}) {
+String createPubspec({
+ @required bool includeFlutterWeb,
+ @required bool nullSafety,
+}) {
// Mark the samples as not null safe.
var content = '''
name: $_samplePackageName
environment:
- sdk: '>=2.10.0 <3.0.0'
+ sdk: '>=${nullSafety ? '2.12.0' : '2.10.0'} <3.0.0'
''';
if (includeFlutterWeb) {