Add a hint when Future is imported from core in code targeted to run with an SDK prior to 2.1

Change-Id: I925412b9c7c3023836a0a2a7d3d00847adf0b0c7
Reviewed-on: https://dart-review.googlesource.com/c/84547
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Brian Wilkerson 2018-11-16 19:07:27 +00:00 committed by commit-bot@chromium.org
parent b27d6078c2
commit 716c9dcde2
11 changed files with 438 additions and 5 deletions

View file

@ -320,6 +320,7 @@ const List<ErrorCode> errorCodeValues = const [
HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD,
HintCode.OVERRIDE_ON_NON_OVERRIDING_SETTER,
HintCode.PACKAGE_IMPORT_CONTAINS_DOT_DOT,
HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE,
HintCode.TYPE_CHECK_IS_NOT_NULL,
HintCode.TYPE_CHECK_IS_NULL,
HintCode.UNDEFINED_GETTER,

View file

@ -8,6 +8,7 @@ import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
@ -25,41 +26,54 @@ import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error_verifier.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/hint/sdk_constraint_extractor.dart';
import 'package:analyzer/src/hint/sdk_constraint_verifier.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/linter_visitor.dart';
import 'package:analyzer/src/services/lint.dart';
import 'package:analyzer/src/task/dart.dart';
import 'package:analyzer/src/task/strong/checker.dart';
import 'package:pub_semver/pub_semver.dart';
/**
* Analyzer of a single library.
*/
class LibraryAnalyzer {
/// A marker object used to prevent the initialization of
/// [_versionConstraintFromPubspec] when the previous initialization attempt
/// failed.
static final VersionRange noSpecifiedRange = new VersionRange();
final AnalysisOptionsImpl _analysisOptions;
final DeclaredVariables _declaredVariables;
final SourceFactory _sourceFactory;
final FileState _library;
final InheritanceManager2 _inheritance;
final InheritanceManager2 _inheritance;
final bool Function(Uri) _isLibraryUri;
final AnalysisContext _context;
final ElementResynthesizer _resynthesizer;
final TypeProvider _typeProvider;
final TypeSystem _typeSystem;
LibraryElement _libraryElement;
LibraryScope _libraryScope;
final Map<FileState, LineInfo> _fileToLineInfo = {};
final Map<FileState, IgnoreInfo> _fileToIgnoreInfo = {};
final Map<FileState, IgnoreInfo> _fileToIgnoreInfo = {};
final Map<FileState, RecordingErrorListener> _errorListeners = {};
final Map<FileState, ErrorReporter> _errorReporters = {};
final List<UsedImportedElements> _usedImportedElementsList = [];
final List<UsedLocalElements> _usedLocalElementsList = [];
final Map<FileState, List<PendingError>> _fileToPendingErrors = {};
final Set<ConstantEvaluationTarget> _constants = new Set();
/// The cached version range for the SDK specified in `pubspec.yaml`, or
/// [noSpecifiedRange] if there is no `pubspec.yaml` or if it does not contain
/// an SDK range. Use [versionConstraintFromPubspec] to access this field.
VersionConstraint _versionConstraintFromPubspec;
LibraryAnalyzer(
this._analysisOptions,
this._declaredVariables,
@ -159,6 +173,20 @@ class LibraryAnalyzer {
return results;
}
VersionConstraint versionConstraintFromPubspec() {
if (_versionConstraintFromPubspec == null) {
_versionConstraintFromPubspec = noSpecifiedRange;
File pubspecFile = _findPubspecFile(_library);
if (pubspecFile != null) {
SdkConstraintExtractor extractor =
new SdkConstraintExtractor(pubspecFile);
_versionConstraintFromPubspec =
extractor.constraint() ?? noSpecifiedRange;
}
}
return _versionConstraintFromPubspec;
}
void _computeConstantErrors(
ErrorReporter errorReporter, CompilationUnit unit) {
ConstantVerifier constantVerifier = new ConstantVerifier(
@ -233,6 +261,16 @@ class LibraryAnalyzer {
new UnusedLocalElementsVerifier(errorListener, usedElements);
unit.accept(visitor);
}
//
// Find code that uses features from an SDK that is newer than the minimum
// version allowed in the pubspec.yaml file.
//
VersionRange versionRange = versionConstraintFromPubspec();
if (versionRange != noSpecifiedRange) {
SdkConstraintVerifier verifier = new SdkConstraintVerifier(
errorReporter, _libraryElement, _typeProvider, versionRange);
unit.accept(verifier);
}
}
void _computeLints(FileState file, LinterContextUnit currentUnit,
@ -364,6 +402,20 @@ class LibraryAnalyzer {
_constants.addAll(dependenciesFinder.dependencies);
}
File _findPubspecFile(FileState file) {
ResourceProvider resourceProvider =
_libraryElement.session?.resourceProvider;
Folder folder = resourceProvider?.getFile(file.path)?.parent;
while (folder != null) {
File pubspecFile = folder.getChildAssumingFile('pubspec.yaml');
if (pubspecFile.exists) {
return pubspecFile;
}
folder = folder.parent;
}
return null;
}
RecordingErrorListener _getErrorListener(FileState file) =>
_errorListeners.putIfAbsent(file, () => new RecordingErrorListener());

View file

@ -519,6 +519,18 @@ class HintCode extends ErrorCode {
'PACKAGE_IMPORT_CONTAINS_DOT_DOT',
"A package import shouldn't contain '..'.");
/**
* A class defined in `dart:async` that was not exported from `dart:core`
* before version 2.1 is being referenced via `dart:core` in code that is
* expected to run on earlier versions.
*/
static const HintCode SDK_VERSION_ASYNC_EXPORTED_FROM_CORE = const HintCode(
'SDK_VERSION_ASYNC_EXPORTED_FROM_CORE',
"The class '{0}' was not exported from 'dart:core' until version 2.1, "
"but this code is required to be able to run on earlier versions.",
correction:
"Try either importing 'dart:async' or updating the SDK constraints.");
/**
* Type checks of the type `x is! Null` should be done with `x != null`.
*/

View file

@ -0,0 +1,55 @@
// Copyright (c) 2018, 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:analyzer/file_system/file_system.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart';
/// A utility class used to extract the SDK version constraint from a
/// `pubspec.yaml` file.
class SdkConstraintExtractor {
/// The file from which the constraint is to be extracted.
final File pubspecFile;
/// The version range that was
VersionConstraint _constraint;
/// Initialize a newly created extractor to extract the SDK version constraint
/// from the given `pubspec.yaml` file.
SdkConstraintExtractor(this.pubspecFile);
/// Return the range of supported versions.
VersionConstraint constraint() {
if (_constraint == null) {
try {
String constraintText = _getConstraintText();
if (constraintText != null) {
_constraint = new VersionConstraint.parse(constraintText);
}
} catch (e) {
// Return `null` by falling through without setting `_versionRange`.
}
}
return _constraint;
}
/// Return the constraint text following "sdk:".
String _getConstraintText() {
String fileContent = pubspecFile.readAsStringSync();
YamlDocument document = loadYamlDocument(fileContent);
YamlNode contents = document.contents;
if (contents is YamlMap) {
var environment = contents['environment'];
if (environment is YamlMap) {
var sdk = environment['sdk'];
if (sdk is String) {
return sdk;
} else if (sdk is YamlScalar) {
return sdk.toString();
}
}
}
return null;
}
}

View file

@ -0,0 +1,68 @@
// Copyright (c) 2018, 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:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:pub_semver/pub_semver.dart';
/// A visitor that finds code that assumes a later version of the SDK than the
/// minimum version required by the SDK constraints in `pubspec.yaml`.
class SdkConstraintVerifier extends RecursiveAstVisitor<void> {
/// The error reporter to be used to report errors.
final ErrorReporter _errorReporter;
/// The element representing the library containing the unit to be verified.
final LibraryElement _containingLibrary;
/// The typ provider used to access SDK types.
final TypeProvider _typeProvider;
/// The range of versions of the SDK that are allowed by the SDK constraints.
final VersionRange _versionRange;
/// A cached flag indicating whether references to Future and Stream need to
/// be checked. Use [] to access this field.
bool _checkFutureAndStream;
/// Initialize a newly created verifier to use the given [_errorReporter] to
/// report errors.
SdkConstraintVerifier(this._errorReporter, this._containingLibrary,
this._typeProvider, this._versionRange);
/// Return a range covering every version up to, but not including, 2.1.0.
VersionRange get before_2_1_0 =>
new VersionRange(max: Version.parse('2.1.0'), includeMax: false);
/// Return `true` if references to Future and Stream need to be checked.
bool get checkFutureAndStream =>
_checkFutureAndStream ??= !before_2_1_0.intersect(_versionRange).isEmpty;
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.inDeclarationContext()) {
return;
}
Element element = node.staticElement;
if (checkFutureAndStream &&
(element == _typeProvider.futureType.element ||
element == _typeProvider.streamType.element)) {
for (LibraryElement importedLibrary
in _containingLibrary.importedLibraries) {
if (!importedLibrary.isDartCore) {
Namespace namespace = importedLibrary.exportNamespace;
if (namespace != null && namespace.get(element.name) != null) {
return;
}
}
}
_errorReporter.reportErrorForNode(
HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE, node, [element.name]);
}
}
}

View file

@ -0,0 +1,94 @@
// Copyright (c) 2018, 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:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/hint/sdk_constraint_extractor.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(SdkConstraintExtractorTest);
});
}
@reflectiveTest
class SdkConstraintExtractorTest with ResourceProviderMixin {
SdkConstraintExtractor extractorFor(String pubspecContent) {
String pubspecPath = '/pkg/test/pubspec.yaml';
File pubspecFile = newFile(pubspecPath, content: pubspecContent);
return new SdkConstraintExtractor(pubspecFile);
}
test_constraint_any() {
SdkConstraintExtractor extractor = extractorFor('''
environment:
sdk: any
''');
expect(extractor.constraint().toString(), 'any');
}
test_constraint_caret() {
SdkConstraintExtractor extractor = extractorFor('''
environment:
sdk: ^2.1.0
''');
expect(extractor.constraint().toString(), '^2.1.0');
}
test_constraint_compound() {
SdkConstraintExtractor extractor = extractorFor('''
environment:
sdk: '>=2.1.0 <3.0.0'
''');
expect(extractor.constraint().toString(), '>=2.1.0 <3.0.0');
}
test_constraint_gt() {
SdkConstraintExtractor extractor = extractorFor('''
environment:
sdk: '>2.1.0'
''');
expect(extractor.constraint().toString(), '>2.1.0');
}
test_constraint_gte() {
SdkConstraintExtractor extractor = extractorFor('''
environment:
sdk: '>=2.2.0-dev.3.0'
''');
expect(extractor.constraint().toString(), '>=2.2.0-dev.3.0');
}
test_constraint_invalid_badConstraint() {
SdkConstraintExtractor extractor = extractorFor('''
environment:
sdk: latest
''');
expect(extractor.constraint(), isNull);
}
test_constraint_invalid_noEnvironment() {
SdkConstraintExtractor extractor = extractorFor('''
name: test
''');
expect(extractor.constraint(), isNull);
}
test_constraint_invalid_noSdk() {
SdkConstraintExtractor extractor = extractorFor('''
environment:
os: 'Analytical Engine'
''');
expect(extractor.constraint(), isNull);
}
test_constraint_invalid_notYaml() {
SdkConstraintExtractor extractor = extractorFor('''
class C {}
''');
expect(extractor.constraint(), isNull);
}
}

