NAME
Test::Spelling::Stopwords - POD spell-checking with project-specific stopwords
VERSION
Version 0.02
SYNOPSIS
Minimal - just drop this into your xt/ directory:
# xt/spell-pod.t
use Test::More;
use Test::Spelling::Stopwords;
unless ($ENV{AUTHOR_TESTING} || $ENV{RELEASE_TESTING} || $ENV{CI}) {
plan skip_all => 'Spelling tests only run under AUTHOR_TESTING';
}
all_pod_files_spelling_ok();
Or with explicit configuration:
use Test::Spelling::Stopwords;
set_spell_lang('en_US');
set_stopwords_file('xt/.stopwords');
set_spell_dirs('lib', 'bin');
all_pod_files_spelling_ok();
Or with per-call overrides:
all_pod_files_spelling_ok(
lang => 'en_US',
stopwords_file => 'xt/.stopwords',
dirs => ['lib', 'bin'],
);
Check a single file:
use Test::Spelling::Stopwords;
use Test::More tests => 1;
pod_file_spelling_ok('lib/My/Module.pm');
DESCRIPTION
Test::Spelling::Stopwords is a drop-in POD spell-checker that integrates project-specific stopword files with aspell. It is designed to work alongside the companion gen-stopwords script, which auto-generates a .stopwords file containing only the vocabulary unique to your project - after filtering out the common Perl ecosystem terms already covered by Pod::Wordlist.
How it differs from Test::Spelling
Test::Spelling is the established CPAN module for POD spell-checking. Test::Spelling::Stopwords does not replace it - it complements it by addressing two specific gaps:
- Automatic stopwords file discovery
-
Test::Spelling requires you to call
add_stopwords()explicitly or maintain a__DATA__section in your test.Test::Spelling::Stopwordsautomatically discovers and loads a .stopwords file from your project root (or any path you configure), so your test file contains no project-specific content and can be reused across projects unchanged. - Line-number reporting
-
When Test::Spelling finds a misspelled word it tells you the word but not where it is.
Test::Spelling::Stopwordsreports the exact line number(s) in the source file where each misspelling appears, making failures fast to locate and fix.
Two-layer stopword architecture
The module merges two sources of known words before checking any file:
- Layer 1 - Pod::Wordlist
-
The CPAN-maintained vocabulary of common Perl and technical terms (
ok,undef,dbi,CPAN,accessor,mutators, etc.). This mirrors whatgen-stopwordsfilters out when building .stopwords, so the module and the generator always agree on what counts as a known word.Without this layer the test is stricter than the generator and flags words that Pod::Wordlist covers - causing false failures even on a freshly generated .stopwords.
- Layer 2 - .stopwords
-
Project-specific vocabulary generated by
gen-stopwords. Contains only terms not already covered by Pod::Wordlist.
Stopwords file format
The .stopwords file is a plain text file with one word per line. Lines beginning with # and blank lines are ignored.
# Auto-generated stopwords for en_GB
dbic
mojolicious
resultset
myauthor
Generate it with the companion gen-stopwords script:
gen-stopwords --dir lib --dir bin
Freshness check
On every run, Test::Spelling::Stopwords compares the modification time of your .stopwords file against your source files. If any source file is newer, it emits a diag warning:
# ------------------------------------------------------------
# WARNING: .stopwords is out of date!
# Run gen-stopwords to regenerate it.
# ------------------------------------------------------------
This is advisory only - the test continues to run.
POD cleaning
Before passing each line to aspell, all POD formatting codes are stripped entirely:
E<gt> removed (not 'gt', preventing the 'Egt' artefact)
L<Some::Module> removed
C<code> removed
B<bold> removed
This is more aggressive than simple content extraction and prevents a class of false positives caused by POD entity fragments appearing as bare words.
Environment variables
All defaults can be overridden without editing the test file:
SPELL_LANG-
Aspell language code. Default:
en_GB. STOPWORD_FILE-
Path to the stopwords file. Default:
.stopwords. SPELL_DIRS-
Colon- or comma-separated list of directories to scan. Default:
lib:bin:script. ASPELL_CMD-
Complete aspell command string, including all flags. Default:
aspell list -l $LANG --run-together.
CONFIGURATION API
set_spell_lang
set_spell_lang('en_US');
Sets the aspell language code. May also be set via the SPELL_LANG environment variable.
set_stopwords_file
set_stopwords_file('xt/.stopwords');
Sets the path to the stopwords file. May also be set via the STOPWORD_FILE environment variable.
set_spell_dirs
set_spell_dirs('lib', 'bin', 'script');
set_spell_dirs( ['lib', 'bin'] );
Sets the list of directories to search for POD files. Accepts either a list or an arrayref. May also be set via the SPELL_DIRS environment variable.
get_stopwords_file
my $path = get_stopwords_file();
Returns the currently configured stopwords file path.
EXPORTED FUNCTIONS
all_pod_files_spelling_ok
all_pod_files_spelling_ok();
all_pod_files_spelling_ok(
lang => 'en_US',
stopwords_file => 'xt/.stopwords',
dirs => ['lib', 'bin'],
);
Finds all Perl source files (.pm, .pl, .pod, .t) under the configured source directories, and runs a spell-check on the POD in each one. Emits one TAP pass/fail per file.
Misspelled words are reported via diag with their line numbers:
not ok 1 - POD spelling: lib/My/Module.pm
# 'serialiisable' line(s): 42
# 'Egtconnect' line(s): 17, 83
Accepts an optional hash of per-call overrides (lang, stopwords_file, dirs) that take precedence over the module-level configuration for the duration of the call.
Skips gracefully (via skip_all) if:
aspell is not installed or not on
$PATHThe stopwords file does not exist
No POD files are found in the configured directories
pod_file_spelling_ok
pod_file_spelling_ok($file);
pod_file_spelling_ok($file, \%stopwords);
pod_file_spelling_ok($file, \%stopwords, $test_name);
Spell-checks the POD in a single file. Emits one pass or fail.
If \%stopwords is omitted the configured stopwords file is loaded automatically. $test_name defaults to "POD spelling: $file".
Returns true if the file passes, false otherwise.
RECOMMENDED WORKFLOW
Install dependencies:
cpanm -vS Test::Spelling::StopwordsThis also installs the companion
gen-stopwordsscript.Generate your project's stopwords file:
gen-stopwords --dir lib --dir binThis scans your source files, runs aspell, filters out terms already covered by Pod::Wordlist, and writes a lean .stopwords containing only project-specific vocabulary.
Create xt/spell-pod.t:
use Test::More; use Test::Spelling::Stopwords; unless ($ENV{AUTHOR_TESTING} || $ENV{RELEASE_TESTING} || $ENV{CI}) { plan skip_all => 'Spelling tests only run under AUTHOR_TESTING'; } all_pod_files_spelling_ok();Run:
AUTHOR_TESTING=1 prove -lv xt/spell-pod.tAfter adding or editing source files, regenerate:
gen-stopwordsThe test will warn you if you forget.
DEPENDENCIES
Test::Builder (core via Test::More)
File::Find (core)
File::Spec (core)
Cwd (core)
Pod::Wordlist (strongly recommended -
cpanm Pod::Wordlist)aspell - must be installed on the system and available on
$PATH
BUGS AND LIMITATIONS
aspell must be installed externally. The module skips gracefully if it is absent but cannot install it for you.
The shell pipe to aspell (via backticks) means Windows is not currently supported. Patches welcome.
The freshness check uses file modification times, which are reset by
git checkoutand similar operations. It is advisory only.
SEE ALSO
Test::Spelling - the established base module this complements
Pod::Wordlist - the community Perl vocabulary list
Pod::Spell - POD-aware text extraction for spell-checking
gen-stopwords - companion script for generating .stopwords
AUTHOR
Mohammad Sajid Anwar <mohammad.anwar@yahoo.com>
REPOSITORY
https://github.com/manwar/Test-Spelling-Stopwords
BUGS
Please report any bugs or feature requests through the web interface at https://github.com/manwar/Test-Spelling-Stopwords/issues. I will be notified and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Test::Spelling::Stopwords
You can also look for information at:
Bug Report
CPAN Ratings
Search MetaCPAN
LICENSE AND COPYRIGHT
Copyright (C) 2026 Mohammad Sajid Anwar.
This program is free software; you can redistribute it and / or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: http://www.perlfoundation.org/artistic_license_2_0 Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License.By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. If your Modified Version has been derived from a Modified Version made by someone other than you,you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement,then this Artistic License to you shall terminate on the date that such litigation is filed. Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.