|
| 1 | +--- |
| 2 | +id: MDS037 |
| 3 | +name: duplicated-content |
| 4 | +status: ready |
| 5 | +description: Paragraphs should not repeat verbatim across Markdown files. |
| 6 | +--- |
| 7 | +# MDS037: duplicated-content |
| 8 | + |
| 9 | +Paragraphs should not repeat verbatim across Markdown files. |
| 10 | + |
| 11 | +- **ID**: MDS037 |
| 12 | +- **Name**: `duplicated-content` |
| 13 | +- **Status**: ready |
| 14 | +- **Default**: disabled (opt-in via `.mdsmith.yml`); |
| 15 | + include: [], exclude: [], min-chars: 200 |
| 16 | +- **Fixable**: no |
| 17 | +- **Implementation**: |
| 18 | + [source](./) |
| 19 | +- **Category**: meta |
| 20 | + |
| 21 | +## Settings |
| 22 | + |
| 23 | +| Setting | Type | Default | Description | |
| 24 | +|-------------|------|---------|------------------------------------------------| |
| 25 | +| `include` | list | `[]` | glob patterns limiting which siblings to scan | |
| 26 | +| `exclude` | list | `[]` | glob patterns of siblings to skip | |
| 27 | +| `min-chars` | int | `200` | minimum normalized paragraph length to compare | |
| 28 | + |
| 29 | +Before comparing, the rule normalizes each paragraph. Whitespace |
| 30 | +collapses to single spaces. Letters become lowercase. Leading and |
| 31 | +trailing space is trimmed. A paragraph shorter than `min-chars` runes |
| 32 | +is skipped; short stubs would otherwise produce noise. |
| 33 | + |
| 34 | +The rule walks `RootFS` when the project root is known. Otherwise it |
| 35 | +falls back to the file's own directory. An `include` list narrows the |
| 36 | +scan to matching paths. An `exclude` entry takes precedence. |
| 37 | + |
| 38 | +## Performance |
| 39 | + |
| 40 | +Each checked file reads every other Markdown file in scope |
| 41 | +(`.md` and `.markdown`). A project |
| 42 | +with *N* Markdown files performs *O(N²)* reads. Small and medium |
| 43 | +corpora stay fast. For large corpora add an `exclude` entry for |
| 44 | +generated or vendored directories. |
| 45 | + |
| 46 | +## Config |
| 47 | + |
| 48 | +```yaml |
| 49 | +rules: |
| 50 | + duplicated-content: |
| 51 | + include: |
| 52 | + - "docs/**" |
| 53 | + exclude: |
| 54 | + - "docs/generated/**" |
| 55 | + min-chars: 200 |
| 56 | +``` |
| 57 | +
|
| 58 | +Disable: |
| 59 | +
|
| 60 | +```yaml |
| 61 | +rules: |
| 62 | + duplicated-content: false |
| 63 | +``` |
| 64 | +
|
| 65 | +## Examples |
| 66 | +
|
| 67 | +### Good |
| 68 | +
|
| 69 | +<?include |
| 70 | +file: good/simple.md |
| 71 | +wrap: markdown |
| 72 | +?> |
| 73 | +
|
| 74 | +```markdown |
| 75 | +# Simple Fixture |
| 76 | + |
| 77 | +One short fixture sits alone in its folder and exists to exercise |
| 78 | +the duplicate detector. Every other rule stays quiet because the |
| 79 | +text is simple and brief. The paragraph holds enough characters to |
| 80 | +pass two hundred runes after normalization. Each sentence is plain |
| 81 | +and ends early. No other file here repeats this wording. |
| 82 | +``` |
| 83 | + |
| 84 | +<?/include?> |
| 85 | + |
| 86 | +### Bad -- duplicated paragraph |
| 87 | + |
| 88 | +<?include |
| 89 | +file: bad/duplicate.md |
| 90 | +wrap: markdown |
| 91 | +?> |
| 92 | + |
| 93 | +```markdown |
| 94 | +# Duplicate Fixture |
| 95 | + |
| 96 | +A distinctive paragraph appears in this file and in a sibling |
| 97 | +fixture, so MDS037 must flag the match and point at the other |
| 98 | +location. The wording stays above the default two-hundred character |
| 99 | +threshold after normalization. It stays unique relative to the |
| 100 | +other rule fixtures so nothing matches by accident across the test |
| 101 | +suite. |
| 102 | +``` |
| 103 | + |
| 104 | +<?/include?> |
| 105 | + |
| 106 | +### Bad -- duplicated source |
| 107 | + |
| 108 | +<?include |
| 109 | +file: bad/ref/source.md |
| 110 | +wrap: markdown |
| 111 | +?> |
| 112 | + |
| 113 | +```markdown |
| 114 | +# Source Fixture |
| 115 | + |
| 116 | +A distinctive paragraph appears in this file and in a sibling |
| 117 | +fixture, so MDS037 must flag the match and point at the other |
| 118 | +location. The wording stays above the default two-hundred character |
| 119 | +threshold after normalization. It stays unique relative to the |
| 120 | +other rule fixtures so nothing matches by accident across the test |
| 121 | +suite. |
| 122 | +``` |
| 123 | + |
| 124 | +<?/include?> |
| 125 | + |
| 126 | +## Diagnostics |
| 127 | + |
| 128 | +| Condition | Message | |
| 129 | +|-------------------|--------------------------------------------------------------------------| |
| 130 | +| paragraph repeats | paragraph duplicated in {other}:{line} | |
| 131 | +| invalid glob | duplicated-content: {include,exclude}: invalid glob pattern "{pat}": ... | |
0 commit comments