View file

@ -0,0 +1,30 @@
// Copyright (c) 2018, 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:analyzer/error/error.dart';
import '../../generated/resolver_test_case.dart';
import '../../generated/test_support.dart';
/// A base class designed to be used by tests of the hints produced by an
/// SdkConstraintVerifier.
class SdkConstraintVerifierTest extends ResolverTestCase {
bool get enableNewAnalysisDriver => true;
verifyVersion(String version, String source,
{List<ErrorCode> errorCodes}) async {
newFile('/pubspec.yaml', content: '''
environment:
sdk: ^$version
''');
TestAnalysisResult result = await computeTestAnalysisResult(source);
GatheringErrorListener listener = new GatheringErrorListener();
listener.addAll(result.errors);
if (errorCodes == null) {
listener.assertNoErrors();
} else {
listener.assertErrorsWithCodes(errorCodes);
}
}
}

View file

@ -0,0 +1,105 @@
// Copyright (c) 2018, 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:analyzer/src/dart/error/hint_codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'sdk_constraint_verifier.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(SdkVersionAsyncExportedFromCoreTest);
});
}
@reflectiveTest
class SdkVersionAsyncExportedFromCoreTest extends SdkConstraintVerifierTest {
test_equals_explicitImportOfAsync() async {
await verifyVersion('2.1.0', '''
import 'dart:async';
Future<int> zero() async => 0;
''');
}
test_equals_explicitImportOfCore() async {
await verifyVersion('2.1.0', '''
import 'dart:core';
Future<int> zero() async => 0;
''');
}
test_equals_explicitImportOfExportingLibrary() async {
addNamedSource('/exporter.dart', '''
export 'dart:async';
''');
await verifyVersion('2.1.0', '''
import 'exporter.dart';
Future<int> zero() async => 0;
''');
}
test_equals_implicitImportOfCore() async {
await verifyVersion('2.1.0', '''
Future<int> zero() async => 0;
''');
}
test_equals_implicitImportOfCore_inPart() async {
newFile('/lib.dart', content: '''
library lib;
''');
await verifyVersion('2.1.0', '''
part of lib;
Future<int> zero() async => 0;
''');
}
test_lessThan_explicitImportOfAsync() async {
await verifyVersion('2.0.0', '''
import 'dart:async';
Future<int> zero() async => 0;
''');
}
test_lessThan_explicitImportOfCore() async {
await verifyVersion('2.0.0', '''
import 'dart:core';
Future<int> zero() async => 0;
''', errorCodes: [HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE]);
}
test_lessThan_explicitImportOfExportingLibrary() async {
addNamedSource('/exporter.dart', '''
export 'dart:async';
''');
await verifyVersion('2.0.0', '''
import 'exporter.dart';
Future<int> zero() async => 0;
''');
}
test_lessThan_implicitImportOfCore() async {
await verifyVersion('2.0.0', '''
Future<int> zero() async => 0;
''', errorCodes: [HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE]);
}
test_lessThan_implicitImportOfCore_inPart() async {
newFile('/lib.dart', content: '''
library lib;
''');
await verifyVersion('2.0.0', '''
part of lib;
Future<int> zero() async => 0;
''', errorCodes: [HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE]);
}
}

