-
Notifications
You must be signed in to change notification settings - Fork 24
Add new rule avoid_duplicate_code #320
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
Open
solid-illiaaihistov
wants to merge
28
commits into
solid-software:analysis_server_migration
Choose a base branch
from
solid-illiaaihistov:153-add-avoid_duplicate_code_rule
base: analysis_server_migration
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+2,778
−20
Open
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
0012940
feat: add avoid_duplicate_code lint rule
77f24f8
feat: increase default min_statements for avoid_duplicate_code from 3…
d8e267e
feat: enable cross-file duplicate code detection by implementing a gl…
f57a284
chore: remove avoid_using_api from analysis exclude list
6c3900b
refactor: introduce SolidDiagnosticMessage to encapsulate diagnostic …
59e6884
feat: register SolidLintsPlugin in pubspec and remove redundant analy…
74b6e93
refactor: optimize duplicate code detection with Jenkins hashing, str…
94b1d74
refactor: simplify avoid_duplicate_code parameters by removing defaul…
f4918d3
refactor: simplify duplicate code detection logic by extracting visit…
a18f796
refactor: modularize duplicate detection logic by introducing special…
f1b870e
chore: remove exclusion for avoid_using_api lint rules
80f888a
fix: use stable strings instead of runtime hash codes in AST structur…
d0611f9
fix: improve accuracy and stability of duplicate code detection by im…
ce36e02
refactor: remove unnecessary empty line in global_hash_registry.dart
1b55728
refactor: fix indentation in AvoidDuplicateCodeParameters hashCode me…
5e8e393
docs: update changelog with avoid_duplicate_code rule and formatting …
2faba00
chore: update default minimum tokens to 30 for avoid_duplicate_code lint
44de4fa
feat: add equality operators and hashCode overrides to parameter mode…
6b6fde6
fix: handle corrupted cache files in HashCacheStorage and add regress…
8a766a6
test: update avoid_duplicate_code_rule_test to include excluded ident…
4c88728
fix: use set for deletedFiles and skip them when building the inverte…
c2af6ec
fix: prevent infinite loop in token counting by returning count when …
46ef176
fix: improve path precision in duplicate code detection to avoid sibl…
5aff0b5
refactor: enable independent debounce logic for different package roo…
3ee9826
fix: catch all exceptions during file operations in hash cache storag…
1a62878
fix: target specific DuplicateLocation for removal in global hash reg…
89b2051
fix: resolve package-specific parameter retrieval during debounced ca…
c9f6df2
refactor: inject ResourceProvider into avoid_duplicate_code services …
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
lib/src/lints/avoid_duplicate_code/avoid_duplicate_code_rule.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| import 'package:analyzer/analysis_rule/rule_context.dart'; | ||
| import 'package:analyzer/analysis_rule/rule_visitor_registry.dart'; | ||
| 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'; | ||
|
|
||
| /// A lint rule that detects duplicated code blocks (clones) within a single | ||
| /// file and across multiple files in the project. | ||
| /// | ||
| /// When two or more function/method/constructor bodies have structurally | ||
| /// identical AST subtrees (Type 2 clones — same structure, variable names | ||
| /// may differ), the rule reports all copies and provides context messages | ||
| /// linking to the other occurrences. | ||
| /// | ||
| /// Cross-file detection works on a "best-effort" basis: duplicates are | ||
| /// detected against files that have already been analyzed in the current | ||
| /// session. The detection improves as more files are analyzed. | ||
| /// | ||
| /// ### Example config: | ||
| /// | ||
| /// ```yaml | ||
| /// plugins: | ||
| /// solid_lints: | ||
| /// diagnostics: | ||
| /// avoid_duplicate_code: | ||
| /// min_tokens: 50 | ||
| /// ignore_literals: false | ||
| /// ignore_identifiers: true | ||
| /// check_blocks: false | ||
| /// exclude: | ||
| /// - method_name: build | ||
| /// ``` | ||
| class AvoidDuplicateCodeRule | ||
| extends SolidLintRule<AvoidDuplicateCodeParameters> { | ||
| /// Name of the lint. | ||
| static const lintName = 'avoid_duplicate_code'; | ||
|
|
||
| static const _code = LintCode( | ||
| lintName, | ||
| 'Perhaps this code is a duplicate.\n' | ||
| 'Consider extracting the shared logic into a common function.', | ||
| ); | ||
|
|
||
| @override | ||
| DiagnosticCode get diagnosticCode => _code; | ||
|
|
||
| /// Creates a new instance of [AvoidDuplicateCodeRule]. | ||
| AvoidDuplicateCodeRule({ | ||
| required super.analysisOptionsLoader, | ||
| }) : super.withParameters( | ||
| name: lintName, | ||
| description: | ||
| 'Detects structurally identical function/method bodies ' | ||
| 'within a single file and across files (code clones).', | ||
| parametersParser: AvoidDuplicateCodeParameters.fromJson, | ||
| ); | ||
|
|
||
| @override | ||
| void registerNodeProcessors( | ||
| RuleVisitorRegistry registry, | ||
| RuleContext context, | ||
| ) { | ||
| super.registerNodeProcessors(registry, context); | ||
|
|
||
| final parameters = | ||
| getParametersForContext(context) ?? | ||
| AvoidDuplicateCodeParameters.empty(); | ||
|
|
||
| final visitor = AvoidDuplicateCodeVisitor( | ||
| this, | ||
| parameters, | ||
| filePath: context.definingUnit.file.path, | ||
| modificationStamp: context.definingUnit.file.modificationStamp, | ||
| contextRoot: context.libraryElement?.session.analysisContext.contextRoot, | ||
| resourceProvider: context.definingUnit.file.provider, | ||
| ); | ||
|
|
||
| registry.addCompilationUnit(this, visitor); | ||
| } | ||
| } | ||
88 changes: 88 additions & 0 deletions
88
lib/src/lints/avoid_duplicate_code/models/avoid_duplicate_code_parameters.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import 'package:solid_lints/src/common/parameters/excluded_identifiers_list_parameter.dart'; | ||
|
|
||
| /// Configuration parameters for the avoid_duplicate_code rule. | ||
| class AvoidDuplicateCodeParameters { | ||
| /// Minimum number of tokens in a function body or block to be considered | ||
| /// for clone detection. Bodies/blocks shorter than this are ignored. | ||
| final int minTokens; | ||
|
|
||
| /// When `true`, literal values (strings, numbers, booleans) are excluded | ||
| /// from the structural hash, making the check ignore literal differences. | ||
| final bool ignoreLiterals; | ||
|
|
||
| /// When `true`, local variable and parameter names are excluded | ||
| /// from the structural hash, allowing detection of renamed variables | ||
| /// (Type 2). Note that method, class, and field names are NOT ignored | ||
| /// to prevent excessive false positives. | ||
| final bool ignoreIdentifiers; | ||
|
|
||
| /// When `true`, statement blocks (like if-blocks or loops) inside functions | ||
| /// are also checked for duplication. | ||
| final bool checkBlocks; | ||
|
|
||
| /// A list of methods/functions that should be excluded from the lint. | ||
| final ExcludedIdentifiersListParameter exclude; | ||
|
|
||
| static const _defaultMinTokens = 30; | ||
|
|
||
| static final _defaultExclude = ExcludedIdentifiersListParameter( | ||
| exclude: const [], | ||
| ); | ||
|
|
||
| /// Constructor for [AvoidDuplicateCodeParameters] model. | ||
| const AvoidDuplicateCodeParameters({ | ||
| required this.minTokens, | ||
| required this.ignoreLiterals, | ||
| required this.ignoreIdentifiers, | ||
| required this.checkBlocks, | ||
| required this.exclude, | ||
| }); | ||
|
|
||
| /// Empty [AvoidDuplicateCodeParameters] model with default values. | ||
| factory AvoidDuplicateCodeParameters.empty() => AvoidDuplicateCodeParameters( | ||
| minTokens: _defaultMinTokens, | ||
| ignoreLiterals: false, | ||
| ignoreIdentifiers: true, | ||
| checkBlocks: true, | ||
| exclude: _defaultExclude, | ||
| ); | ||
|
|
||
| /// Creates parameters from JSON configuration. | ||
| factory AvoidDuplicateCodeParameters.fromJson(Map<String, Object?> json) { | ||
| return AvoidDuplicateCodeParameters( | ||
| minTokens: json['min_tokens'] as int? ?? _defaultMinTokens, | ||
| ignoreLiterals: json['ignore_literals'] as bool? ?? false, | ||
| ignoreIdentifiers: json['ignore_identifiers'] as bool? ?? true, | ||
| checkBlocks: json['check_blocks'] as bool? ?? true, | ||
| exclude: ExcludedIdentifiersListParameter.defaultFromJson(json), | ||
| ); | ||
| } | ||
|
|
||
| /// Converts the parameters to a JSON-compatible Map. | ||
| Map<String, Object?> toJson() => { | ||
| 'min_tokens': minTokens, | ||
| 'ignore_literals': ignoreLiterals, | ||
| 'ignore_identifiers': ignoreIdentifiers, | ||
| 'check_blocks': checkBlocks, | ||
| 'exclude': exclude.exclude.map((e) => e.toJson()).toList(), | ||
| }; | ||
|
|
||
| @override | ||
| bool operator ==(Object other) => | ||
| identical(this, other) || | ||
| other is AvoidDuplicateCodeParameters && | ||
| other.minTokens == minTokens && | ||
| other.ignoreLiterals == ignoreLiterals && | ||
| other.ignoreIdentifiers == ignoreIdentifiers && | ||
| other.checkBlocks == checkBlocks && | ||
| other.exclude == exclude; | ||
|
|
||
| @override | ||
| int get hashCode => Object.hash( | ||
| minTokens, | ||
| ignoreLiterals, | ||
| ignoreIdentifiers, | ||
| checkBlocks, | ||
| exclude, | ||
| ); | ||
|
solid-illiaaihistov marked this conversation as resolved.
|
||
| } | ||
7 changes: 7 additions & 0 deletions
7
lib/src/lints/avoid_duplicate_code/models/body_candidate.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import 'package:analyzer/dart/ast/ast.dart'; | ||
|
|
||
| /// A record representing a block or expression candidate for clone detection. | ||
| typedef BodyCandidate = ({ | ||
| AstNode node, | ||
| Declaration? enclosingDeclaration, | ||
| }); |
18 changes: 18 additions & 0 deletions
18
lib/src/lints/avoid_duplicate_code/models/cross_file_match.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import 'package:solid_lints/src/lints/avoid_duplicate_code/models/duplicate_location.dart'; | ||
| import 'package:solid_lints/src/lints/avoid_duplicate_code/models/hash_entry.dart'; | ||
|
|
||
| /// Represents a structural duplicate found in other files. | ||
| class CrossFileMatch { | ||
| /// The hash entry in the current file. | ||
| final HashEntry currentEntry; | ||
|
|
||
| /// The list of locations in other files where a duplicate of this entry | ||
| /// exists. | ||
| final List<DuplicateLocation> duplicates; | ||
|
|
||
| /// Creates a new [CrossFileMatch]. | ||
| const CrossFileMatch({ | ||
| required this.currentEntry, | ||
| required this.duplicates, | ||
| }); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.