Skip to content
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
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ You can customize individual rule settings in your `analysis_options.yaml`:

```yaml
plugins:
solid_lints:
version: <INSERT LATEST VERSION>
diagnostics:
cyclomatic_complexity:
max_complexity: 10
avoid_non_null_assertion: true
solid_lints: <INSERT LATEST VERSION>

solid_lints:
diagnostics:
cyclomatic_complexity:
max_complexity: 10
avoid_non_null_assertion: true
```

# Badge
Expand Down
52 changes: 27 additions & 25 deletions example/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,30 @@ include: package:solid_lints/analysis_options.yaml
plugins:
solid_lints:
path: ../
diagnostics:
cyclomatic_complexity:
max_complexity: 4
number_of_parameters:
max_parameters: 2
function_lines_of_code:
max_lines: 50
avoid_non_null_assertion: true
avoid_late_keyword: true
avoid_global_state: true
avoid_returning_widgets: true
avoid_unnecessary_setstate: true
double_literal_format: true
avoid_unnecessary_type_assertions: true
avoid_debug_print_in_release: true
avoid_using_api:
severity: info
entries:
- class_name: Future
identifier: wait
source: dart:async
reason: >-
Future.wait should be avoided because it loses type safety for
the results. Use a Record's `wait` method instead.
severity: warning

solid_lints:
diagnostics:
cyclomatic_complexity:
max_complexity: 4
number_of_parameters:
max_parameters: 2
function_lines_of_code:
max_lines: 50
avoid_non_null_assertion: true
avoid_late_keyword: true
avoid_global_state: true
avoid_returning_widgets: true
avoid_unnecessary_setstate: true
double_literal_format: true
avoid_unnecessary_type_assertions: true
avoid_debug_print_in_release: true
avoid_using_api:
severity: info
entries:
- class_name: Future
identifier: wait
source: dart:async
reason: >-
Future.wait should be avoided because it loses type safety for
the results. Use a Record's `wait` method instead.
severity: warning
3 changes: 2 additions & 1 deletion lib/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ solid_lints:
newline_before_return: true
no_empty_block: true
no_equal_then_else: true
prefer_early_return: true
# Disabled by default for now. Will be considered for future inclusion.
prefer_early_return: false

no_magic_number:
allowed_in_widget_params: true
Expand Down
38 changes: 36 additions & 2 deletions lib/src/common/parameter_parser/analysis_options_loader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,34 @@ class AnalysisOptionsLoader {
) ??
false;

/// Checks if a file is excluded by the analysis options configuration.
bool isFileExcluded(RuleContext context) {
final targetPath = (context.currentUnit ?? context.definingUnit).file.path;

return isFileExcludedForFile(targetPath);
}

/// Checks if a specific file at [filePath] is excluded by the nearest
/// analysis options file.
bool isFileExcludedForFile(String filePath) {
final pathContext = _resourceProvider.pathContext;
if (!pathContext.isAbsolute(filePath)) return false;

final dirPath = pathContext.dirname(filePath);
final yamlPath = _findNearestAnalysisOptionsFilePath(dirPath);
if (yamlPath == null) return false;

_loadRulesOptionsIfNewer(yamlPath);
final rootDir = pathContext.dirname(yamlPath);

return _rulesCache[yamlPath]?.isPathExcluded(
filePath,
pathContext,
rootDir,
) ??
false;
}

/// Loads lint rules from the analysis options file for all rules
/// using the provided [RuleContext].
void loadRulesOptionsFromContext(RuleContext context) =>
Expand All @@ -84,8 +112,11 @@ class AnalysisOptionsLoader {
RuleContext context,
T Function(String) f,
) {
final filePath = context.definingUnit.file.path;
final dirPath = _resourceProvider.pathContext.dirname(filePath);
final filePath = (context.currentUnit ?? context.definingUnit).file.path;
final pathContext = _resourceProvider.pathContext;
if (!pathContext.isAbsolute(filePath)) return null;

final dirPath = pathContext.dirname(filePath);
final yamlPath = _findNearestAnalysisOptionsFilePath(dirPath);

if (yamlPath == null) return null;
Expand All @@ -107,11 +138,14 @@ class AnalysisOptionsLoader {
modificationStamp: modificationStamp,
rules: rulesData.rules,
disabledRules: rulesData.disabledRules,
excludedPatterns: rulesData.excludedPatterns,
);
}