View file

@ -0,0 +1,14 @@
// Copyright (c) 2018, 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:test_reflective_loader/test_reflective_loader.dart';
import 'sdk_version_async_exported_from_core_test.dart'
as sdk_version_async_exported_from_core;
main() {
defineReflectiveSuite(() {
sdk_version_async_exported_from_core.main();
}, name: 'hint');
}

View file

@ -8,6 +8,7 @@ import 'command_line/test_all.dart' as command_line;
import 'context/test_all.dart' as context;
import 'dart/test_all.dart' as dart;
import 'fasta/test_all.dart' as fasta;
import 'hint/test_all.dart' as hint;
import 'lint/test_all.dart' as lint;
import 'pubspec/test_all.dart' as pubspec;
import 'source/test_all.dart' as source;
@ -22,6 +23,7 @@ main() {
context.main();
dart.main();
fasta.main();
hint.main();
lint.main();
pubspec.main();
source.main();

View file

@ -6,7 +6,7 @@ author: Dart Team <misc@dartlang.org>
description: Front end for compilation of Dart code.
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/front_end
environment:
sdk: '>=2.0.0-dev.48.0 <3.0.0'
sdk: '>=2.1.0 <3.0.0'
dependencies:
charcode: '^1.1.1'
convert: '^2.0.1'