Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

feat: add monorepo flag for check-unused-files command #684

Merged
merged 5 commits into from
Feb 9, 2022
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

* fix: add `monorepo` flag for `check-unused-code` command.
* fix: ignore a class usage inside `State<T>` for `check-unused-code` command.
* fix: correctly handle variables declaration for `check-unused-code` command.
* feat: add static code diagnostics `avoid-dynamic`, `prefer-async-await`.
Expand Down
20 changes: 1 addition & 19 deletions lib/src/analyzers/unused_code_analyzer/unused_code_analyzer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/element/element.dart';
// ignore: implementation_imports
import 'package:analyzer/src/dart/element/element.dart';
import 'package:glob/glob.dart';
import 'package:path/path.dart';
import 'package:source_span/source_span.dart';

import '../../config_builder/config_builder.dart';
import '../../config_builder/models/analysis_options.dart';
import '../../reporters/models/reporter.dart';
import '../../utils/analyzer_utils.dart';
import '../../utils/file_utils.dart';
import 'models/file_elements_usage.dart';
import 'models/unused_code_file_report.dart';
import 'models/unused_code_issue.dart';
Expand Down Expand Up @@ -61,7 +59,7 @@ class UnusedCodeAnalyzer {

final excludes = unusedCodeAnalysisConfig.globalExcludes
.followedBy(unusedCodeAnalysisConfig.analyzerExcludedPatterns);
final filePaths = _getFilePaths(folders, context, rootFolder, excludes);
final filePaths = getFilePaths(folders, context, rootFolder, excludes);

final analyzedFiles =
filePaths.intersection(context.contextRoot.analyzedFiles().toSet());
Expand Down Expand Up @@ -111,22 +109,6 @@ class UnusedCodeAnalyzer {
return ConfigBuilder.getUnusedCodeConfig(contextConfig, rootFolder);
}

Set<String> _getFilePaths(
Iterable<String> folders,
AnalysisContext context,
String rootFolder,
Iterable<Glob> excludes,
) {
final contextFolders = folders.where((path) {
final newPath = normalize(join(rootFolder, path));

return newPath == context.contextRoot.root.path ||
context.contextRoot.root.path.startsWith('$newPath/');
}).toList();

return extractDartFilesFromFolders(contextFolders, rootFolder, excludes);
}

FileElementsUsage? _analyzeFileCodeUsages(SomeResolvedUnitResult unit) {
if (unit is ResolvedUnitResult) {
final visitor = UsedCodeVisitor();
Expand Down
52 changes: 31 additions & 21 deletions lib/src/analyzers/unused_files_analyzer/unused_files_analyzer.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:io';

import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:path/path.dart';
Expand All @@ -8,10 +9,10 @@ import '../../config_builder/config_builder.dart';
import '../../config_builder/models/analysis_options.dart';
import '../../reporters/models/reporter.dart';
import '../../utils/analyzer_utils.dart';
import '../../utils/file_utils.dart';
import 'models/unused_files_file_report.dart';
import 'reporters/reporter_factory.dart';
import 'reporters/unused_files_report_params.dart';
import 'unused_files_analysis_config.dart';
import 'unused_files_config.dart';
import 'unused_files_visitor.dart';

Expand Down Expand Up @@ -45,22 +46,12 @@ class UnusedFilesAnalyzer {
final unusedFiles = <String>{};

for (final context in collection.contexts) {
final analysisOptions = await analysisOptionsFromContext(context) ??
await analysisOptionsFromFilePath(rootFolder);

final contextConfig =
ConfigBuilder.getUnusedFilesConfigFromOption(analysisOptions)
.merge(config);
final unusedFilesAnalysisConfig =
ConfigBuilder.getUnusedFilesConfig(contextConfig, rootFolder);

final contextFolders = folders
.where((path) => normalize(join(rootFolder, path))
.startsWith(context.contextRoot.root.path))
.toList();
await _getAnalysisConfig(context, rootFolder, config);

final filePaths = extractDartFilesFromFolders(
contextFolders,
final filePaths = getFilePaths(
folders,
context,
rootFolder,
unusedFilesAnalysisConfig.globalExcludes,
);
Expand All @@ -69,19 +60,18 @@ class UnusedFilesAnalyzer {

final analyzedFiles =
filePaths.intersection(context.contextRoot.analyzedFiles().toSet());

for (final filePath in analyzedFiles) {
final unit = await context.currentSession.getResolvedUnit(filePath);
unusedFiles.removeAll(_analyzeFile(filePath, unit));
unusedFiles.removeAll(_analyzeFile(filePath, unit, config.isMonorepo));
}

final notAnalyzedFiles = filePaths.difference(analyzedFiles);

for (final filePath in notAnalyzedFiles) {
if (unusedFilesAnalysisConfig.analyzerExcludedPatterns
.any((pattern) => pattern.matches(filePath))) {
final unit = await resolveFile2(path: filePath);
unusedFiles.removeAll(_analyzeFile(filePath, unit));
unusedFiles
.removeAll(_analyzeFile(filePath, unit, config.isMonorepo));
}
}
}
Expand All @@ -102,9 +92,29 @@ class UnusedFilesAnalyzer {
}
}

Iterable<String> _analyzeFile(String filePath, SomeResolvedUnitResult unit) {
Future<UnusedFilesAnalysisConfig> _getAnalysisConfig(
AnalysisContext context,
String rootFolder,
UnusedFilesConfig config,
) async {
final analysisOptions = await analysisOptionsFromContext(context) ??
await analysisOptionsFromFilePath(rootFolder);

final contextConfig =
ConfigBuilder.getUnusedFilesConfigFromOption(analysisOptions)
.merge(config);

return ConfigBuilder.getUnusedFilesConfig(contextConfig, rootFolder);
}

Iterable<String> _analyzeFile(
String filePath,
SomeResolvedUnitResult unit,
bool ignoreExports,
) {
if (unit is ResolvedUnitResult) {
final visitor = UnusedFilesVisitor(filePath);
final visitor =
UnusedFilesVisitor(filePath, ignoreExports: ignoreExports);
unit.unit.visitChildren(visitor);

return visitor.paths;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import '../../config_builder/models/analysis_options.dart';
class UnusedFilesConfig {
final Iterable<String> excludePatterns;
final Iterable<String> analyzerExcludePatterns;
final bool isMonorepo;

const UnusedFilesConfig({
required this.excludePatterns,
required this.analyzerExcludePatterns,
required this.isMonorepo,
});

/// Creates the config from analysis [options].
Expand All @@ -16,13 +18,18 @@ class UnusedFilesConfig {
excludePatterns: const [],
analyzerExcludePatterns:
options.readIterableOfString(['analyzer', 'exclude']),
isMonorepo: false,
);

/// Creates the config from cli args.
factory UnusedFilesConfig.fromArgs(Iterable<String> excludePatterns) =>
factory UnusedFilesConfig.fromArgs(
Iterable<String> excludePatterns, {
required bool isMonorepo,
}) =>
UnusedFilesConfig(
excludePatterns: excludePatterns,
analyzerExcludePatterns: const [],
isMonorepo: isMonorepo,
);

/// Merges two configs into a single one.
Expand All @@ -35,5 +42,6 @@ class UnusedFilesConfig {
...analyzerExcludePatterns,
...overrides.analyzerExcludePatterns,
},
isMonorepo: isMonorepo || overrides.isMonorepo,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import 'package:analyzer/dart/ast/visitor.dart';
import '../../utils/node_utils.dart';

class UnusedFilesVisitor extends GeneralizingAstVisitor<void> {
final String _currentFilePath;
final String currentFilePath;
final bool ignoreExports;

final _paths = <String>[];

Iterable<String> get paths => _paths;

UnusedFilesVisitor(this._currentFilePath);
UnusedFilesVisitor(this.currentFilePath, {required this.ignoreExports});

@override
void visitUriBasedDirective(UriBasedDirective node) {
Expand All @@ -32,15 +33,17 @@ class UnusedFilesVisitor extends GeneralizingAstVisitor<void> {
}
}

if (node is ExportDirective && !_paths.contains(_currentFilePath)) {
_paths.add(_currentFilePath);
if (!ignoreExports &&
node is ExportDirective &&
!_paths.contains(currentFilePath)) {
_paths.add(currentFilePath);
}
}

@override
void visitFunctionDeclaration(FunctionDeclaration node) {
if (isEntrypoint(node.name.name, node.metadata)) {
_paths.add(_currentFilePath);
_paths.add(currentFilePath);
}
}
}
2 changes: 1 addition & 1 deletion lib/src/cli/commands/check_unused_code_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class CheckUnusedCodeCommand extends BaseCommand {
..addSeparator('')
..addFlag(
FlagNames.fatalOnUnused,
help: 'Treat find unused file as fatal.',
help: 'Treat find unused code as fatal.',
// TODO(dkrutrkikh): activate on next major version
// defaultsTo: true,
);
Expand Down
17 changes: 15 additions & 2 deletions lib/src/cli/commands/check_unused_files_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ class CheckUnusedFilesCommand extends BaseCommand {
final folders = argResults.rest;
final excludePath = argResults[FlagNames.exclude] as String;
final reporterName = argResults[FlagNames.reporter] as String;

final isMonorepo = argResults[FlagNames.isMonorepo] as bool;
final noCongratulate = argResults[FlagNames.noCongratulate] as bool;
final deleteFiles = argResults[FlagNames.deleteFiles] as bool;

final config = ConfigBuilder.getUnusedFilesConfigFromArgs([excludePath]);
final config = ConfigBuilder.getUnusedFilesConfigFromArgs(
[excludePath],
isMonorepo: isMonorepo,
);

final unusedFilesResult = await _analyzer.runCliAnalysis(
folders,
Expand Down Expand Up @@ -70,6 +73,7 @@ class CheckUnusedFilesCommand extends BaseCommand {
void _addFlags() {
_usesReporterOption();
addCommonFlags();
_usesIsMonorepoOption();
_usesExitOption();
_usesDeleteUnusedFiles();
}
Expand All @@ -90,6 +94,15 @@ class CheckUnusedFilesCommand extends BaseCommand {
);
}

void _usesIsMonorepoOption() {
argParser
..addSeparator('')
..addFlag(
FlagNames.isMonorepo,
help: 'Treats all exported files as unused by default.',
);
}

void _usesExitOption() {
argParser
..addSeparator('')
Expand Down
7 changes: 4 additions & 3 deletions lib/src/config_builder/config_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ class ConfigBuilder {

/// Creates a raw unused files config from given [excludePatterns].
static UnusedFilesConfig getUnusedFilesConfigFromArgs(
Iterable<String> excludePatterns,
) =>
UnusedFilesConfig.fromArgs(excludePatterns);
Iterable<String> excludePatterns, {
required bool isMonorepo,
}) =>
UnusedFilesConfig.fromArgs(excludePatterns, isMonorepo: isMonorepo);

/// Creates a raw unused files config from given [options].
static UnusedFilesConfig getUnusedFilesConfigFromOption(
Expand Down
20 changes: 20 additions & 0 deletions lib/src/utils/analyzer_utils.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
// ignore_for_file: implementation_imports
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/file_byte_store.dart';
import 'package:glob/glob.dart';
import 'package:path/path.dart';

import 'file_utils.dart';

AnalysisContextCollection createAnalysisContextCollection(
Iterable<String> folders,
String rootFolder,
Expand All @@ -22,6 +26,22 @@ AnalysisContextCollection createAnalysisContextCollection(
);
}

Set<String> getFilePaths(
Iterable<String> folders,
AnalysisContext context,
String rootFolder,
Iterable<Glob> excludes,
) {
final contextFolders = folders.where((path) {
final newPath = normalize(join(rootFolder, path));

return newPath == context.contextRoot.root.path ||
context.contextRoot.root.path.startsWith('$newPath/');
}).toList();

return extractDartFilesFromFolders(contextFolders, rootFolder, excludes);
}

/// If the state location can be accessed, return the file byte store,
/// otherwise return the memory byte store.
ByteStore _createByteStore(PhysicalResourceProvider resourceProvider) {
Expand Down
2 changes: 2 additions & 0 deletions test/src/analyzers/lint_analyzer/lint_config_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ void main() {
);
expect(result.rules, equals(_defaults.rules));
});

test('empty and overrides configs', () {
final result = _empty.merge(_overrides);

Expand All @@ -208,6 +209,7 @@ void main() {
);
expect(result.rules, equals(_overrides.rules));
});

test('defaults and overrides configs', () {
final result = _defaults.merge(_overrides);

Expand Down
Loading