Skip to main content

avoid_duplicate_code

A lint rule that detects duplicated code blocks (clones) across the project.

When two or more function, method, or constructor bodies or nested code blocks (such as if blocks or loops) have structurally identical AST subtrees, the rule reports all currently known copies and provides context messages linking to the other occurrences.

Clone Detection Algorithm

The rule is built upon the fundamental code clone classification by Roy & Cordy (2007) ("A Survey on Software Clone Detection Research"):

Type 2 & Type 3 Clones

The rule focuses on Type 2 clones (syntactic clones with renamed variables) and Type 3 clones with differing literals (structurally identical AST subtrees where literal values differ). While plain text diff tools only catch exact copies (Type 1), this rule operates on the AST level to detect copy-pasted logic even after variable renaming, code formatting changes, or literal constant tweaks.

Sequential Variable Indexing

Local variable and parameter names in the AST subtree are replaced with sequential positional IDs (0, 1, 2, ...) based on their first appearance in the code block. This enables the algorithm to recognize identical underlying logic even if variables were renamed (e.g., x to item).

Dual Structural & Exact Hashing

Computes both a structural hash (ignoring literal values) and an exact hash (including literal values) in a single pass using Bob Jenkins' One-at-a-time hash algorithm. When duplicate candidates have identical structural hashes but differing exact hashes, the rule provides detailed context messages showing which literal slots differ (e.g., [1, 2] or ['hello', 'world']).

Nested Clone Suppression

The algorithm intelligently suppresses overlapping or nested warnings. If a large code block (e.g., an entire function) is identified as a clone, its inner blocks (such as if statements or loops) will not be reported separately, preventing warning spam.

Best-Effort Sequential Analysis & Caching

  • Cross-File Analysis (Best-Effort): Because the Dart Analyzer processes project files sequentially, during the initial analysis pass, only the second (and subsequent) clone is highlighted immediately. This happens because the first file was analyzed before information about its copy entered the global registry. The first file will be highlighted upon its next edit, save, or re-analysis.
  • Persistent Disk Cache: Candidate hash entries and their metadata are cached on disk at .dart_tool/solid_lints/duplicate_index.json. Consequently, subsequent IDE sessions or re-analysis passes skip unchanged files, making duplicate code detection significantly faster.

Handling False Positives

warning

Important Context on Code Duplication

Because this rule analyzes the AST structure, you may encounter warnings in scenarios where extracting the code is actually undesirable:

  • Boilerplate Code: Repetitive structures like form initializers, DTO mappers, or standard lifecycle hooks (e.g., initState, dispose) often share identical AST flows by necessity.
  • Coincidental Duplication: Not all duplication is harmful. If two identical code blocks serve completely distinct business purposes, merging them creates a forced dependency. This can lead to hasty abstractions and tight coupling.

In such justified cases, rather than forcing an unnatural abstraction, we encourage you to consider the following solutions:

  1. Ignore specific occurrences in code: Use inline analysis ignore comments above the affected method:
    // ignore: avoid_duplicate_code
    void myBoilerplateMethod() { /* ... */ }
  2. Exclude methods globally: Add the method name to the exclude list in your analysis_options.yaml config (e.g., excluding initState or dispose).
  3. Increase min_tokens threshold: Raise the min_tokens parameter (e.g., to 40 or 50) to ignore shorter structural clones across the project.

Example config:

solid_lints:
diagnostics:
avoid_duplicate_code:
min_tokens: 30
exclude:
- method_name: initState
- method_name: dispose

Parameters

min_tokens (int)

Minimum number of tokens in a function body or block required for it to be included in clone detection. Shorter bodies/blocks are ignored.

What is a Token?

The smallest indivisible syntactic unit of code emitted by the compiler's lexer (keywords final, if, switch; identifiers; operators =, =>; punctuation {, }, ; and literals).

Considering the modern and concise syntax of Dart 3+ (switch expressions, pattern matching, record destructuring), the optimal default threshold was determined to be 30 tokens (approximately 4-6 lines of meaningful code). This automatically filters out trivial single-line expressions and focuses exclusively on substantial logic blocks.

Example 1: Less than 30 tokens (Ignored): 26 tokens

A concise switch expression in Dart 3 syntax contains 26 tokens and is ignored:

Color getShapeColor(Shape shape) => switch (shape) { // 6 tokens
Circle(:final color) => color, // 9 tokens
Square(:final color) => color, // 9 tokens
}; // 2 tokens
Example 2: 30+ tokens (Checked for duplicates): 34 tokens

A function with record destructuring and pattern matching in Dart 3 syntax contains 34 tokens and is checked for duplicates:

String formatUserRole(Object user) => switch (user) { // 6 tokens
User(isAdmin: true, isVerified: true) => 'Admin', // 13 tokens
User(isVerified: true) => 'User', // 9 tokens
_ => 'Guest User', // 4 tokens
}; // 2 tokens

exclude (String | Map | List<String | Map>)

A list of methods/functions that should be excluded from clone detection.