Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add back check for invalid core library imports #2783

Merged
merged 4 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion pkgs/dart_services/lib/src/analysis.dart
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,24 @@ class AnalysisServerWrapper {
final importIssues = <api.AnalysisIssue>[];

for (final import in imports) {
// Ignore `dart:` core library imports.
if (import.dartImport) {
final libraryName = import.packageName;
if (!isSupportedCoreLibrary(libraryName)) {
importIssues.add(api.AnalysisIssue(
kind: 'error',
message: "Unsupported library on the web: 'dart:$libraryName'.",
correction: 'Try removing the import and usages of the library.',
location: import.getLocation(source),
));
} else if (isDeprecatedCoreWebLibrary(libraryName)) {
importIssues.add(api.AnalysisIssue(
kind: 'info', // TODO(parlough): Expand to 'warning' in future.
message: "Deprecated core web library: 'dart:$libraryName'.",
correction: 'Try using static JS interop instead.',
url: 'https://dart.dev/go/next-gen-js-interop',
location: import.getLocation(source),
));
}
continue;
}

Expand Down
1 change: 1 addition & 0 deletions pkgs/dart_services/lib/src/project_creator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ include: package:flutter_lints/flutter.yaml
linter:
rules:
avoid_print: false
avoid_web_libraries_in_flutter: false
use_key_in_widget_constructors: false
''';
if (_sdk.experiments.isNotEmpty) {
Expand Down
108 changes: 34 additions & 74 deletions pkgs/dart_services/lib/src/project_templates.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,27 +151,42 @@ const Set<String> _deprecatedPackages = {
'tuple',
};

/// A set of all allowed `dart:` imports. Currently includes non-VM libraries
/// listed as the [doc](https://api.dart.dev/stable/index.html) categories.
const Set<String> _allowedDartImports = {
'dart:async',
'dart:collection',
'dart:convert',
'dart:core',
'dart:developer',
'dart:math',
'dart:typed_data',
'dart:html',
'dart:indexed_db',
'dart:js',
'dart:js_util',
'dart:svg',
'dart:web_audio',
'dart:web_gl',
'dart:ui',
/// The set of core web libraries whose support in
/// DartPad or Dart is deprecated.
const Set<String> _deprecatedCoreWebLibraries = {
'js',
};

/// Returns whether [imports] denote use of Flutter Web.
/// A set of all allowed `dart:` libraries, includes
/// all libraries from "Core", some from "Web", and none from "VM".
const Set<String> _allowedCoreLibraries = {
'async',
'collection',
'convert',
'core',
'developer',
'math',
'typed_data',

'html', // TODO(parlough): Deprecate soon.
'js_util', // TODO(parlough): Deprecate soon.
'js_interop',
'js_interop_unsafe',
..._deprecatedCoreWebLibraries,

'ui',
};

/// Whether [libraryName] is the name of a supported `dart:` core library.
bool isSupportedCoreLibrary(String libraryName) =>
_allowedCoreLibraries.contains(libraryName);

/// Whether [libraryName] is the name of a supported, but deprecated,
/// `dart:` core web library.
bool isDeprecatedCoreWebLibrary(String libraryName) =>
_deprecatedCoreWebLibraries.contains(libraryName);

/// Whether [imports] denote use of Flutter Web.
bool usesFlutterWeb(Iterable<ImportDirective> imports) =>
imports.any((import) => isFlutterWebImport(import.uri.stringValue));

Expand Down Expand Up @@ -211,61 +226,6 @@ String? _packageNameFromPackageUri(String uriString) {
return uri.pathSegments.first;
}

/// Goes through imports list and returns list of unsupported imports.
/// Optional [sourceFiles] contains a list of the source filenames
/// which are all part of this overall sources file set (these are to
/// be allowed).
///
/// Note: The filenames in [sourceFiles] were sanitized of any
/// 'package:'/etc syntax as the file set arrives from the endpoint, and
/// before being passed to [getUnsupportedImports]. This is done so
/// the list can't be used to bypass unsupported imports.
List<ImportDirective> getUnsupportedImports(
List<ImportDirective> imports, {
Set<String>? sourceFiles,
}) {
return imports
.where((import) => isUnsupportedImport(import.uri.stringValue,
sourceFiles: sourceFiles ?? const {}))
.toList(growable: false);
}

/// Whether the [importString] represents an import
/// that is unsupported.
@visibleForTesting
bool isUnsupportedImport(
String? importString, {
Set<String> sourceFiles = const {},
}) {
if (importString == null || importString.isEmpty) {
return false;
}
// All non-VM 'dart:' imports are ok.
if (importString.startsWith('dart:')) {
return !_allowedDartImports.contains(importString);
}
// Filenames from within this compilation files={} sources file set
// are OK. (These filenames have been sanitized to prevent 'package:'
// (and other) prefixes, so the a filename cannot be used to bypass
// import restrictions (see comment above)).
if (sourceFiles.contains(importString)) {
return false;
}

final uri = Uri.tryParse(importString);
if (uri == null) return false;

// We allow a specific set of package imports.
if (uri.scheme == 'package') {
if (uri.pathSegments.isEmpty) return true;
final package = uri.pathSegments.first;
return !isSupportedPackage(package);
}

// Don't allow file imports.
return true;
}

bool isSupportedPackage(String package) =>
_packagesIndicatingFlutter.contains(package) ||
supportedBasicDartPackages.contains(package);
Expand Down
32 changes: 32 additions & 0 deletions pkgs/dart_services/test/analysis_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ void defineTests() {
}
});

test('Error on VM library imports', () async {
final results = await analysisServer.analyze(unsupportedCoreLibrary);

expect(results.issues, hasLength(1));
final issue = results.issues.first;
expect(issue.message, contains('Unsupported library on the web'));
});

test('Warn on deprecated web library imports', () async {
final results = await analysisServer.analyze(deprecatedWebLibrary);

expect(results.issues, hasLength(1));
final issue = results.issues.first;
expect(issue.message, contains('Deprecated core web library'));
});

test('import_dart_core_test', () async {
// Ensure we can import dart: imports.
final testCode = "import 'dart:c'; main() { int a = 0; a. }";
Expand Down Expand Up @@ -325,3 +341,19 @@ void main() async {
print(unknown);
}
''';

const unsupportedCoreLibrary = '''
import 'dart:io' as io;

void main() {
print(io.exitCode);
}
''';

const deprecatedWebLibrary = '''
import 'dart:js' as js;

void main() {
print(js.context);
}
''';
39 changes: 16 additions & 23 deletions pkgs/dart_services/test/flutter_web_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,43 +29,36 @@ void defineTests() {
expect(isFlutterWebImport('package:flutter/'), isTrue);
});

test('isUnsupportedImport allows current library import', () {
expect(isUnsupportedImport(''), isFalse);
test('isSupportedCoreLibrary allows dart:html', () {
expect(isSupportedCoreLibrary('html'), isTrue);
});

test('isUnsupportedImport allows dart:html', () {
expect(isUnsupportedImport('dart:html'), isFalse);
test('isSupportedCoreLibrary allows dart:ui', () {
expect(isSupportedCoreLibrary('ui'), isTrue);
});

test('isUnsupportedImport allows dart:ui', () {
expect(isUnsupportedImport('dart:ui'), isFalse);
test('isSupportedCoreLibrary does not allow VM-only imports', () {
expect(isSupportedCoreLibrary('io'), isFalse);
});

test('isUnsupportedImport allows package:flutter', () {
expect(isUnsupportedImport('package:flutter'), isFalse);
test('isSupportedCoreLibrary does not allow superseded web libraries', () {
expect(isSupportedCoreLibrary('web_gl'), isFalse);
});

test('isUnsupportedImport allows package:path', () {
expect(isUnsupportedImport('package:path'), isFalse);
test('isSupportedPackage allows package:flutter', () {
expect(isSupportedPackage('flutter'), isTrue);
});

test('isUnsupportedImport does not allow package:unsupported', () {
expect(isUnsupportedImport('package:unsupported'), isTrue);
test('isSupportedPackage allows package:path', () {
expect(isSupportedPackage('path'), isTrue);
});

test('isUnsupportedImport does not allow random local imports', () {
expect(isUnsupportedImport('foo.dart'), isTrue);
test('isSupportedPackage does not allow package:unsupported', () {
expect(isSupportedPackage('unsupported'), isFalse);
});

test('isUnsupportedImport allows specified local imports', () {
expect(
isUnsupportedImport('foo.dart', sourceFiles: {'foo.dart'}),
isFalse,
);
});

test('isUnsupportedImport does not allow VM-only imports', () {
expect(isUnsupportedImport('dart:io'), isTrue);
test('isSupportedPackage does not allow random local imports', () {
expect(isSupportedPackage('foo.dart'), isFalse);
});
});

Expand Down