rubocop-spellcheck 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +152 -45
- data/config/default.yml +11 -1
- data/lib/rubocop/cop/spellcheck/spelling.rb +49 -45
- data/lib/rubocop/spellcheck/asciidoc.rb +120 -0
- data/lib/rubocop/spellcheck/asciidoc_inline.rb +89 -0
- data/lib/rubocop/spellcheck/asciidoc_table.rb +49 -0
- data/lib/rubocop/spellcheck/checker.rb +6 -6
- data/lib/rubocop/spellcheck/correction.rb +66 -0
- data/lib/rubocop/spellcheck/front_matter.rb +49 -6
- data/lib/rubocop/spellcheck/front_matter_paths.rb +37 -0
- data/lib/rubocop/spellcheck/markdown.rb +34 -14
- data/lib/rubocop/spellcheck/markdown_directives.rb +83 -0
- data/lib/rubocop/spellcheck/markdown_ruby.rb +94 -0
- data/lib/rubocop/spellcheck/plugin.rb +1 -1
- data/lib/rubocop/spellcheck/prose_source.rb +1 -1
- data/lib/rubocop/spellcheck/rdoc.rb +140 -0
- data/lib/rubocop/spellcheck/rdoc_comments.rb +66 -0
- data/lib/rubocop/spellcheck/ruby_code.rb +37 -0
- data/lib/rubocop/spellcheck/ruby_identifiers.rb +54 -0
- data/lib/rubocop/spellcheck/ruby_string_map.rb +89 -0
- data/lib/rubocop/spellcheck/ruby_strings.rb +163 -0
- data/lib/rubocop/spellcheck/span.rb +35 -0
- data/lib/rubocop/spellcheck/version.rb +1 -1
- data/lib/rubocop/spellcheck/vocabulary.rb +94 -0
- data/lib/rubocop/spellcheck/vocabulary_config.rb +72 -0
- data/lib/rubocop/spellcheck/vocabulary_formatter.rb +47 -0
- data/lib/rubocop-spellcheck.rb +17 -0
- metadata +50 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1632104e9d052f6d9f121d814c60b072fd5af231437efc0cb4e424c910fd5cf6
|
|
4
|
+
data.tar.gz: 4ed0b83c791c4ea57a8d5f6dd8ed2e3b37fdb67604b1c2f0376e4c3fe9dc4135
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1cf73548e344df7cfbdece8a4ac0b82d983f68b5facf2cd820515ab64aa923bb197f377bd22ad1a805039fdbe89ed68659bf81b3b6cc161eefbffbb80ac1707f
|
|
7
|
+
data.tar.gz: b397a5491ea97e5040ed71288442d940558acb8a42d4089626210ed213dab5c18c473a3ddaefbb0b629ca980e4dbc53c6c7177dc5bdbfdd95aa73062558745cf
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### 0.2.0 (2026/10/21)
|
|
4
|
+
|
|
5
|
+
* [#28](https://github.com/dblock/rubocop-spellcheck/pull/28): Exclude `require`, `require_relative`, `load`, and `autoload` paths and `gem` names and version requirements from spelling checks in Ruby files and Markdown Ruby fences - [@dblock](https://github.com/dblock).
|
|
6
|
+
* [#29](https://github.com/dblock/rubocop-spellcheck/pull/29): Display generated vocabulary paths relative to the working directory, matching RuboCop output - [@dblock](https://github.com/dblock).
|
|
7
|
+
* [#27](https://github.com/dblock/rubocop-spellcheck/pull/27): Correct lowercase and title-case typos when the first suggestion is one edit away, even when multiple suggestions exist; require dictionary-accepted replacements to prevent correction loops and retain uppercase, mixed-case, and technical-token guards - [@dblock](https://github.com/dblock).
|
|
8
|
+
* [#26](https://github.com/dblock/rubocop-spellcheck/pull/26): Restrict autocorrection to unique, single-edit lowercase typos outside dotted and identifier-like tokens; keep other suggestions report-only - [@dblock](https://github.com/dblock).
|
|
9
|
+
* [#25](https://github.com/dblock/rubocop-spellcheck/pull/25): Skip spelling suggestions during vocabulary generation to speed up `--auto-gen-config` - [@dblock](https://github.com/dblock).
|
|
10
|
+
* [#24](https://github.com/dblock/rubocop-spellcheck/pull/24): Fix spelling checks crashing on `__FILE__` in Ruby files and Markdown Ruby fences - [@dblock](https://github.com/dblock).
|
|
11
|
+
* [#22](https://github.com/dblock/rubocop-spellcheck/pull/22): Check strings and opt-in identifiers in explicitly tagged Ruby Markdown fences; keep strings enabled and identifiers disabled by default in all Ruby checks - [@dblock](https://github.com/dblock).
|
|
12
|
+
* [#21](https://github.com/dblock/rubocop-spellcheck/pull/21): Check Ruby strings and identifiers by default, with independent opt-outs, source-mapped string corrections, and report-only identifier diagnostics - [@dblock](https://github.com/dblock).
|
|
13
|
+
* [#20](https://github.com/dblock/rubocop-spellcheck/pull/20): Generate project vocabulary and dictionary configuration with `rubocop --auto-gen-config` instead of suppressing spelling checks - [@dblock](https://github.com/dblock).
|
|
14
|
+
* [#19](https://github.com/dblock/rubocop-spellcheck/pull/19): Add parser-backed AsciiDoc spelling checks and autocorrection - [@dblock](https://github.com/dblock).
|
|
15
|
+
* [#18](https://github.com/dblock/rubocop-spellcheck/pull/18): Check RDoc files and parse RDoc markup in Ruby comments, excluding code examples and link destinations - [@dblock](https://github.com/dblock).
|
|
16
|
+
* [#17](https://github.com/dblock/rubocop-spellcheck/pull/17): Select front matter values with dotted inclusion and exclusion paths - [@dblock](https://github.com/dblock).
|
|
17
|
+
* [#16](https://github.com/dblock/rubocop-spellcheck/pull/16): Support Markdown HTML comment directives for spelling suppression - [@dblock](https://github.com/dblock).
|
|
18
|
+
* [#15](https://github.com/dblock/rubocop-spellcheck/pull/15): Add spelling autocorrection for words with exactly one dictionary suggestion - [@dblock](https://github.com/dblock).
|
|
19
|
+
* [#14](https://github.com/dblock/rubocop-spellcheck/pull/14): Reuse loaded dictionaries and word verdicts across file investigations instead of rebuilding them for every file - [@dblock](https://github.com/dblock).
|
|
3
20
|
### 0.1.0 (2026/10/20)
|
|
4
21
|
|
|
5
22
|
* [#12](https://github.com/dblock/rubocop-spellcheck/pull/12): Preserve Markdown parsing and use original prose buffers - [@dblock](https://github.com/dblock).
|
data/README.md
CHANGED
|
@@ -5,15 +5,27 @@
|
|
|
5
5
|
[](https://github.com/dblock/rubocop-spellcheck/actions/workflows/package.yml)
|
|
6
6
|
[](https://rubygems.org/gems/rubocop-spellcheck)
|
|
7
7
|
|
|
8
|
-
Spellcheck Ruby comments,
|
|
8
|
+
Spellcheck Ruby comments, strings, identifiers, and documentation with [RuboCop](https://rubocop.org/) and [Kotoshu](https://github.com/kotoshu/kotoshu).
|
|
9
9
|
|
|
10
10
|
## Table of Contents
|
|
11
11
|
|
|
12
12
|
- [Installation](#installation)
|
|
13
13
|
- [Usage](#usage)
|
|
14
|
+
- [Other languages](#other-languages)
|
|
15
|
+
- [Supported files](#supported-files)
|
|
16
|
+
- [Ruby comments](#ruby-comments)
|
|
17
|
+
- [Ruby strings and identifiers](#ruby-strings-and-identifiers)
|
|
18
|
+
- [RDoc](#rdoc)
|
|
19
|
+
- [Markdown](#markdown)
|
|
20
|
+
- [YAML front matter](#yaml-front-matter)
|
|
21
|
+
- [AsciiDoc](#asciidoc)
|
|
22
|
+
- [Plain text](#plain-text)
|
|
23
|
+
- [Autocorrection](#autocorrection)
|
|
14
24
|
- [Configuration](#configuration)
|
|
25
|
+
- [Front matter paths](#front-matter-paths)
|
|
15
26
|
- [Dictionary files](#dictionary-files)
|
|
16
27
|
- [Adding project vocabulary](#adding-project-vocabulary)
|
|
28
|
+
- [Generating project vocabulary](#generating-project-vocabulary)
|
|
17
29
|
- [Replacing dictionaries and adding languages](#replacing-dictionaries-and-adding-languages)
|
|
18
30
|
- [Exclusions and suppression](#exclusions-and-suppression)
|
|
19
31
|
- [Scope and limitations](#scope-and-limitations)
|
|
@@ -46,7 +58,42 @@ bundle install
|
|
|
46
58
|
bundle exec rubocop
|
|
47
59
|
```
|
|
48
60
|
|
|
49
|
-
|
|
61
|
+
No dictionary setup or network access is needed.
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
`Spellcheck/Spelling` is enabled by default and checks Ruby comments, strings, Markdown, RDoc, AsciiDoc, and plain text. Identifier checking is opt-in:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
bundle exec rubocop
|
|
69
|
+
bundle exec rubocop --only Spellcheck/Spelling
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For example, given `example.rb`:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
# The red brown fox jumsp over the lazy dog.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Running `bundle exec rubocop --only Spellcheck/Spelling example.rb` reports:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
example.rb:1:21: C: Spellcheck/Spelling: Misspelled word: jumsp. Did you mean jumps, jump, jumpy?
|
|
82
|
+
# The red brown fox jumsp over the lazy dog.
|
|
83
|
+
^^^^^
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Each offense highlights the misspelled word and includes up to three suggestions when available.
|
|
87
|
+
|
|
88
|
+
Fix typos, then generate an initial `.spellcheck.txt` for remaining project-specific words:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
bundle exec rubocop --auto-gen-config --only Spellcheck/Spelling
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The command adds both the bundled English dictionary and `.spellcheck.txt` to your configuration when no dictionaries are configured. Review the generated vocabulary: remaining typos are accepted too. See [Generating project vocabulary](#generating-project-vocabulary) for regeneration behavior.
|
|
95
|
+
|
|
96
|
+
## Other languages
|
|
50
97
|
|
|
51
98
|
For another language, download a Hunspell dictionary and its license into your project. For example, download the [latest German resources](https://github.com/kotoshu/dictionaries/tree/v1/de/spelling):
|
|
52
99
|
|
|
@@ -66,49 +113,42 @@ Spellcheck:
|
|
|
66
113
|
- dictionaries/de/de.dic
|
|
67
114
|
```
|
|
68
115
|
|
|
69
|
-
The matching
|
|
116
|
+
The matching `.aff` file is required. Commit both files and their license, or provision them in CI. Review each dictionary's license before redistributing it; linting never downloads dictionaries.
|
|
70
117
|
|
|
71
|
-
##
|
|
118
|
+
## Supported files
|
|
72
119
|
|
|
73
|
-
|
|
120
|
+
### Ruby comments
|
|
74
121
|
|
|
75
|
-
|
|
76
|
-
# bad
|
|
77
|
-
# The red brown fox jumsp over the lazy dog.
|
|
122
|
+
Ruby comments, including trailing and block comments, are checked. Shebangs, magic comments, RuboCop/Standard directives, and Sorbet `typed:` declarations are skipped. URLs and email addresses are ignored.
|
|
78
123
|
|
|
79
|
-
|
|
80
|
-
# The red brown fox jumps over the lazy dog.
|
|
81
|
-
```
|
|
124
|
+
Comments are parsed as RDoc, excluding indented examples and inline code.
|
|
82
125
|
|
|
83
|
-
|
|
84
|
-
bundle exec rubocop
|
|
85
|
-
bundle exec rubocop --only Spellcheck/Spelling
|
|
86
|
-
```
|
|
126
|
+
### Ruby strings and identifiers
|
|
87
127
|
|
|
88
|
-
|
|
128
|
+
Strings include ordinary and percent literals, string arrays, heredocs, and literal text inside interpolation. Contents are checked literally, not as markup. Paths passed to `require`, `require_relative`, `load`, and `autoload`, plus `gem` names and version requirements, are excluded. Symbols, regular expression text, and shell-command text are also excluded; Ruby expressions inside interpolation are still checked. Ruby code is never evaluated.
|
|
89
129
|
|
|
90
|
-
|
|
91
|
-
# The red brown fox jumsp over the lazy dog.
|
|
92
|
-
```
|
|
130
|
+
Identifier checking is disabled by default. When enabled, every occurrence is checked, including method calls, definitions, constants, arguments, and variables. Names such as `hello_world`, `helloWorld`, and `HTTPClient` are split at underscores, numbers, and case boundaries. Add intentional API names and abbreviations to your project vocabulary.
|
|
93
131
|
|
|
94
|
-
|
|
132
|
+
Enable identifiers or disable strings independently:
|
|
95
133
|
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
|
|
134
|
+
```yaml
|
|
135
|
+
Spellcheck/Spelling:
|
|
136
|
+
Ruby:
|
|
137
|
+
Strings: false
|
|
138
|
+
Identifiers: true
|
|
139
|
+
```
|
|
99
140
|
|
|
100
|
-
|
|
141
|
+
Standard Ruby suppression directives apply to all three scopes. Identifier errors are report-only: renaming one occurrence could break references or external APIs. Strings support `rubocop -A`, but changing a string can change program behavior; review these edits carefully.
|
|
101
142
|
|
|
102
|
-
|
|
103
|
-
# The red brown fox jumsp over the lazy dog.
|
|
104
|
-
^^^^^
|
|
143
|
+
Explicitly tagged `ruby` Markdown fences use the same string and identifier settings, including inside lists and blockquotes. Untagged or indented code, other languages, comments inside fences, and invalid Ruby snippets remain excluded.
|
|
105
144
|
|
|
106
|
-
|
|
107
|
-
|
|
145
|
+
### RDoc
|
|
146
|
+
|
|
147
|
+
`.rdoc` files and Ruby comments use RDoc parsing. Headings, paragraphs, lists, and link labels are checked; code, link destinations, and documentation directives are skipped. Directives such as `:include:` are never executed.
|
|
108
148
|
|
|
109
|
-
|
|
149
|
+
### Markdown
|
|
110
150
|
|
|
111
|
-
The
|
|
151
|
+
The cop discovers `.md` and `.markdown` files and checks their prose:
|
|
112
152
|
|
|
113
153
|
```markdown
|
|
114
154
|
# The red brown fox jumsp over the lazy dog.
|
|
@@ -116,9 +156,11 @@ The same cop discovers `.md` and `.markdown` files and checks their prose:
|
|
|
116
156
|
Check [the spelling](https://example.com) but not `code_identifiers`.
|
|
117
157
|
```
|
|
118
158
|
|
|
119
|
-
|
|
159
|
+
Prose, headings, lists, tables, link labels, image descriptions, and footnotes are checked. Code other than explicitly tagged `ruby` fences, link destinations, HTML, Liquid expressions, URLs, and email addresses are skipped. No `rubocop-md` plugin is required.
|
|
160
|
+
|
|
161
|
+
### YAML front matter
|
|
120
162
|
|
|
121
|
-
|
|
163
|
+
String values are checked recursively, including multiline values and arrays. Keys, numbers, booleans, dates, and nulls are skipped. Layout names and slugs are strings too; use [front matter paths](#front-matter-paths) to select prose fields. Invalid YAML produces an error. To disable these checks:
|
|
122
164
|
|
|
123
165
|
```yaml
|
|
124
166
|
Spellcheck/Spelling:
|
|
@@ -127,14 +169,51 @@ Spellcheck/Spelling:
|
|
|
127
169
|
Enabled: false
|
|
128
170
|
```
|
|
129
171
|
|
|
130
|
-
|
|
172
|
+
### AsciiDoc
|
|
173
|
+
|
|
174
|
+
`.adoc` and `.asciidoc` files use [Asciidoctor](https://asciidoctor.org/) to check prose, headings, lists, tables, and link labels. Code, comments, attributes, link destinations, and macros are skipped. Includes and extensions are disabled; linting does not render documents or access external resources.
|
|
175
|
+
|
|
176
|
+
### Plain text
|
|
131
177
|
|
|
132
|
-
|
|
178
|
+
`.txt` files are checked literally, excluding URLs and email addresses. Configured dictionaries and bundled resources are excluded automatically. Use normal RuboCop exclusions for other files you do not want checked.
|
|
133
179
|
|
|
134
|
-
|
|
180
|
+
## Autocorrection
|
|
181
|
+
|
|
182
|
+
Dictionary suggestions may not match your intended meaning, so spelling corrections require `rubocop -A`; `rubocop -a` leaves spelling unchanged. To apply corrections, use:
|
|
183
|
+
|
|
184
|
+
```sh
|
|
185
|
+
bundle exec rubocop -A --only Spellcheck/Spelling
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Lowercase and title-case words whose first suggestion is at a distance of one edit are corrected: one insertion, deletion, substitution, or adjacent transposition. The replacement must pass the configured dictionaries. Other suggestions may be equally close; the first suggestion wins, preserving lowercase or title case. ALL CAPS, mixed-case words, and components of dotted or identifier-like tokens remain report-only. Corrections do not introduce multiple words or formatting characters. Title-case corrections can change people's names; review the resulting diff.
|
|
189
|
+
|
|
190
|
+
Corrections preserve surrounding markup and string delimiters; escaped letters may become literal letters. Unsafe replacements such as a heredoc terminator or a typed YAML value are withheld. Identifiers are never renamed.
|
|
135
191
|
|
|
136
192
|
## Configuration
|
|
137
193
|
|
|
194
|
+
### Front matter paths
|
|
195
|
+
|
|
196
|
+
By default, all front matter string values are checked. Use dotted paths under `Markdown.FrontMatter` to select which values participate:
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
Spellcheck/Spelling:
|
|
200
|
+
Markdown:
|
|
201
|
+
FrontMatter:
|
|
202
|
+
Enabled: true
|
|
203
|
+
Include:
|
|
204
|
+
- title
|
|
205
|
+
- description
|
|
206
|
+
- metadata
|
|
207
|
+
- authors.description
|
|
208
|
+
Exclude:
|
|
209
|
+
- metadata.slug
|
|
210
|
+
- metadata.internal
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Paths are case-sensitive, with dots separating nested keys. Selecting a parent includes its descendants; `authors.description` selects descriptions in every author mapping without array indices. Wildcards and literal dots in key names are not supported.
|
|
214
|
+
|
|
215
|
+
`Include: null` checks all paths; `Include: []` checks none. `Exclude` always takes precedence. These settings do not affect Markdown body prose.
|
|
216
|
+
|
|
138
217
|
### Dictionary files
|
|
139
218
|
|
|
140
219
|
`DictionaryFiles` maps language codes to lists of dictionary paths. When omitted or `null`, it selects packaged English, equivalent to:
|
|
@@ -146,13 +225,13 @@ Spellcheck:
|
|
|
146
225
|
- gem:rubocop-spellcheck/data/en/en.dic
|
|
147
226
|
```
|
|
148
227
|
|
|
149
|
-
An explicit map replaces the
|
|
228
|
+
An explicit map replaces the defaults: retain the English path if you still want English. A word passes if any configured dictionary accepts it; there is no automatic language detection.
|
|
150
229
|
|
|
151
|
-
|
|
230
|
+
`gem:<gem-name>/` paths resolve inside the installed gem. Other paths are relative to the effective RuboCop configuration directory. Missing dictionaries and invalid settings produce errors rather than falling back to defaults.
|
|
152
231
|
|
|
153
232
|
Hunspell `.dic` files load their matching `.aff` file in the same directory with the same basename. Plain `.txt` files contain literal words, one per line, matched case-insensitively without automatic inflection. Blank lines and lines starting with `#` are ignored; phrases are rejected.
|
|
154
233
|
|
|
155
|
-
|
|
234
|
+
Normal RuboCop inheritance and cop-specific overrides apply. Dictionary changes invalidate cached results; Kotoshu's personal dictionary is not used.
|
|
156
235
|
|
|
157
236
|
### Adding project vocabulary
|
|
158
237
|
|
|
@@ -172,9 +251,27 @@ Kotoshu
|
|
|
172
251
|
RuboCop
|
|
173
252
|
```
|
|
174
253
|
|
|
175
|
-
|
|
254
|
+
Words match case-insensitively; list plural and other forms explicitly. Blank lines and `#` comments are ignored. To use only your own word list, omit the gem path.
|
|
255
|
+
|
|
256
|
+
### Generating project vocabulary
|
|
176
257
|
|
|
177
|
-
|
|
258
|
+
Use the RuboCop configuration generator to build an initial vocabulary:
|
|
259
|
+
|
|
260
|
+
```sh
|
|
261
|
+
bundle exec rubocop --auto-gen-config
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Spelling offenses create or update `.spellcheck.txt`. To generate vocabulary without configuring other cops:
|
|
265
|
+
|
|
266
|
+
```sh
|
|
267
|
+
bundle exec rubocop --auto-gen-config --only Spellcheck/Spelling
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Every reported misspelling becomes accepted vocabulary, including genuine typos and words with correction suggestions. **Review the generated list**, remove mistakes, and fix them in the source. Generation does not correct source files.
|
|
271
|
+
|
|
272
|
+
Rerunning rebuilds `.spellcheck.txt` from current violations without accepting its existing words during checking. Unneeded entries are removed, including manually added ones. Comments and capitalization of retained words are preserved; new words are lowercase, sorted, and added without duplicates. The command adds the vocabulary to `DictionaryFiles` while retaining existing dictionaries, or bundled English when using defaults.
|
|
273
|
+
|
|
274
|
+
Vocabulary is written beside the effective configuration, including nested configurations. Exclusions and suppression still apply. **Run against the full intended file set:** words needed only in omitted files will be removed. Clean runs clear obsolete entries but do not create a new vocabulary file.
|
|
178
275
|
|
|
179
276
|
### Replacing dictionaries and adding languages
|
|
180
277
|
|
|
@@ -207,7 +304,17 @@ Spellcheck/Spelling:
|
|
|
207
304
|
# rubocop:enable Spellcheck/Spelling
|
|
208
305
|
```
|
|
209
306
|
|
|
210
|
-
Ruby
|
|
307
|
+
Ruby directives apply to comments, strings, and identifiers, including within tagged Ruby fences. In Markdown, use HTML comments:
|
|
308
|
+
|
|
309
|
+
```markdown
|
|
310
|
+
<!-- rubocop:disable Spellcheck/Spelling -->
|
|
311
|
+
Intentional project vocabulary.
|
|
312
|
+
<!-- rubocop:enable Spellcheck/Spelling -->
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
A trailing `disable` or any `todo` applies only to its own line. Directives accept `all`, cop lists, and reasons after ` -- `. HTML-comment directives inside code, Liquid, or front matter are ignored; Ruby directives apply within their own fence. Suppression prevents diagnostics and autocorrection.
|
|
316
|
+
|
|
317
|
+
To check only one format, use standard file inclusion:
|
|
211
318
|
|
|
212
319
|
```yaml
|
|
213
320
|
Spellcheck/Spelling:
|
|
@@ -217,9 +324,9 @@ Spellcheck/Spelling:
|
|
|
217
324
|
|
|
218
325
|
## Scope and limitations
|
|
219
326
|
|
|
220
|
-
|
|
327
|
+
English words retain accented letters and internal apostrophes; surrounding quotation marks and numeric identifiers such as commit hashes and ordinals are not checked.
|
|
221
328
|
|
|
222
|
-
|
|
329
|
+
Ruby comments use RDoc markup, not YARD or other documentation syntaxes. Other documentation tags are treated as text. Use a project dictionary or standard suppression for intentional non-prose. Word boundaries and spelling follow Kotoshu's selected language; this extension does not add language detection or grammar checking.
|
|
223
330
|
|
|
224
331
|
Kotoshu ships precompiled gems for macOS, Linux, and Windows. Building native dependencies from source on other platforms may require a Rust toolchain.
|
|
225
332
|
|
|
@@ -233,4 +340,4 @@ Copyright (c) 2026 Daniel (dB.) Doubrovkine. MIT License; see [LICENSE.txt](LICE
|
|
|
233
340
|
|
|
234
341
|
Kotoshu is a separate BSD-2-Clause dependency. The bundled SCOWL-derived American English dictionary carries its own copyright and redistribution terms; see [its full license](data/en/LICENSE) and [pinned provenance](data/en/README.md). Dictionary files and notices are included in the packaged gem.
|
|
235
342
|
|
|
236
|
-
|
|
343
|
+
Other dictionaries have their own licenses regardless of the dictionary repository's license. The German example is [`GPL-2.0 OR GPL-3.0`](https://github.com/kotoshu/dictionaries/blob/v1/de/spelling/license) and is not bundled. Review each dictionary's notices before redistributing it.
|
data/config/default.yml
CHANGED
|
@@ -7,11 +7,21 @@ AllCops:
|
|
|
7
7
|
- "**/*.md"
|
|
8
8
|
- "**/*.markdown"
|
|
9
9
|
- "**/*.txt"
|
|
10
|
+
- "**/*.rdoc"
|
|
11
|
+
- "**/*.adoc"
|
|
12
|
+
- "**/*.asciidoc"
|
|
10
13
|
|
|
11
14
|
Spellcheck/Spelling:
|
|
12
|
-
Description: Check spelling in Ruby comments,
|
|
15
|
+
Description: Check spelling in Ruby comments, strings, identifiers, and documentation.
|
|
13
16
|
Enabled: true
|
|
17
|
+
SafeAutoCorrect: false
|
|
18
|
+
Ruby:
|
|
19
|
+
Strings: true
|
|
20
|
+
Identifiers: false
|
|
14
21
|
Markdown:
|
|
15
22
|
FrontMatter:
|
|
16
23
|
Enabled: true
|
|
24
|
+
Include:
|
|
25
|
+
Exclude: []
|
|
17
26
|
VersionAdded: "0.1"
|
|
27
|
+
VersionChanged: "0.2"
|
|
@@ -3,25 +3,24 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module Spellcheck
|
|
6
|
-
# Checks Ruby
|
|
6
|
+
# Checks Ruby code and documentation with shared spelling dictionaries.
|
|
7
7
|
class Spelling < Base
|
|
8
|
+
extend AutoCorrector
|
|
9
|
+
|
|
8
10
|
BUNDLED_DATA = File.expand_path('../../../../data', __dir__).freeze
|
|
9
|
-
DIRECTIVE = /
|
|
10
|
-
\A\#\s*(?:!|(?:rubocop|standard|coding|encoding|frozen_string_literal|
|
|
11
|
-
warn_indent|shareable_constant_value|typed)\s*:)|\A\#.*-\*-\s*(?:coding|encoding):
|
|
12
|
-
/x
|
|
13
11
|
ADDRESS = %r{(?:https?://|www\.)[^\s<>]+|[\w.+-]+@[\w.-]+\.[a-zA-Z]{2,}}
|
|
14
12
|
|
|
13
|
+
def self.support_multiple_source?
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
|
|
15
17
|
def on_new_investigation
|
|
16
18
|
super
|
|
17
19
|
if RuboCop::Spellcheck::Markdown.file?(processed_source.path)
|
|
18
20
|
check_markdown
|
|
19
21
|
else
|
|
20
|
-
processed_source.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
check_comment(comment)
|
|
24
|
-
end
|
|
22
|
+
RuboCop::Spellcheck::RubyCode.new(processed_source, config: cop_config.fetch('Ruby', {}))
|
|
23
|
+
.each_span { |span| check_markdown_span(span) }
|
|
25
24
|
end
|
|
26
25
|
end
|
|
27
26
|
|
|
@@ -30,6 +29,8 @@ module RuboCop
|
|
|
30
29
|
check_text
|
|
31
30
|
elsif RuboCop::Spellcheck::Markdown.file?(processed_source.path)
|
|
32
31
|
check_markdown
|
|
32
|
+
elsif (extractor = prose_extractor)
|
|
33
|
+
extractor.new(prose_buffer).each_span { |span| check_markdown_span(span) }
|
|
33
34
|
else
|
|
34
35
|
super
|
|
35
36
|
end
|
|
@@ -47,23 +48,40 @@ module RuboCop
|
|
|
47
48
|
checker.checksum
|
|
48
49
|
end
|
|
49
50
|
|
|
51
|
+
def current_corrector
|
|
52
|
+
return super unless RuboCop::Spellcheck::ProseSource.file?(processed_source.path)
|
|
53
|
+
|
|
54
|
+
# Prose deliberately bypasses Ruby parsing, but its buffer can still be rewritten.
|
|
55
|
+
@current_corrector ||= Corrector.new(processed_source)
|
|
56
|
+
end
|
|
57
|
+
|
|
50
58
|
private
|
|
51
59
|
|
|
52
|
-
def
|
|
53
|
-
|
|
60
|
+
def prose_extractor
|
|
61
|
+
[RuboCop::Spellcheck::RDoc, RuboCop::Spellcheck::AsciiDoc].find do |extractor|
|
|
62
|
+
extractor.file?(processed_source.path)
|
|
63
|
+
end
|
|
54
64
|
end
|
|
55
65
|
|
|
56
66
|
def check_markdown
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
check_front_matter
|
|
68
|
+
markdown = RuboCop::Spellcheck::Markdown.new(prose_buffer)
|
|
69
|
+
markdown.each_span do |text, positions|
|
|
70
|
+
check_markdown_span(RuboCop::Spellcheck::Span.new(text, positions))
|
|
61
71
|
end
|
|
62
|
-
|
|
63
|
-
check_markdown_span(
|
|
72
|
+
markdown.each_ruby_span(processed_source.ruby_version, config: cop_config.fetch('Ruby', {})) do |span|
|
|
73
|
+
check_markdown_span(span)
|
|
64
74
|
end
|
|
65
75
|
end
|
|
66
76
|
|
|
77
|
+
def check_front_matter
|
|
78
|
+
front_matter = cop_config.fetch('Markdown', {}).fetch('FrontMatter', {})
|
|
79
|
+
return unless front_matter.fetch('Enabled', true)
|
|
80
|
+
|
|
81
|
+
RuboCop::Spellcheck::FrontMatter.new(prose_buffer, config: front_matter)
|
|
82
|
+
.each_span { |span| check_markdown_span(span) }
|
|
83
|
+
end
|
|
84
|
+
|
|
67
85
|
def check_text
|
|
68
86
|
checker.misspellings(mask_addresses(processed_source.buffer.source)).each do |word, position|
|
|
69
87
|
next unless word.match?(/\p{L}/)
|
|
@@ -73,32 +91,31 @@ module RuboCop
|
|
|
73
91
|
end
|
|
74
92
|
|
|
75
93
|
def checker
|
|
76
|
-
@checker ||=
|
|
94
|
+
@checker ||= spelling_checker.new(
|
|
77
95
|
dictionary_files: cop_config['DictionaryFiles'],
|
|
78
96
|
base_directory: config.base_dir_for_path_parameters
|
|
79
97
|
)
|
|
80
98
|
end
|
|
81
99
|
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
first = comment.source_range.begin_pos + position
|
|
85
|
-
report_word(word, first, first + word.length)
|
|
86
|
-
end
|
|
100
|
+
def spelling_checker
|
|
101
|
+
@options[:auto_gen_config] ? RuboCop::Spellcheck::Vocabulary::Checker : RuboCop::Spellcheck::Checker
|
|
87
102
|
end
|
|
88
103
|
|
|
89
|
-
def check_markdown_span(
|
|
90
|
-
text = text.sub(/\A['’]s\b/, ' ')
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
report_word(word, positions.fetch(position).first, positions.fetch(position + word.length - 1).last)
|
|
104
|
+
def check_markdown_span(span)
|
|
105
|
+
text = mask_addresses(span.text.sub(/\A['’]s\b/, ' '))
|
|
106
|
+
span.each_misspelling(checker, text) do |word, first, last, correction, whole|
|
|
107
|
+
report_word(word, first, last, correction: correction, correctable: whole)
|
|
95
108
|
end
|
|
96
109
|
end
|
|
97
110
|
|
|
98
|
-
def report_word(word, first, last)
|
|
111
|
+
def report_word(word, first, last, correction: nil, correctable: true)
|
|
99
112
|
range = Parser::Source::Range.new(prose_buffer, first, last)
|
|
100
113
|
range = markdown_document.processed_range(range) if markdown_document
|
|
101
|
-
|
|
114
|
+
correction ||= RuboCop::Spellcheck::Correction.new(word, checker.suggestions(word))
|
|
115
|
+
replacement = correction.replacement_in(prose_buffer.source, first, last, checker: checker)
|
|
116
|
+
add_offense(range, message: correction.message) do |corrector|
|
|
117
|
+
corrector.replace(range, replacement) if replacement && correctable
|
|
118
|
+
end
|
|
102
119
|
end
|
|
103
120
|
|
|
104
121
|
def markdown_document
|
|
@@ -109,22 +126,9 @@ module RuboCop
|
|
|
109
126
|
markdown_document ? markdown_document.buffer : processed_source.buffer
|
|
110
127
|
end
|
|
111
128
|
|
|
112
|
-
def prose(text)
|
|
113
|
-
# Preserve character offsets while removing non-prose addresses and block delimiters.
|
|
114
|
-
mask_addresses(text).gsub(/^=(?:begin|end)\b[^\n]*/) { |marker| ' ' * marker.length }
|
|
115
|
-
end
|
|
116
|
-
|
|
117
129
|
def mask_addresses(text)
|
|
118
130
|
text.gsub(ADDRESS) { |address| ' ' * address.length }
|
|
119
131
|
end
|
|
120
|
-
|
|
121
|
-
def spelling_message(word)
|
|
122
|
-
suggestions = checker.suggestions(word)
|
|
123
|
-
message = "Misspelled word: `#{word}`."
|
|
124
|
-
return message if suggestions.empty?
|
|
125
|
-
|
|
126
|
-
"#{message} Did you mean #{suggestions.map { |suggestion| "`#{suggestion}`" }.join(', ')}?"
|
|
127
|
-
end
|
|
128
132
|
end
|
|
129
133
|
end
|
|
130
134
|
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'asciidoctor'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Spellcheck
|
|
7
|
+
# Extracts original prose selected by the Asciidoctor block parser in secure mode.
|
|
8
|
+
class AsciiDoc
|
|
9
|
+
PROSE = %i[paragraph admonition verse list_item table_cell].freeze
|
|
10
|
+
SKIP = %i[listing literal pass stem comment].freeze
|
|
11
|
+
|
|
12
|
+
def self.file?(path)
|
|
13
|
+
path && %w[.adoc .asciidoc].include?(File.extname(path).downcase)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(buffer)
|
|
17
|
+
@buffer = buffer
|
|
18
|
+
@lines = buffer.source.lines
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def each_span(&)
|
|
22
|
+
return enum_for(__method__) unless block_given?
|
|
23
|
+
|
|
24
|
+
@columns = Hash.new(0)
|
|
25
|
+
@tables = {}
|
|
26
|
+
source = @buffer.source.gsub(/^include::.*$/) { |line| ' ' * line.length }
|
|
27
|
+
document = ::Asciidoctor.load(source, safe: :secure, sourcemap: true, extensions: false)
|
|
28
|
+
spans(document, &)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def spans(document, &block)
|
|
34
|
+
document.find_by.each do |node|
|
|
35
|
+
next if excluded?(node) || !node.source_location
|
|
36
|
+
|
|
37
|
+
if node.context == :table_cell
|
|
38
|
+
table_spans(node, &block)
|
|
39
|
+
else
|
|
40
|
+
fragments(node).each { |text, line| yield mapped_span(text, line) }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def table_spans(node, &)
|
|
46
|
+
table = node.parent.parent
|
|
47
|
+
span = (@tables[table] ||= AsciiDocTable.new(@buffer, table)).span(node)
|
|
48
|
+
return if node.style == :literal
|
|
49
|
+
return spans(node.inner_document, &) if node.style == :asciidoc
|
|
50
|
+
|
|
51
|
+
yield span
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def excluded?(node)
|
|
55
|
+
return false if node.context == :document
|
|
56
|
+
return true if SKIP.include?(node.context)
|
|
57
|
+
|
|
58
|
+
node.parent && excluded?(node.parent)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def fragments(node)
|
|
62
|
+
line = node.source_location.lineno
|
|
63
|
+
return [] if node.context == :document
|
|
64
|
+
return heading(line) if node.context == :section
|
|
65
|
+
|
|
66
|
+
titles(node, line) + prose(node, line)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def heading(line)
|
|
70
|
+
source = @lines.fetch(line - 1)
|
|
71
|
+
title = source[/\A={1,6} (.*)/, 1]
|
|
72
|
+
title ||= source.chomp if @lines[line]&.match?(/\A[=\-~^+]{2,}\s*\z/)
|
|
73
|
+
title ? [[title, line]] : []
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def titles(node, line)
|
|
77
|
+
return [] unless node.title?
|
|
78
|
+
|
|
79
|
+
index = line - 2
|
|
80
|
+
index -= 1 while index >= 0 && @lines[index].start_with?('[')
|
|
81
|
+
text = @lines[index] if index >= 0
|
|
82
|
+
text&.start_with?('.') ? [[text.chomp.delete_prefix('.'), index + 1]] : []
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def prose(node, line)
|
|
86
|
+
return [] unless PROSE.include?(node.context)
|
|
87
|
+
|
|
88
|
+
if node.context == :list_item
|
|
89
|
+
node.subs.clear
|
|
90
|
+
text = node.text
|
|
91
|
+
else
|
|
92
|
+
text = node.source
|
|
93
|
+
end
|
|
94
|
+
text ? [[text, line]] : []
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def mapped_span(text, line)
|
|
98
|
+
positions = text.lines.flat_map do |part|
|
|
99
|
+
line += 1 while @lines.fetch(line - 1).start_with?('//')
|
|
100
|
+
mapped = line_positions(part, line)
|
|
101
|
+
line += 1
|
|
102
|
+
mapped
|
|
103
|
+
end
|
|
104
|
+
AsciiDocInline.new(text, positions).span
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def line_positions(part, line)
|
|
108
|
+
column = column_for(part, line)
|
|
109
|
+
first = @buffer.line_range(line).begin_pos + column
|
|
110
|
+
@columns[line] = column + part.chomp.length
|
|
111
|
+
part.length.times.map { |index| [first + index, first + index + 1] }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def column_for(part, line)
|
|
115
|
+
@lines.fetch(line - 1).index(part.chomp, @columns[line]) ||
|
|
116
|
+
raise(RuboCop::ValidationError, "Cannot map AsciiDoc prose in #{@buffer.name}:#{line}: #{part.inspect}")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|