String? _findNearestAnalysisOptionsFilePath(String startDirectoryPath) {
final pathContext = _resourceProvider.pathContext;
if (!pathContext.isAbsolute(startDirectoryPath)) return null;

var currentDirectoryPath = startDirectoryPath;

while (currentDirectoryPath.isNotEmpty) {
Expand Down
31 changes: 30 additions & 1 deletion lib/src/common/parameter_parser/analysis_options_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class AnalysisOptionsParser {

final mergedRules = <String, Map<String, Object?>>{};
final disabledRules = <String>{};
final excludedPatterns = <String>{};

final nextSeenPaths = {...seenPaths, path};
_resolveAndMergeIncludes(
Expand All @@ -44,11 +45,17 @@ class AnalysisOptionsParser {
nextSeenPaths,
mergedRules,
disabledRules,
excludedPatterns,
);
_parseRuleOptions(yaml, mergedRules, disabledRules);
_parseSuppressedErrors(yaml, mergedRules, disabledRules);
_parseExcludedPatterns(yaml, excludedPatterns);

return RulesData(rules: mergedRules, disabledRules: disabledRules);
return RulesData(
rules: mergedRules,
disabledRules: disabledRules,
excludedPatterns: excludedPatterns,
);
}

Map<String, Object?>? _parseYaml(File file) {
Expand Down Expand Up @@ -79,6 +86,7 @@ class AnalysisOptionsParser {
Set<String> seenPaths,
Map<String, Map<String, Object?>> mergedRules,
Set<String> disabledRules,
Set<String> excludedPatterns,
) {
final includeOption = yaml['include'];
switch (includeOption) {
Expand All @@ -89,6 +97,7 @@ class AnalysisOptionsParser {
seenPaths,
mergedRules,
disabledRules,
excludedPatterns,
);
case List():
for (final include in includeOption) {
Expand All @@ -99,6 +108,7 @@ class AnalysisOptionsParser {
seenPaths,
mergedRules,
disabledRules,
excludedPatterns,
);
}
}
Expand All @@ -111,6 +121,7 @@ class AnalysisOptionsParser {
Set<String> seenPaths,
Map<String, Map<String, Object?>> mergedRules,
Set<String> disabledRules,
Set<String> excludedPatterns,
) {
final includedFile = _resolveIncludedFile(baseFile, includePath);
if (includedFile == null) return;
Expand All @@ -124,6 +135,7 @@ class AnalysisOptionsParser {
};
}
disabledRules.addAll(includedData.disabledRules);
excludedPatterns.addAll(includedData.excludedPatterns);
}

File? _resolveIncludedFile(File baseFile, String includePath) {
Expand Down Expand Up @@ -228,4 +240,21 @@ class AnalysisOptionsParser {
}
}
}

/// Parses file exclusion patterns configured under `analyzer: exclude:`.
void _parseExcludedPatterns(
Map<String, Object?> yaml,
Set<String> excludedPatterns,
) {
final analyzer = yaml['analyzer'];
if (analyzer is! Map) return;

final exclude = analyzer['exclude'];
switch (exclude) {
case String():
excludedPatterns.add(exclude);
case Iterable():
excludedPatterns.addAll(exclude.whereType<String>());
}
}
}
42 changes: 40 additions & 2 deletions lib/src/common/parameter_parser/cached_package_rules.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import 'package:glob/glob.dart';
import 'package:path/path.dart' as p;
import 'package:solid_lints/src/utils/function_utils.dart';

/// Cached rules for a dart package
class CachedPackageRules {
/// The last modification stamp of the analysis options file
Expand All @@ -9,10 +13,44 @@ class CachedPackageRules {
/// Rules that are explicitly disabled
final Set<String> disabledRules;

/// Patterns of excluded files
final Set<String> excludedPatterns;

final List<Glob> _compiledGlobs;
final Map<String, bool> _pathExclusionCache = {};

/// Creates an instance of [CachedPackageRules]
const CachedPackageRules({
CachedPackageRules({
required this.modificationStamp,
required this.rules,
required this.disabledRules,
});
required this.excludedPatterns,
}) : _compiledGlobs = _compileGlobs(excludedPatterns);

static List<Glob> _compileGlobs(Set<String> patterns) => patterns
.map(
(pattern) => FunctionUtils.tryOrNull(
() => Glob(pattern, context: p.posix),
),
)
.nonNulls
.toList();

/// Checks if [filePath] matches any of the excluded patterns.
bool isPathExcluded(
String filePath,
p.Context pathContext,
String rootDir,
) {
if (excludedPatterns.isEmpty) return false;

return _pathExclusionCache.putIfAbsent(filePath, () {
final relativePath = pathContext.isWithin(rootDir, filePath)
? pathContext.relative(filePath, from: rootDir)
: filePath;
final normalizedPath = p.posix.joinAll(pathContext.split(relativePath));

return _compiledGlobs.any((glob) => glob.matches(normalizedPath));
});
}
}
14 changes: 12 additions & 2 deletions lib/src/common/parameter_parser/rules_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,19 @@ class RulesData {
/// The set of explicitly disabled rules.
final Set<String> disabledRules;

/// The set of file paths or glob patterns excluded from analysis.
final Set<String> excludedPatterns;

/// Creates a new instance of [RulesData].
const RulesData({required this.rules, required this.disabledRules});
const RulesData({
required this.rules,
required this.disabledRules,
required this.excludedPatterns,
});

/// Creates a new empty instance of [RulesData].
const RulesData.empty() : rules = const {}, disabledRules = const {};
const RulesData.empty()
: rules = const {},
disabledRules = const {},
excludedPatterns = const {};
}
34 changes: 20 additions & 14 deletions lib/src/lints/avoid_duplicate_code/avoid_duplicate_code_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:analyzer/error/error.dart';
import 'package:solid_lints/src/lints/avoid_duplicate_code/models/avoid_duplicate_code_parameters.dart';
import 'package:solid_lints/src/lints/avoid_duplicate_code/visitors/avoid_duplicate_code_visitor.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';
import 'package:solid_lints/src/utils/ignore_matcher.dart';

/// A lint rule that detects duplicated code blocks (clones) across the project.
///
Expand Down Expand Up @@ -99,17 +100,16 @@ import 'package:solid_lints/src/models/solid_lint_rule.dart';
/// ### Example config:
///
/// ```yaml
/// plugins:
/// solid_lints:
/// diagnostics:
/// avoid_duplicate_code:
/// min_tokens: 30
/// ignore_literals: false
/// ignore_identifiers: true
/// check_blocks: true
/// exclude:
/// - method_name: initState
/// - method_name: dispose
/// solid_lints:
/// diagnostics:
/// avoid_duplicate_code:
/// min_tokens: 30
/// ignore_literals: false
/// ignore_identifiers: true
/// check_blocks: true
/// exclude:
/// - method_name: initState
/// - method_name: dispose
/// ```
class AvoidDuplicateCodeRule
extends SolidLintRule<AvoidDuplicateCodeParameters> {
Expand All @@ -136,6 +136,9 @@ class AvoidDuplicateCodeRule
parametersParser: AvoidDuplicateCodeParameters.fromJson,
);

/// The ignore matcher instance for this rule.
final ignoreMatcher = IgnoreMatcher(lintName);

@override
void registerNodeProcessors(
RuleVisitorRegistry registry,
Expand All @@ -147,13 +150,16 @@ class AvoidDuplicateCodeRule
getParametersForContext(context) ??
AvoidDuplicateCodeParameters.empty();

final currentUnit = context.currentUnit ?? context.definingUnit;
final visitor = AvoidDuplicateCodeVisitor(
this,
parameters,
filePath: context.definingUnit.file.path,
modificationStamp: context.definingUnit.file.modificationStamp,
filePath: currentUnit.file.path,
modificationStamp: currentUnit.file.modificationStamp,
contextRoot: context.libraryElement?.session.analysisContext.contextRoot,
resourceProvider: context.definingUnit.file.provider,
resourceProvider: currentUnit.file.provider,
analysisOptionsLoader: analysisOptionsLoader,
ignoreMatcher: ignoreMatcher,
);

registry.addCompilationUnit(this, visitor);
Expand Down

This file was deleted.

Loading