From 0d95a37de091e3740684a2d481ebc37952079856 Mon Sep 17 00:00:00 2001 From: byshy <33957976+byshy@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:52:40 +0200 Subject: [PATCH 1/4] - Add fix command to the CLI runner --- .../lint_analyzer/lint_analyzer.dart | 114 ++++++++++++++++-- lib/src/cli/cli_runner.dart | 2 + lib/src/cli/commands/fix_lints_command.dart | 78 ++++++++++++ lib/src/cli/models/parsed_arguments.dart | 7 ++ 4 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 lib/src/cli/commands/fix_lints_command.dart diff --git a/lib/src/analyzers/lint_analyzer/lint_analyzer.dart b/lib/src/analyzers/lint_analyzer/lint_analyzer.dart index daef04bb6..d6e9bfed5 100644 --- a/lib/src/analyzers/lint_analyzer/lint_analyzer.dart +++ b/lib/src/analyzers/lint_analyzer/lint_analyzer.dart @@ -141,6 +141,105 @@ class LintAnalyzer { return analyzerResult; } + Future runCliFix( + Iterable folders, + String rootFolder, + LintConfig config, { + String? sdkPath, + }) async { + final collection = createAnalysisContextCollection(folders, rootFolder, sdkPath); + + for (final context in collection.contexts) { + final lintAnalysisConfig = _getAnalysisConfig(context, rootFolder, config); + + if (config.shouldPrintConfig) { + _logger?.printConfig(lintAnalysisConfig.toJson()); + } + + final filePaths = getFilePaths( + folders, + context, + rootFolder, + lintAnalysisConfig.globalExcludes, + ); + + final analyzedFiles = filePaths.intersection(context.contextRoot.analyzedFiles().toSet()); + + final contextsLength = collection.contexts.length; + final filesLength = analyzedFiles.length; + final updateMessage = contextsLength == 1 + ? 'Fixing $filesLength file(s)' + : 'Fixing ${collection.contexts.indexOf(context) + 1}/$contextsLength contexts with $filesLength file(s)'; + _logger?.progress.update(updateMessage); + + for (final filePath in analyzedFiles) { + _logger?.infoVerbose('Fixing $filePath\n'); + + final unit = await context.currentSession.getResolvedUnit(filePath); + if (unit is ResolvedUnitResult) { + final (issuesNo, fixesNo) = _analyzeAndFixFile( + unit, + lintAnalysisConfig, + rootFolder, + filePath: filePath, + ); + + if (issuesNo != 0) { + _logger?.write( + '\nFix result: fixed $fixesNo out of $issuesNo issues\n', + ); + } else { + _logger?.infoVerbose( + 'No issues found', + ); + } + } + } + } + + return; + } + + (int issuesNo, int fixesNo) _analyzeAndFixFile( + ResolvedUnitResult unit, + LintAnalysisConfig config, + String rootFolder, { + required String filePath, + }) { + final result = _analyzeFile(unit, config, rootFolder, filePath: filePath); + + if (result == null || result.issues.isEmpty) { + return (0, 0); + } + + final originalContent = StringBuffer(unit.content); + var fixedContent = originalContent.toString(); + final fixedIssues = []; + + for (final issue in result.issues) { + final fix = issue.suggestion; + + if (fix != null) { + fixedContent = fixedContent.replaceRange( + issue.location.start.offset, + issue.location.end.offset, + fix.replacement, + ); + + fixedIssues.add(issue); + } + } + + _applyFixesToFile(fixedContent, filePath); + + return (result.issues.length, fixedIssues.length); + } + + Future _applyFixesToFile(String fixedContent, String filePath) async { + final file = File(filePath); + await file.writeAsString(fixedContent); + } + Iterable> getSummary( Iterable records, ) => @@ -164,14 +263,12 @@ class LintAnalyzer { SummaryLintReportRecord( title: 'Average Cyclomatic Number per line of code', value: averageCYCLO(records), - violations: - metricViolations(records, CyclomaticComplexityMetric.metricId), + violations: metricViolations(records, CyclomaticComplexityMetric.metricId), ), SummaryLintReportRecord( title: 'Average Source Lines of Code per method', value: averageSLOC(records), - violations: - metricViolations(records, SourceLinesOfCodeMetric.metricId), + violations: metricViolations(records, SourceLinesOfCodeMetric.metricId), ), SummaryLintReportRecord( title: 'Total tech debt', @@ -184,11 +281,9 @@ class LintAnalyzer { String rootFolder, LintConfig config, ) { - final analysisOptions = analysisOptionsFromContext(context) ?? - analysisOptionsFromFilePath(rootFolder, context); + final analysisOptions = analysisOptionsFromContext(context) ?? analysisOptionsFromFilePath(rootFolder, context); - final contextConfig = - ConfigBuilder.getLintConfigFromOptions(analysisOptions).merge(config); + final contextConfig = ConfigBuilder.getLintConfigFromOptions(analysisOptions).merge(config); return ConfigBuilder.getLintAnalysisConfig( contextConfig, @@ -231,8 +326,7 @@ class LintAnalyzer { final classMetrics = _checkClassMetrics(visitor, internalResult, config); final fileMetrics = _checkFileMetrics(visitor, internalResult, config); - final functionMetrics = - _checkFunctionMetrics(visitor, internalResult, config); + final functionMetrics = _checkFunctionMetrics(visitor, internalResult, config); final antiPatterns = _checkOnAntiPatterns( ignores, internalResult, diff --git a/lib/src/cli/cli_runner.dart b/lib/src/cli/cli_runner.dart index cecb9ade4..fc3f447ab 100644 --- a/lib/src/cli/cli_runner.dart +++ b/lib/src/cli/cli_runner.dart @@ -10,6 +10,7 @@ import 'commands/check_unnecessary_nullable_command.dart'; import 'commands/check_unused_code_command.dart'; import 'commands/check_unused_files_command.dart'; import 'commands/check_unused_l10n_command.dart'; +import 'commands/fix_lints_command.dart'; import 'models/flag_names.dart'; /// Represents a cli runner responsible @@ -29,6 +30,7 @@ class CliRunner extends CommandRunner { CheckUnusedL10nCommand(_logger), CheckUnusedCodeCommand(_logger), CheckUnnecessaryNullableCommand(_logger), + FixCommand(_logger), ].forEach(addCommand); _usesVersionOption(); diff --git a/lib/src/cli/commands/fix_lints_command.dart b/lib/src/cli/commands/fix_lints_command.dart new file mode 100644 index 000000000..25b3501a6 --- /dev/null +++ b/lib/src/cli/commands/fix_lints_command.dart @@ -0,0 +1,78 @@ +// ignore_for_file: public_member_api_docs + +import '../../../config.dart'; +import '../../../lint_analyzer.dart'; +import '../../logger/logger.dart'; +import '../models/flag_names.dart'; +import 'base_command.dart'; + +class FixCommand extends BaseCommand { + final LintAnalyzer _analyzer; + final Logger _logger; + + @override + String get name => 'fix'; + + @override + String get description => + 'Automatically fix code issues based on lint rules and metrics.'; + + @override + String get invocation => + '${runner?.executableName} $name [arguments] '; + + FixCommand(this._logger) : _analyzer = LintAnalyzer(_logger) { + _addFlags(); + } + + @override + Future runCommand() async { + _logger + ..isSilent = isNoCongratulate + ..isVerbose = isVerbose + ..progress.start('Applying fixes'); + + final parsedArgs = ParsedArguments.fromArgsNoMetrics(argResults); + final config = ConfigBuilder.getLintConfigFromArgs(parsedArgs); + + // Run the analysis and apply fixes + await _analyzer.runCliFix( + argResults.rest, + parsedArgs.rootFolder, + config, + sdkPath: findSdkPath(), + ); + + _logger.progress.complete('Fixes have been applied. Preparing the results:'); + } + + void _addFlags() { + addCommonFlags(); + _usesExitOption(); + } + + void _usesExitOption() { + argParser + ..addSeparator('') + ..addOption( + FlagNames.setExitOnViolationLevel, + allowed: ['noted', 'warning', 'alarm'], + valueHelp: 'warning', + help: + 'Set exit code 2 if code violations same or higher level than selected are detected.', + ) + ..addFlag( + FlagNames.fatalStyle, + help: 'Treat style level issues as fatal.', + ) + ..addFlag( + FlagNames.fatalPerformance, + help: 'Treat performance level issues as fatal.', + ) + ..addFlag( + FlagNames.fatalWarnings, + help: 'Treat warning level issues as fatal.', + defaultsTo: true, + ); + } +} diff --git a/lib/src/cli/models/parsed_arguments.dart b/lib/src/cli/models/parsed_arguments.dart index 1a69302d5..861119cb3 100644 --- a/lib/src/cli/models/parsed_arguments.dart +++ b/lib/src/cli/models/parsed_arguments.dart @@ -30,4 +30,11 @@ class ParsedArguments { metric.id: argResults[metric.id] as Object, }, ); + + factory ParsedArguments.fromArgsNoMetrics(ArgResults argResults) => ParsedArguments( + excludePath: argResults[FlagNames.exclude] as String, + rootFolder: argResults[FlagNames.rootFolder] as String, + shouldPrintConfig: argResults[FlagNames.printConfig] as bool, + metricsConfig: {}, + ); } From 85ecfa7fed4f470ef8d8794d5462fbe469e629d9 Mon Sep 17 00:00:00 2001 From: byshy <33957976+byshy@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:35:32 +0200 Subject: [PATCH 2/4] - Extract duplicated code - Add jsonReportPath to the fix command - Add unit test to the fix command --- .../lint_analyzer/lint_analyzer.dart | 122 +++++++++--------- lib/src/cli/commands/fix_lints_command.dart | 36 ++++++ lib/src/cli/models/parsed_arguments.dart | 1 + .../lint_analyzer/lint_fix_fixed_example.dart | 8 ++ .../lint_fix_original_example.dart | 8 ++ .../lint_analyzer/lint_analyzer_test.dart | 89 ++++++++++--- .../examples/fix_command_fixed_example.dart | 75 +++++++++++ .../fix_command_original_example.dart | 75 +++++++++++ test/src/cli/commands/fix_command_test.dart | 73 +++++++++++ 9 files changed, 405 insertions(+), 82 deletions(-) create mode 100644 test/resources/lint_analyzer/lint_fix_fixed_example.dart create mode 100644 test/resources/lint_analyzer/lint_fix_original_example.dart create mode 100644 test/src/cli/commands/examples/fix_command_fixed_example.dart create mode 100644 test/src/cli/commands/examples/fix_command_original_example.dart create mode 100644 test/src/cli/commands/fix_command_test.dart diff --git a/lib/src/analyzers/lint_analyzer/lint_analyzer.dart b/lib/src/analyzers/lint_analyzer/lint_analyzer.dart index d6e9bfed5..584389afe 100644 --- a/lib/src/analyzers/lint_analyzer/lint_analyzer.dart +++ b/lib/src/analyzers/lint_analyzer/lint_analyzer.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:analyzer/dart/analysis/analysis_context.dart'; +import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:path/path.dart'; @@ -77,44 +78,22 @@ class LintAnalyzer { LintConfig config, { String? sdkPath, }) async { - final collection = - createAnalysisContextCollection(folders, rootFolder, sdkPath); - + final collection = createAnalysisContextCollection(folders, rootFolder, sdkPath); final analyzerResult = []; for (final context in collection.contexts) { - final lintAnalysisConfig = - _getAnalysisConfig(context, rootFolder, config); - - final report = LintAnalysisOptionsValidator.validateOptions( - lintAnalysisConfig, + final (lintAnalysisConfig, analyzedFiles, report) = prepareLintAnalysis( + context, + folders, rootFolder, + config, + collection, ); + if (report != null) { analyzerResult.add(report); } - if (config.shouldPrintConfig) { - _logger?.printConfig(lintAnalysisConfig.toJson()); - } - - final filePaths = getFilePaths( - folders, - context, - rootFolder, - lintAnalysisConfig.globalExcludes, - ); - - final analyzedFiles = - filePaths.intersection(context.contextRoot.analyzedFiles().toSet()); - - final contextsLength = collection.contexts.length; - final filesLength = analyzedFiles.length; - final updateMessage = contextsLength == 1 - ? 'Analyzing $filesLength file(s)' - : 'Analyzing ${collection.contexts.indexOf(context) + 1}/$contextsLength contexts with $filesLength file(s)'; - _logger?.progress.update(updateMessage); - for (final filePath in analyzedFiles) { _logger?.infoVerbose('Analyzing $filePath'); @@ -131,7 +110,6 @@ class LintAnalyzer { _logger?.infoVerbose( 'Analysis result: found ${result.issues.length} issues', ); - analyzerResult.add(result); } } @@ -150,28 +128,14 @@ class LintAnalyzer { final collection = createAnalysisContextCollection(folders, rootFolder, sdkPath); for (final context in collection.contexts) { - final lintAnalysisConfig = _getAnalysisConfig(context, rootFolder, config); - - if (config.shouldPrintConfig) { - _logger?.printConfig(lintAnalysisConfig.toJson()); - } - - final filePaths = getFilePaths( - folders, + final (lintAnalysisConfig, analyzedFiles, _) = prepareLintAnalysis( context, + folders, rootFolder, - lintAnalysisConfig.globalExcludes, + config, + collection, ); - final analyzedFiles = filePaths.intersection(context.contextRoot.analyzedFiles().toSet()); - - final contextsLength = collection.contexts.length; - final filesLength = analyzedFiles.length; - final updateMessage = contextsLength == 1 - ? 'Fixing $filesLength file(s)' - : 'Fixing ${collection.contexts.indexOf(context) + 1}/$contextsLength contexts with $filesLength file(s)'; - _logger?.progress.update(updateMessage); - for (final filePath in analyzedFiles) { _logger?.infoVerbose('Fixing $filePath\n'); @@ -200,6 +164,45 @@ class LintAnalyzer { return; } + ( + LintAnalysisConfig lintAnalysisConfig, + Set analyzedFiles, + LintFileReport? report, + ) prepareLintAnalysis( + AnalysisContext context, + Iterable folders, + String rootFolder, + LintConfig config, + AnalysisContextCollection collection, + ) { + final lintAnalysisConfig = _getAnalysisConfig(context, rootFolder, config); + final report = LintAnalysisOptionsValidator.validateOptions( + lintAnalysisConfig, + rootFolder, + ); + + if (config.shouldPrintConfig) { + _logger?.printConfig(lintAnalysisConfig.toJson()); + } + + final filePaths = getFilePaths( + folders, + context, + rootFolder, + lintAnalysisConfig.globalExcludes, + ); + final analyzedFiles = filePaths.intersection(context.contextRoot.analyzedFiles().toSet()); + + final contextsLength = collection.contexts.length; + final filesLength = analyzedFiles.length; + final updateMessage = contextsLength == 1 + ? 'Processing $filesLength file(s)' + : 'Processing ${collection.contexts.indexOf(context) + 1}/$contextsLength contexts with $filesLength file(s)'; + _logger?.progress.update(updateMessage); + + return (lintAnalysisConfig, analyzedFiles, report); + } + (int issuesNo, int fixesNo) _analyzeAndFixFile( ResolvedUnitResult unit, LintAnalysisConfig config, @@ -374,11 +377,10 @@ class LintAnalyzer { createAbsolutePatterns(rule.excludes, config.rootFolder), )) .expand( - (rule) => - rule.check(source).where((issue) => !ignores.isSuppressedAt( - issue.ruleId, - issue.location.start.line, - )), + (rule) => rule.check(source).where((issue) => !ignores.isSuppressedAt( + issue.ruleId, + issue.location.start.line, + )), ) .toList(); @@ -396,12 +398,10 @@ class LintAnalyzer { source.path, createAbsolutePatterns(pattern.excludes, config.rootFolder), )) - .expand((pattern) => pattern - .check(source, classMetrics, functionMetrics) - .where((issue) => !ignores.isSuppressedAt( - issue.ruleId, - issue.location.start.line, - ))) + .expand((pattern) => pattern.check(source, classMetrics, functionMetrics).where((issue) => !ignores.isSuppressedAt( + issue.ruleId, + issue.location.start.line, + ))) .toList(); Map _checkClassMetrics( @@ -518,7 +518,5 @@ class LintAnalyzer { } bool _isSupported(FileResult result) => - result.path.endsWith('.dart') && - !result.path.endsWith('.g.dart') && - !result.path.endsWith('.freezed.dart'); + result.path.endsWith('.dart') && !result.path.endsWith('.g.dart') && !result.path.endsWith('.freezed.dart'); } diff --git a/lib/src/cli/commands/fix_lints_command.dart b/lib/src/cli/commands/fix_lints_command.dart index 25b3501a6..5cadce13a 100644 --- a/lib/src/cli/commands/fix_lints_command.dart +++ b/lib/src/cli/commands/fix_lints_command.dart @@ -47,10 +47,46 @@ class FixCommand extends BaseCommand { } void _addFlags() { + _usesReporterOption(); addCommonFlags(); _usesExitOption(); } + void _usesReporterOption() { + argParser + ..addSeparator('') + ..addOption( + FlagNames.reporter, + abbr: 'r', + help: 'The format of the output of the analysis.', + valueHelp: FlagNames.consoleReporter, + allowed: [ + FlagNames.consoleReporter, + FlagNames.consoleVerboseReporter, + FlagNames.checkstyleReporter, + FlagNames.codeClimateReporter, + FlagNames.githubReporter, + FlagNames.gitlabCodeClimateReporter, + FlagNames.htmlReporter, + FlagNames.jsonReporter, + ], + defaultsTo: FlagNames.consoleReporter, + ) + ..addOption( + FlagNames.reportFolder, + abbr: 'o', + help: 'Write HTML output to OUTPUT.', + valueHelp: 'OUTPUT', + defaultsTo: 'metrics', + ) + ..addOption( + FlagNames.jsonReportPath, + help: 'Path to the JSON file with the output of the analysis.', + valueHelp: 'path/to/file.json', + defaultsTo: null, + ); + } + void _usesExitOption() { argParser ..addSeparator('') diff --git a/lib/src/cli/models/parsed_arguments.dart b/lib/src/cli/models/parsed_arguments.dart index 861119cb3..48cd4a31b 100644 --- a/lib/src/cli/models/parsed_arguments.dart +++ b/lib/src/cli/models/parsed_arguments.dart @@ -34,6 +34,7 @@ class ParsedArguments { factory ParsedArguments.fromArgsNoMetrics(ArgResults argResults) => ParsedArguments( excludePath: argResults[FlagNames.exclude] as String, rootFolder: argResults[FlagNames.rootFolder] as String, + jsonReportPath: argResults[FlagNames.jsonReportPath] as String?, shouldPrintConfig: argResults[FlagNames.printConfig] as bool, metricsConfig: {}, ); diff --git a/test/resources/lint_analyzer/lint_fix_fixed_example.dart b/test/resources/lint_analyzer/lint_fix_fixed_example.dart new file mode 100644 index 000000000..b55bab684 --- /dev/null +++ b/test/resources/lint_analyzer/lint_fix_fixed_example.dart @@ -0,0 +1,8 @@ +// ignore_for_file: prefer_const_declarations, literal_only_boolean_expressions +class ExcludeExample { + void test() { + final value = [1, 2, 3]; + + print(value.firstOrNull); + } +} diff --git a/test/resources/lint_analyzer/lint_fix_original_example.dart b/test/resources/lint_analyzer/lint_fix_original_example.dart new file mode 100644 index 000000000..b55bab684 --- /dev/null +++ b/test/resources/lint_analyzer/lint_fix_original_example.dart @@ -0,0 +1,8 @@ +// ignore_for_file: prefer_const_declarations, literal_only_boolean_expressions +class ExcludeExample { + void test() { + final value = [1, 2, 3]; + + print(value.firstOrNull); + } +} diff --git a/test/src/analyzers/lint_analyzer/lint_analyzer_test.dart b/test/src/analyzers/lint_analyzer/lint_analyzer_test.dart index 381c01892..e6df0667e 100644 --- a/test/src/analyzers/lint_analyzer/lint_analyzer_test.dart +++ b/test/src/analyzers/lint_analyzer/lint_analyzer_test.dart @@ -5,6 +5,7 @@ import 'package:dart_code_linter/src/analyzers/lint_analyzer/lint_config.dart'; import 'package:dart_code_linter/src/analyzers/lint_analyzer/metrics/models/metric_value_level.dart'; import 'package:dart_code_linter/src/analyzers/lint_analyzer/models/lint_file_report.dart'; import 'package:dart_code_linter/src/analyzers/lint_analyzer/models/report.dart'; +import 'package:dart_code_linter/src/analyzers/lint_analyzer/rules/rules_list/prefer_first_or_null/prefer_first_or_null_rule.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; @@ -29,7 +30,7 @@ void main() { config, ); - expect(result, hasLength(2)); + expect(result, hasLength(4)); }); test('should analyze only one file', () async { @@ -43,7 +44,7 @@ void main() { config, ); - expect(result, hasLength(1)); + expect(result, hasLength(3)); }); test('should report default code metrics', () async { @@ -55,11 +56,7 @@ void main() { config, ); - final report = - reportForFile(result, 'lint_analyzer_exclude_example.dart') - .functions - .values - .first; + final report = reportForFile(result, 'lint_analyzer_exclude_example.dart').functions.values.first; final metrics = {for (final m in report.metrics) m.metricsId: m.level}; expect(metrics, { @@ -82,11 +79,7 @@ void main() { config, ); - final report = - reportForFile(result, 'lint_analyzer_exclude_example.dart') - .functions - .values - .first; + final report = reportForFile(result, 'lint_analyzer_exclude_example.dart').functions.values.first; final metrics = {for (final m in report.metrics) m.metricsId: m.level}; expect(metrics, { @@ -111,10 +104,7 @@ void main() { config, ); - final report = - reportForFile(result, 'lint_analyzer_exclude_example.dart') - .functions - .values; + final report = reportForFile(result, 'lint_analyzer_exclude_example.dart').functions.values; expect(report, isEmpty); }); @@ -128,8 +118,7 @@ void main() { config, ); - final issues = - reportForFile(result, 'lint_analyzer_exclude_example.dart').issues; + final issues = reportForFile(result, 'lint_analyzer_exclude_example.dart').issues; expect( issues.map((issue) => issue.ruleId), @@ -149,8 +138,7 @@ void main() { config, ); - final report = - reportForFile(result, 'lint_analyzer_exclude_example.dart').issues; + final report = reportForFile(result, 'lint_analyzer_exclude_example.dart').issues; expect(report, isEmpty); }); @@ -214,6 +202,67 @@ void main() { equals('10 USD'), ); }); + + test('should not fix files', () async { + final basePath = '${Directory.current.path}/test/resources/lint_analyzer'; + final fixedExamplePath = '$basePath/lint_fix_fixed_example.dart'; + + final originalFixedExampleContent = await File(fixedExamplePath).readAsString(); + + final config = _createConfig( + excludePatterns: [ + 'test/resources/lint_analyzer/lint_analyzer_example.dart', + 'test/resources/lint_analyzer/lint_analyzer_exclude_example.dart', + 'test/resources/lint_analyzer/lint_fix_original_example.dart', + ], + rules: { + PreferFirstOrNullRule.ruleId: {}, + }, + ); + + await analyzer.runCliFix( + folders, + rootDirectory, + config, + ); + + final fixedExampleContent = await File(fixedExamplePath).readAsString(); + + expect( + originalFixedExampleContent, + equals(fixedExampleContent), + ); + }); + + test('should fix files', () async { + final basePath = '${Directory.current.path}/test/resources/lint_analyzer'; + final originalExamplePath = '$basePath/lint_fix_original_example.dart'; + final fixedExamplePath = '$basePath/lint_fix_fixed_example.dart'; + + final originalExampleContent = await File(originalExamplePath).readAsString(); + + final config = _createConfig( + rules: { + PreferFirstOrNullRule.ruleId: {}, + }, + ); + + await analyzer.runCliFix( + folders, + rootDirectory, + config, + ); + + final modifiedExampleContent = await File(originalExamplePath).readAsString(); + final fixedExampleContent = await File(fixedExamplePath).readAsString(); + + expect( + modifiedExampleContent, + equals(fixedExampleContent), + ); + + await File(originalExamplePath).writeAsString(originalExampleContent); + }); }, testOn: 'posix', ); diff --git a/test/src/cli/commands/examples/fix_command_fixed_example.dart b/test/src/cli/commands/examples/fix_command_fixed_example.dart new file mode 100644 index 000000000..0ef8b29ec --- /dev/null +++ b/test/src/cli/commands/examples/fix_command_fixed_example.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_example/src/unnecessary_nullable_widget.dart'; +import 'package:flutter_example/src/unused_code_widget.dart'; + +void main() { + runApp(const MyApp()); +} + +/// The issue below is reported by Dart Code Linter. +/// In order to resolve the warning, the class or the file should be renamed. +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) => MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + /// The issue below is reported by Dart Code Linter. + /// In order to resolve the warning, the parameter should be used or removed. + void _incrementCounter(BuildContext context) { + setState(() { + _counter++; + }); + } + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + UnusedCodeWidget(), + UnnecessaryNullableWidget(), + ], + ), + ), + + /// The issue below is reported by Dart Code Linter. The severity of a rule can be configured via `severity` config entry. + /// Adding a trailing comma will remove the highlight. + /// Trailing comma can also be added with auto-fix menu command in the IDE. + /// Rules that support auto-fixes are marked with corresponding emoji in the docs https://dartcodemetrics.dev/docs/rules. + floatingActionButton: FloatingActionButton( + onPressed: () => _incrementCounter(context), + tooltip: 'Increment', + child: const Icon(Icons.add), + ),); +} diff --git a/test/src/cli/commands/examples/fix_command_original_example.dart b/test/src/cli/commands/examples/fix_command_original_example.dart new file mode 100644 index 000000000..46e5c4852 --- /dev/null +++ b/test/src/cli/commands/examples/fix_command_original_example.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_example/src/unnecessary_nullable_widget.dart'; +import 'package:flutter_example/src/unused_code_widget.dart'; + +void main() { + runApp(const MyApp()); +} + +/// The issue below is reported by Dart Code Linter. +/// In order to resolve the warning, the class or the file should be renamed. +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) => MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + /// The issue below is reported by Dart Code Linter. + /// In order to resolve the warning, the parameter should be used or removed. + void _incrementCounter(BuildContext context) { + setState(() { + _counter++; + }); + } + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + UnusedCodeWidget(), + UnnecessaryNullableWidget(), + ], + ), + ), + + /// The issue below is reported by Dart Code Linter. The severity of a rule can be configured via `severity` config entry. + /// Adding a trailing comma will remove the highlight. + /// Trailing comma can also be added with auto-fix menu command in the IDE. + /// Rules that support auto-fixes are marked with corresponding emoji in the docs https://dartcodemetrics.dev/docs/rules. + floatingActionButton: FloatingActionButton( + onPressed: () => _incrementCounter(context), + tooltip: 'Increment', + child: const Icon(Icons.add), + )); +} diff --git a/test/src/cli/commands/fix_command_test.dart b/test/src/cli/commands/fix_command_test.dart new file mode 100644 index 000000000..66e71a926 --- /dev/null +++ b/test/src/cli/commands/fix_command_test.dart @@ -0,0 +1,73 @@ +import 'package:dart_code_linter/src/cli/cli_runner.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +const _usage = 'Automatically fix code issues based on lint rules and metrics.\n' + '\n' + 'Usage: metrics fix [arguments] \n' + '-h, --help Print this usage information.\n' + '\n' + '\n' + '-r, --reporter= The format of the output of the analysis.\n' + ' [console (default), console-verbose, checkstyle, codeclimate, github, gitlab, html, json]\n' + '-o, --output-directory= Write HTML output to OUTPUT.\n' + ' (defaults to "metrics")\n' + ' --json-path= Path to the JSON file with the output of the analysis.\n' + '\n' + '\n' + '-c, --print-config Print resolved config.\n' + '\n' + '\n' + ' --root-folder=<./> Root folder.\n' + ' (defaults to current directory)\n' + ' --sdk-path= Dart SDK directory path. Should be provided only when you run the application as compiled executable(https://dart.dev/tools/dart-compile#exe) and automatic Dart SDK path detection fails.\n' + ' --exclude=<{/**.g.dart,/**.freezed.dart}> File paths in Glob syntax to be exclude.\n' + ' (defaults to "{/**.g.dart,/**.freezed.dart}")\n' + '\n' + '\n' + " --no-congratulate Don't show output even when there are no issues.\n" + '\n' + '\n' + ' --[no-]verbose Show verbose logs.\n' + '\n' + '\n' + ' --set-exit-on-violation-level= Set exit code 2 if code violations same or higher level than selected are detected.\n' + ' [noted, warning, alarm]\n' + ' --[no-]fatal-style Treat style level issues as fatal.\n' + ' --[no-]fatal-performance Treat performance level issues as fatal.\n' + ' --[no-]fatal-warnings Treat warning level issues as fatal.\n' + ' (defaults to on)\n' + '\n' + 'Run "metrics help" to see global options.'; + +void main() { + group('FixCommand', () { + final runner = CliRunner(); + final command = runner.commands['fix']; + + test('should have correct name', () { + expect(command?.name, equals('fix')); + }); + + test('should have correct description', () { + expect( + command?.description, + equals('Automatically fix code issues based on lint rules and metrics.'), + ); + }); + + test('should have correct invocation', () { + expect( + command?.invocation, + equals('metrics fix [arguments] '), + ); + }); + + test('should have correct usage', () { + expect( + command?.usage.replaceAll('"${p.current}"', 'current directory'), + equals(_usage), + ); + }); + }); +} From 7d63060876d98a346ae80dc1d53b249149497887 Mon Sep 17 00:00:00 2001 From: byshy <33957976+byshy@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:54:49 +0200 Subject: [PATCH 3/4] - minor, return the original example to it's correct state (using first instead of firstOrNull to be picked by the linter --- test/resources/lint_analyzer/lint_fix_original_example.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/resources/lint_analyzer/lint_fix_original_example.dart b/test/resources/lint_analyzer/lint_fix_original_example.dart index b55bab684..61c79d46e 100644 --- a/test/resources/lint_analyzer/lint_fix_original_example.dart +++ b/test/resources/lint_analyzer/lint_fix_original_example.dart @@ -3,6 +3,6 @@ class ExcludeExample { void test() { final value = [1, 2, 3]; - print(value.firstOrNull); + print(value.first); } } From e21c58a9d729c7c645979e1e89cf1a3fa2ba2238 Mon Sep 17 00:00:00 2001 From: byshy <33957976+byshy@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:56:15 +0200 Subject: [PATCH 4/4] - Delete unwanted files --- .../examples/fix_command_fixed_example.dart | 75 ------------------- .../fix_command_original_example.dart | 75 ------------------- 2 files changed, 150 deletions(-) delete mode 100644 test/src/cli/commands/examples/fix_command_fixed_example.dart delete mode 100644 test/src/cli/commands/examples/fix_command_original_example.dart diff --git a/test/src/cli/commands/examples/fix_command_fixed_example.dart b/test/src/cli/commands/examples/fix_command_fixed_example.dart deleted file mode 100644 index 0ef8b29ec..000000000 --- a/test/src/cli/commands/examples/fix_command_fixed_example.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_example/src/unnecessary_nullable_widget.dart'; -import 'package:flutter_example/src/unused_code_widget.dart'; - -void main() { - runApp(const MyApp()); -} - -/// The issue below is reported by Dart Code Linter. -/// In order to resolve the warning, the class or the file should be renamed. -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) => MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - /// The issue below is reported by Dart Code Linter. - /// In order to resolve the warning, the parameter should be used or removed. - void _incrementCounter(BuildContext context) { - setState(() { - _counter++; - }); - } - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - UnusedCodeWidget(), - UnnecessaryNullableWidget(), - ], - ), - ), - - /// The issue below is reported by Dart Code Linter. The severity of a rule can be configured via `severity` config entry. - /// Adding a trailing comma will remove the highlight. - /// Trailing comma can also be added with auto-fix menu command in the IDE. - /// Rules that support auto-fixes are marked with corresponding emoji in the docs https://dartcodemetrics.dev/docs/rules. - floatingActionButton: FloatingActionButton( - onPressed: () => _incrementCounter(context), - tooltip: 'Increment', - child: const Icon(Icons.add), - ),); -} diff --git a/test/src/cli/commands/examples/fix_command_original_example.dart b/test/src/cli/commands/examples/fix_command_original_example.dart deleted file mode 100644 index 46e5c4852..000000000 --- a/test/src/cli/commands/examples/fix_command_original_example.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_example/src/unnecessary_nullable_widget.dart'; -import 'package:flutter_example/src/unused_code_widget.dart'; - -void main() { - runApp(const MyApp()); -} - -/// The issue below is reported by Dart Code Linter. -/// In order to resolve the warning, the class or the file should be renamed. -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) => MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - /// The issue below is reported by Dart Code Linter. - /// In order to resolve the warning, the parameter should be used or removed. - void _incrementCounter(BuildContext context) { - setState(() { - _counter++; - }); - } - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - UnusedCodeWidget(), - UnnecessaryNullableWidget(), - ], - ), - ), - - /// The issue below is reported by Dart Code Linter. The severity of a rule can be configured via `severity` config entry. - /// Adding a trailing comma will remove the highlight. - /// Trailing comma can also be added with auto-fix menu command in the IDE. - /// Rules that support auto-fixes are marked with corresponding emoji in the docs https://dartcodemetrics.dev/docs/rules. - floatingActionButton: FloatingActionButton( - onPressed: () => _incrementCounter(context), - tooltip: 'Increment', - child: const Icon(Icons.add), - )); -}