Unverified Commit cdf5b1d4 authored by Jesús S Guerrero's avatar Jesús S Guerrero Committed by GitHub

Pub dependencies project validator (#106895)

parent da8070d6
......@@ -142,6 +142,7 @@ List<FlutterCommand> generateCommands({
logger: globals.logger,
terminal: globals.terminal,
artifacts: globals.artifacts!,
// new ProjectValidators should be added here for the --suggestions to run
allProjectValidators: <ProjectValidator>[],
),
AssembleCommand(verboseHelp: verboseHelp, buildSystem: globals.buildSystem),
......
......@@ -138,10 +138,14 @@ class AnalyzeCommand extends FlutterCommand {
}
if (workingDirectory == null) {
final Set<String> items = findDirectories(argResults!, _fileSystem);
if (items.isEmpty || items.length > 1) {
throwToolExit('The suggestions flags needs one directory path');
if (items.isEmpty) { // user did not specify any path
directoryPath = _fileSystem.currentDirectory.path;
_logger.printTrace('Showing suggestions for current directory: $directoryPath');
} else if (items.length > 1) { // if the user sends more than one path
throwToolExit('The suggestions flag can process only one directory path');
} else {
directoryPath = items.first;
}
directoryPath = items.first;
} else {
directoryPath = workingDirectory!.path;
}
......@@ -150,6 +154,7 @@ class AnalyzeCommand extends FlutterCommand {
logger: _logger,
allProjectValidators: _allProjectValidators,
userPath: directoryPath,
processManager: _processManager,
).run();
} else if (boolArgDeprecated('watch')) {
await AnalyzeContinuously(
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:process/process.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../project.dart';
......@@ -15,7 +17,8 @@ class ValidateProject {
required this.logger,
required this.allProjectValidators,
required this.userPath,
this.verbose = false
required this.processManager,
this.verbose = false,
});
final FileSystem fileSystem;
......@@ -23,6 +26,7 @@ class ValidateProject {
final bool verbose;
final String userPath;
final List<ProjectValidator> allProjectValidators;
final ProcessManager processManager;
Future<FlutterCommandResult> run() async {
final Directory workingDirectory = userPath.isEmpty ? fileSystem.currentDirectory : fileSystem.directory(userPath);
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
/// Parsing the output of "dart pub deps --json"
///
/// expected structure: {"name": "package name", "source": "hosted", "dependencies": [...]}
class DartDependencyPackage {
DartDependencyPackage({
required this.name,
required this.version,
required this.source,
required this.dependencies,
});
factory DartDependencyPackage.fromHashMap(dynamic packageInfo) {
String name = '';
String version = '';
String source = '';
List<dynamic> dependencies = <dynamic>[];
if (packageInfo is LinkedHashMap) {
final LinkedHashMap<String, dynamic> info = packageInfo as LinkedHashMap<String, dynamic>;
if (info.containsKey('name')) {
name = info['name'] as String;
}
if (info.containsKey('version')) {
version = info['version'] as String;
}
if (info.containsKey('source')) {
source = info['source'] as String;
}
if (info.containsKey('dependencies')) {
dependencies = info['dependencies'] as List<dynamic>;
}
}
return DartDependencyPackage(
name: name,
version: version,
source: source,
dependencies: dependencies.map((dynamic e) => e.toString()).toList(),
);
}
final String name;
final String version;
final String source;
final List<String> dependencies;
}
class DartPubJson {
DartPubJson(this._json);
final LinkedHashMap<String, dynamic> _json;
final List<DartDependencyPackage> _packages = <DartDependencyPackage>[];
List<DartDependencyPackage> get packages {
if (_packages.isNotEmpty) {
return _packages;
}
if (_json.containsKey('packages')) {
final List<dynamic> packagesInfo = _json['packages'] as List<dynamic>;
for (final dynamic info in packagesInfo) {
_packages.add(DartDependencyPackage.fromHashMap(info));
}
}
return _packages;
}
}
......@@ -2,19 +2,23 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
import 'package:process/process.dart';
import 'base/io.dart';
import 'convert.dart';
import 'dart_pub_json_formatter.dart';
import 'flutter_manifest.dart';
import 'project.dart';
import 'project_validator_result.dart';
abstract class ProjectValidator {
const ProjectValidator();
String get title;
bool supportsProject(FlutterProject project);
/// Can return more than one result in case a file/command have a lot of info to share to the user
Future<List<ProjectValidatorResult>> start(FlutterProject project);
/// new ProjectValidators should be added here for the ValidateProjectCommand to run
static List <ProjectValidator> allProjectValidators = <ProjectValidator>[
GeneralInfoProjectValidator(),
];
}
/// Validator run for all platforms that extract information from the pubspec.yaml.
......@@ -109,3 +113,77 @@ class GeneralInfoProjectValidator extends ProjectValidator{
@override
String get title => 'General Info';
}
class PubDependenciesProjectValidator extends ProjectValidator {
const PubDependenciesProjectValidator(this._processManager);
final ProcessManager _processManager;
@override
Future<List<ProjectValidatorResult>> start(FlutterProject project) async {
const String name = 'Dart dependencies';
final ProcessResult processResult = await _processManager.run(<String>['dart', 'pub', 'deps', '--json']);
if (processResult.stdout is! String) {
return <ProjectValidatorResult>[
_createProjectValidatorError(name, 'Command dart pub deps --json failed')
];
}
final LinkedHashMap<String, dynamic> jsonResult;
final List<ProjectValidatorResult> result = <ProjectValidatorResult>[];
try {
jsonResult = json.decode(
processResult.stdout.toString()
) as LinkedHashMap<String, dynamic>;
} on FormatException{
result.add(_createProjectValidatorError(name, processResult.stderr.toString()));
return result;
}
final DartPubJson dartPubJson = DartPubJson(jsonResult);
final List <String> dependencies = <String>[];
// Information retrieved from the pubspec.lock file if a dependency comes from
// the hosted url https://pub.dartlang.org we ignore it or if the package
// is the current directory being analyzed (root).
final Set<String> hostedDependencies = <String>{'hosted', 'root'};
for (final DartDependencyPackage package in dartPubJson.packages) {
if (!hostedDependencies.contains(package.source)) {
dependencies.addAll(package.dependencies);
}
}
if (dependencies.isNotEmpty) {
final String verb = dependencies.length == 1 ? 'is' : 'are';
result.add(
ProjectValidatorResult(
name: name,
value: '${dependencies.join(', ')} $verb not hosted',
status: StatusProjectValidator.warning,
)
);
} else {
result.add(
const ProjectValidatorResult(
name: name,
value: 'All pub dependencies are hosted on https://pub.dartlang.org',
status: StatusProjectValidator.success,
)
);
}
return result;
}
@override
bool supportsProject(FlutterProject project) {
return true;
}
@override
String get title => 'Pub dependencies';
ProjectValidatorResult _createProjectValidatorError(String name, String value) {
return ProjectValidatorResult(name: name, value: value, status: StatusProjectValidator.error);
}
}
// Copyright 2014 The Flutter Authors. 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:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/project_validator.dart';
import 'package:flutter_tools/src/project_validator_result.dart';
import '../src/common.dart';
import '../src/context.dart';
void main() {
late FileSystem fileSystem;
group('PubDependenciesProjectValidator', () {
setUp(() {
fileSystem = MemoryFileSystem.test();
});
testWithoutContext('success when all dependencies are hosted', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['dart', 'pub', 'deps', '--json'],
stdout: '{"packages": [{"dependencies": ["abc"], "source": "hosted"}]}',
),
]);
final PubDependenciesProjectValidator validator = PubDependenciesProjectValidator(processManager);
final List<ProjectValidatorResult> result = await validator.start(
FlutterProject.fromDirectoryTest(fileSystem.currentDirectory)
);
const String expected = 'All pub dependencies are hosted on https://pub.dartlang.org';
expect(result.length, 1);
expect(result[0].value, expected);
expect(result[0].status, StatusProjectValidator.success);
});
testWithoutContext('error when command dart pub deps fails', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['dart', 'pub', 'deps', '--json'],
stderr: 'command fail',
),
]);
final PubDependenciesProjectValidator validator = PubDependenciesProjectValidator(processManager);
final List<ProjectValidatorResult> result = await validator.start(
FlutterProject.fromDirectoryTest(fileSystem.currentDirectory)
);
const String expected = 'command fail';
expect(result.length, 1);
expect(result[0].value, expected);
expect(result[0].status, StatusProjectValidator.error);
});
testWithoutContext('warning on dependencies not hosted', () async {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['dart', 'pub', 'deps', '--json'],
stdout: '{"packages": [{"dependencies": ["dep1", "dep2"], "source": "other"}]}',
),
]);
final PubDependenciesProjectValidator validator = PubDependenciesProjectValidator(processManager);
final List<ProjectValidatorResult> result = await validator.start(
FlutterProject.fromDirectoryTest(fileSystem.currentDirectory)
);
const String expected = 'dep1, dep2 are not hosted';
expect(result.length, 1);
expect(result[0].value, expected);
expect(result[0].status, StatusProjectValidator.warning);
});
});
}
......@@ -54,5 +54,36 @@ void main() {
expect(loggerTest.statusText, contains(expected));
});
testUsingContext('PubDependenciesProjectValidator success ', () async {
final BufferLogger loggerTest = BufferLogger.test();
final AnalyzeCommand command = AnalyzeCommand(
artifacts: globals.artifacts!,
fileSystem: fileSystem,
logger: loggerTest,
platform: globals.platform,
terminal: globals.terminal,
processManager: globals.processManager,
allProjectValidators: <ProjectValidator>[
PubDependenciesProjectValidator(globals.processManager),
],
);
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>[
'analyze',
'--no-pub',
'--no-current-package',
'--suggestions',
'../../dev/integration_tests/flutter_gallery',
]);
const String expected = '\n'
'┌────────────────────────────────────────────────────────────────────────────────────┐\n'
'│ Pub dependencies │\n'
'│ [✓] Dart dependencies: All pub dependencies are hosted on https://pub.dartlang.org │\n'
'└────────────────────────────────────────────────────────────────────────────────────┘\n';
expect(loggerTest.statusText, contains(expected));
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment