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

Auto-fix for errors #87

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
21 changes: 19 additions & 2 deletions bin/dependency_validator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:io' show exit, stderr, stdout;
import 'dart:io' show Directory, exit, stderr, stdout;

import 'package:args/args.dart';
import 'package:dependency_validator/src/dependency_validator.dart';
import 'package:io/io.dart';
import 'package:logging/logging.dart';

const String pathArg = 'path';
const String autoFixArg = 'auto-fix';
const String helpArg = 'help';
const String verboseArg = 'verbose';
const String helpMessage =
Expand All @@ -36,6 +38,16 @@ usage:''';

/// Parses the command-line arguments
final ArgParser argParser = ArgParser()
..addOption(
pathArg,
abbr: 'p',
help: 'Specify package path',
)
..addFlag(
autoFixArg,
abbr: 'a',
help: 'Auto fix issues',
)
..addFlag(
helpArg,
abbr: 'h',
Expand Down Expand Up @@ -78,5 +90,10 @@ void main(List<String> args) async {
Logger.root.level = Level.ALL;
}

await run();
final path = argResults[pathArg] as String?;
if (path != null) {
Directory.current = path;
}

await run(shouldAutoFix: argResults[autoFixArg]);
}
130 changes: 130 additions & 0 deletions lib/src/auto_fix.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import 'package:dependency_validator/src/utils.dart';
import 'package:pubspec_parse/pubspec_parse.dart';

class AutoFix {
final Pubspec pubspec;

final _pubRemoveNames = <String>[];
final _pubAdds = <PubAddCommand>[];

AutoFix(this.pubspec);

void handleMissingDependencies(Set<String> deps) {
_pubAdds.add(PubAddCommand(packageAndConstraints: deps.toList(), dev: false));
}

void handleMissingDevDependencies(Set<String> deps) {
_pubAdds.add(PubAddCommand(packageAndConstraints: deps.toList(), dev: true));
}

void handleOverPromotedDependencies(Set<String> deps) {
_pubRemoveNames.addAll(deps);
_pubAdds.addAll(_parsePubAddListByPubspec(deps, dev: true));
}

void handleUnderPromotedDependencies(Set<String> deps) {
_pubRemoveNames.addAll(deps);
_pubAdds.addAll(_parsePubAddListByPubspec(deps, dev: false));
}

void handleUnusedDependencies(Set<String> deps) {
_pubRemoveNames.addAll(deps);
}

List<PubAddCommand> _parsePubAddListByPubspec(Set<String> deps, {required bool dev}) {
return deps.map((dep) => _parsePubAddByPubspec(dep, dev: dev)).where((e) => e != null).map((e) => e!).toList();
}

PubAddCommand? _parsePubAddByPubspec(String name, {required bool dev}) {
final dependency = pubspec.dependencies[name] ?? pubspec.devDependencies[name];
if (dependency == null) {
logger.warning('WARN: cannot find dependency name=$name');
return null;
}

if (dependency is HostedDependency) {
final constraint = dependency.version.toString();
return PubAddCommand(packageAndConstraints: ['$name:$constraint'], dev: dev);
}

if (dependency is PathDependency) {
return PubAddCommand(
packageAndConstraints: [name],
dev: dev,
extraArgs: '--path ${dependency.path}',
);
}

if (dependency is GitDependency) {
var extraArgs = '--git-url ${dependency.url} ';
if (dependency.ref != null) extraArgs += '--git-ref ${dependency.ref} ';
if (dependency.path != null) extraArgs += '--git-path ${dependency.path} ';

return PubAddCommand(
packageAndConstraints: [name],
dev: dev,
extraArgs: extraArgs,
);
}

logger.warning('WARN: do not know type of dependency '
'name=$name dependency=$dependency type=${dependency.runtimeType}');
return null;
}

String compile() {
// final mergedPubAdds = PubAddCommand.merge(_pubAdds);
return [
if (_pubRemoveNames.isNotEmpty) 'dart pub remove ' + _pubRemoveNames.join(' '),
..._pubAdds.map((e) => e.compile()),
].join('; ');
}
}

class PubAddCommand {
final bool dev;
final List<String> packageAndConstraints;
final String? extraArgs;

PubAddCommand({
required this.packageAndConstraints,
required this.dev,
this.extraArgs,
});

String compile() {
var ans = 'dart pub add ';
if (dev) ans += '--dev ';
ans += packageAndConstraints.join(' ') + ' ';
ans += extraArgs ?? '';
return ans;
}

// static List<PubAddCommand> merge(List<PubAddCommand> commands) {
// final simpleAdd = <PubAddCommand>[];
// final simpleAddDev = <PubAddCommand>[];
// final others = <PubAddCommand>[];
//
// for (final command in commands) {
// if (command.extraArgs == null) {
// (command.dev ? simpleAddDev : simpleAdd).add(command);
// } else {
// others.add(command);
// }
// }
//
// return [
// if (simpleAdd.isNotEmpty)
// PubAddCommand(
// packageAndConstraints: simpleAdd.expand((c) => c.packageAndConstraints).toList(),
// dev: false,
// ),
// if (simpleAddDev.isNotEmpty)
// PubAddCommand(
// packageAndConstraints: simpleAddDev.expand((c) => c.packageAndConstraints).toList(),
// dev: true,
// ),
// ...others,
// ];
// }
}
27 changes: 26 additions & 1 deletion lib/src/dependency_validator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import 'dart:io';

import 'package:build_config/build_config.dart';
import 'package:dependency_validator/src/auto_fix.dart';
import 'package:glob/glob.dart';
import 'package:io/ansi.dart';
import 'package:logging/logging.dart';
Expand All @@ -27,7 +28,7 @@ import 'pubspec_config.dart';
import 'utils.dart';

/// Check for missing, under-promoted, over-promoted, and unused dependencies.
Future<void> run() async {
Future<void> run({required bool shouldAutoFix}) async {
if (!File('pubspec.yaml').existsSync()) {
logger.shout(red.wrap('pubspec.yaml not found'));
exit(1);
Expand Down Expand Up @@ -76,6 +77,8 @@ Future<void> run() async {
final pubspec =
Pubspec.parse(pubspecFile.readAsStringSync(), sourceUrl: pubspecFile.uri);

final autoFix = AutoFix(pubspec);

logger.info('Validating dependencies for ${pubspec.name}...');

if (!config.allowPins) {
Expand Down Expand Up @@ -200,6 +203,7 @@ Future<void> run() async {
'These packages are used in lib/ but are not dependencies:',
missingDependencies,
);
autoFix.handleMissingDependencies(missingDependencies);
exitCode = 1;
}

Expand All @@ -222,6 +226,7 @@ Future<void> run() async {
'These packages are used outside lib/ but are not dev_dependencies:',
missingDevDependencies,
);
autoFix.handleMissingDevDependencies(missingDevDependencies);
exitCode = 1;
}

Expand All @@ -242,6 +247,7 @@ Future<void> run() async {
'These packages are only used outside lib/ and should be downgraded to dev_dependencies:',
overPromotedDependencies,
);
autoFix.handleOverPromotedDependencies(overPromotedDependencies);
exitCode = 1;
}

Expand All @@ -258,6 +264,7 @@ Future<void> run() async {
'These packages are used in lib/ and should be promoted to actual dependencies:',
underPromotedDependencies,
);
autoFix.handleUnderPromotedDependencies(underPromotedDependencies);
exitCode = 1;
}

Expand Down Expand Up @@ -343,9 +350,27 @@ Future<void> run() async {
'These packages may be unused, or you may be using assets from these packages:',
unusedDependencies,
);
autoFix.handleUnusedDependencies(unusedDependencies);
exitCode = 1;
}

final autoFixCommand = autoFix.compile();
if (autoFixCommand.isNotEmpty) {
logger.info('Suggestion for auto fix: ${autoFixCommand}');
}

if (shouldAutoFix && autoFixCommand.isNotEmpty) {
logger.info('Start autofix...');
final process = await Process.start('/bin/sh', ['-xc', autoFixCommand]);
process.stdout.pipe(stdout);
process.stderr.pipe(stderr);
final processExitCode = await process.exitCode;
if (processExitCode != 0) throw Exception('process exit with exitCode=$processExitCode');
logger.info('End autofix.');

exitCode = 0;
}

if (exitCode == 0) {
logger.info(green.wrap('✓ No dependency issues found!'));
}
Expand Down