Skip to content

Commit d16e0a2

Browse files
jeremyfeltclaude
andcommitted
Clean up lint and adopt project JSDoc style
npm run lint now exits with zero problems, and the style points that were previously just convention are enforced by config: - no-console allows log/warn/error in bin/ and src/, where console output is the CLI's user interface. Templates ship to the browser and keep the shared rule. - JSDoc params drop the hyphen before descriptions (require-hyphen-before-param-description: never) and align types, names, and descriptions in columns (check-line-alignment, params only — a long @returns type would drag every description far right). - The three missing JSDoc descriptions are filled in. Also replaces the remaining vendor-specific hostnames in docs, error hints, and tests with reserved example domains (captcha.example.com, cdn.example.net). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 4b79935 commit d16e0a2

16 files changed

Lines changed: 147 additions & 118 deletions

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,16 @@ self-ignored `.reglance/` directory — nothing to add to `.gitignore`.
5353
| `output` | no | Output directory. Defaults to `.reglance`. |
5454
| `pixelmatchOptions` | no | [pixelmatch](https://github.com/mapbox/pixelmatch) options, e.g. `{ "threshold": 0.1 }`. |
5555
| `timeouts` | no | `{ goto, settle }` in ms. `goto` bounds navigation; `settle` bounds each post-scroll wait (network idle, then image load/decode). Defaults `{ goto: 15000, settle: 8000 }`. Raise `settle` for slow, lazy-loading pages. |
56-
| `blockHosts` | no | Hostnames to block requests to during capture, e.g. `["challenges.cloudflare.com"]`. Each entry also blocks its subdomains. |
56+
| `blockHosts` | no | Hostnames to block requests to during capture, e.g. `["captcha.example.com"]`. Each entry also blocks its subdomains. |
5757

5858
`domain` is only needed by `capture`; `control` and `compare` work on the files
5959
already captured. See [`reglance.example.json`](reglance.example.json) for a full
6060
example.
6161

6262
`blockHosts` aborts every request to the listed hosts (and their subdomains)
6363
before it leaves the browser. Use it for third-party embeds that keep the
64-
network busy and stall capture — CAPTCHA widgets like Cloudflare Turnstile,
65-
ad tech, analytics — or that render differently on every load and pollute
66-
diffs. Captures wait for the network to go idle, so a widget that polls or
64+
network busy and stall capture — CAPTCHA widgets, ad tech, analytics — or
65+
that render differently on every load and pollute diffs. Captures wait for the network to go idle, so a widget that polls or
6766
retries indefinitely will otherwise time out every viewport on pages that
6867
embed it. Entries are bare hostnames; `"example.org"` blocks `example.org` and
6968
`sub.example.org` alike.

eslint.config.mjs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
11
import config from '@happyprime/eslint-config';
22

3-
export default [...config];
3+
export default [
4+
...config,
5+
{
6+
// reglance is a CLI: console.log/warn ARE its user-facing output, so
7+
// only the noisier debugging methods (debug, info, table, …) stay
8+
// flagged. Templates ship to the browser and keep the shared rule.
9+
files: ['bin/**/*.mjs', 'src/**/*.mjs'],
10+
rules: {
11+
'no-console': ['warn', { allow: ['log', 'warn', 'error'] }],
12+
},
13+
},
14+
{
15+
// Project style: no hyphen between a JSDoc param name and its
16+
// description, with types, names, and descriptions aligned in columns.
17+
rules: {
18+
'jsdoc/require-hyphen-before-param-description': ['warn', 'never'],
19+
// Align params with each other only — a long @returns type would
20+
// otherwise drag every description far to the right.
21+
'jsdoc/check-line-alignment': [
22+
'warn',
23+
'always',
24+
{ tags: ['param', 'property'] },
25+
],
26+
},
27+
},
28+
];

reglance.example.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@
2121
"goto": 15000,
2222
"settle": 8000
2323
},
24-
"blockHosts": ["challenges.cloudflare.com"]
24+
"blockHosts": ["captcha.example.com"]
2525
}

src/args.mjs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
* instead of silently truncated, and enforces a minimum so a value like
66
* `--concurrency=0` cannot reach the capture loop and hang it.
77
*
8-
* @param {string} value - The raw flag value.
9-
* @param {string} name - The flag name, for error messages.
10-
* @param {object} [options] - Parse options.
11-
* @param {number} [options.min] - The smallest allowed value (default 1).
8+
* @param {string} value The raw flag value.
9+
* @param {string} name The flag name, for error messages.
10+
* @param {object} [options] Parse options.
11+
* @param {number} [options.min] The smallest allowed value (default 1).
1212
* @returns {number} The parsed integer.
1313
*/
1414
export function toInt(value, name, { min = 1 } = {}) {

src/capture.mjs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { filterTargets, DEFAULT_TIMEOUTS } from './config.mjs';
77
* Whether a host is a local development host, for which self-signed/invalid
88
* TLS certificates are expected and certificate errors are ignored by default.
99
*
10-
* @param {string|null} host - The hostname (no port).
10+
* @param {string|null} host The hostname (no port).
1111
* @returns {boolean} True for localhost/loopback and .test/.local(.localhost) hosts.
1212
*/
1313
export function isLocalHost(host) {
@@ -34,8 +34,8 @@ export function isLocalHost(host) {
3434
* surfacing it makes off-domain (and potentially internal-network) capture a
3535
* conscious choice rather than a silent one.
3636
*
37-
* @param {Array} targets - The targets to inspect.
38-
* @param {string|null} domain - The configured domain origin.
37+
* @param {Array} targets The targets to inspect.
38+
* @param {string|null} domain The configured domain origin.
3939
* @returns {Array} Targets pointing at a different host.
4040
*/
4141
export function offDomainTargets(targets, domain) {
@@ -66,8 +66,8 @@ export function offDomainTargets(targets, domain) {
6666
* "example.org" blocks both "example.org" and "sub.example.org". Non-network
6767
* URLs (blob:, data:, chrome-extension:) have no hostname and never match.
6868
*
69-
* @param {string} url - The request URL.
70-
* @param {string[]} blockHosts - Normalized (lowercase) hostnames.
69+
* @param {string} url The request URL.
70+
* @param {string[]} blockHosts Normalized (lowercase) hostnames.
7171
* @returns {boolean} True when the request should be blocked.
7272
*/
7373
export function isBlockedHost(url, blockHosts) {
@@ -102,7 +102,7 @@ export function isBlockedHost(url, blockHosts) {
102102
* timing race. The height is re-read every step so content that loads and
103103
* grows the page extends the walk.
104104
*
105-
* @param {import('playwright').Page} page - The page to scroll.
105+
* @param {import('playwright').Page} page The page to scroll.
106106
*/
107107
async function autoScroll(page) {
108108
await page.evaluate(async () => {
@@ -137,8 +137,8 @@ async function autoScroll(page) {
137137
* a broken image rejects, which counts as settled — a missing image is the
138138
* page's actual state, not something to keep waiting on.
139139
*
140-
* @param {import('playwright').Page} page - The page to wait on.
141-
* @param {number} timeout - Max wait in ms.
140+
* @param {import('playwright').Page} page The page to wait on.
141+
* @param {number} timeout Max wait in ms.
142142
* @returns {Promise<number>} How many images were still loading at timeout.
143143
*/
144144
async function waitForImages(page, timeout) {
@@ -171,7 +171,7 @@ async function waitForImages(page, timeout) {
171171
* per viewport while still letting setViewportSize switch sizes within a group.
172172
* A viewport without an explicit deviceScaleFactor defaults to 1.
173173
*
174-
* @param {Array} viewports - Viewport definitions.
174+
* @param {Array} viewports Viewport definitions.
175175
* @returns {Array<{ deviceScaleFactor: number, viewports: Array }>} Groups in
176176
* first-seen DPR order.
177177
*/
@@ -378,13 +378,13 @@ export function shouldFailRun(failures, failOnDegraded = false) {
378378
/**
379379
* Capture screenshots for every configured target.
380380
*
381-
* @param {object} config The normalized config.
382-
* @param {object} [options] Capture options.
383-
* @param {number} [options.concurrency] Parallel browser contexts.
384-
* @param {number} [options.staggerDelay] Delay (ms) between context starts.
385-
* @param {boolean}[options.skipReload] Reuse the page between viewports.
386-
* @param {boolean}[options.failOnDegraded] Exit non-zero if any capture is degraded.
387-
* @param {Array} [options.only] Limit to these target keys.
381+
* @param {object} config The normalized config.
382+
* @param {object} [options] Capture options.
383+
* @param {number} [options.concurrency] Parallel browser contexts.
384+
* @param {number} [options.staggerDelay] Delay (ms) between context starts.
385+
* @param {boolean} [options.skipReload] Reuse the page between viewports.
386+
* @param {boolean} [options.failOnDegraded] Exit non-zero if any capture is degraded.
387+
* @param {Array} [options.only] Limit to these target keys.
388388
* @returns {Promise<{ failures: Array }>} The degraded-slug records.
389389
*/
390390
export async function capture(config, options = {}) {

src/compare.mjs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ const HTML_DIFF_MAX_BYTES = 2 * 1024 * 1024;
2424
* Write a comparison artifact, surfacing the path and error code on failure
2525
* (e.g. a full disk or a read-only output dir) instead of an opaque stack.
2626
*
27-
* @param {string} file - The destination path.
28-
* @param {string|Buffer} data - The contents to write.
27+
* @param {string} file The destination path.
28+
* @param {string|Buffer} data The contents to write.
2929
*/
3030
function writeArtifact(file, data) {
3131
try {
@@ -43,9 +43,9 @@ function writeArtifact(file, data) {
4343
* Returns the image unchanged when it already matches the target dimensions,
4444
* avoiding a needless full-buffer copy.
4545
*
46-
* @param {PNG} img - The image to pad.
47-
* @param {number} targetWidth - The desired width.
48-
* @param {number} targetHeight - The desired height.
46+
* @param {PNG} img The image to pad.
47+
* @param {number} targetWidth The desired width.
48+
* @param {number} targetHeight The desired height.
4949
* @returns {PNG} The padded image (or the original when no padding is needed).
5050
*/
5151
export function padImage(img, targetWidth, targetHeight) {
@@ -61,9 +61,9 @@ export function padImage(img, targetWidth, targetHeight) {
6161
/**
6262
* Compare a single control/capture pair for one target and viewport.
6363
*
64-
* @param {object} config - The normalized config.
65-
* @param {object} target - The target ({ key, url }).
66-
* @param {object} viewport - The viewport definition.
64+
* @param {object} config The normalized config.
65+
* @param {object} target The target ({ key, url }).
66+
* @param {object} viewport The viewport definition.
6767
* @returns {object|null} The comparison result, or null when it can't run.
6868
*/
6969
export function compareSlug(config, target, viewport) {
@@ -172,8 +172,8 @@ export function compareSlug(config, target, viewport) {
172172
* Warn when the controls being compared were promoted across more than one
173173
* `control` run, which means the baseline mixes captures from different times.
174174
*
175-
* @param {object} config - The normalized config.
176-
* @param {Array} targets - The targets being compared.
175+
* @param {object} config The normalized config.
176+
* @param {Array} targets The targets being compared.
177177
*/
178178
function warnOnStaleControls(config, targets) {
179179
const slugs = targets.flatMap((target) =>
@@ -202,9 +202,9 @@ function warnOnStaleControls(config, targets) {
202202
* size (each in-flight comparison holds large RGBA buffers), which is why the
203203
* concurrency is capped and configurable.
204204
*
205-
* @param {object} config - The normalized config.
206-
* @param {Array} jobs - `{ target, viewport }` work items.
207-
* @param {number} concurrency - Maximum workers to run at once.
205+
* @param {object} config The normalized config.
206+
* @param {Array} jobs `{ target, viewport }` work items.
207+
* @param {number} concurrency Maximum workers to run at once.
208208
* @returns {Promise<Array>} The comparison reports (in completion order).
209209
*/
210210
async function runComparisons(config, jobs, concurrency) {
@@ -265,11 +265,11 @@ async function runComparisons(config, jobs, concurrency) {
265265
/**
266266
* Compare every captured target against its control and build the report.
267267
*
268-
* @param {object} config - The normalized config.
269-
* @param {object} [options] - Compare options.
270-
* @param {Array} [options.only] - Limit to these target keys.
271-
* @param {boolean} [options.open] - Open the report when finished.
272-
* @param {number} [options.concurrency] - Parallel diff workers.
268+
* @param {object} config The normalized config.
269+
* @param {object} [options] Compare options.
270+
* @param {Array} [options.only] Limit to these target keys.
271+
* @param {boolean} [options.open] Open the report when finished.
272+
* @param {number} [options.concurrency] Parallel diff workers.
273273
*/
274274
export async function compare(config, options = {}) {
275275
const { dirs } = config;

src/config.mjs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const DEFAULT_TIMEOUTS = { goto: 15000, settle: 8000 };
3636
* ("https://site.test"), and trailing slashes, returning a consistent
3737
* origin like "https://site.test".
3838
*
39-
* @param {string} domain - The domain to normalize.
39+
* @param {string} domain The domain to normalize.
4040
* @returns {string} The normalized origin.
4141
*/
4242
export function normalizeDomain(domain) {
@@ -77,8 +77,8 @@ const SAFE_SLUG_PART = /^[a-z0-9][a-z0-9._-]*$/i;
7777
/**
7878
* Assert a value is safe to use as part of an output filename.
7979
*
80-
* @param {string} value - The value to check.
81-
* @param {string} kind - A label for the error message (e.g. "path key").
80+
* @param {string} value The value to check.
81+
* @param {string} kind A label for the error message (e.g. "path key").
8282
*/
8383
function assertSafeSlugPart(value, kind) {
8484
if (
@@ -101,8 +101,8 @@ function assertSafeSlugPart(value, kind) {
101101
* a `.join` TypeError at the very end of a compare run, and a crafted array
102102
* would be interpolated into the report's CSS.
103103
*
104-
* @param {*} color - The configured color value.
105-
* @param {string} name - The option name, for the error message.
104+
* @param {*} color The configured color value.
105+
* @param {string} name The option name, for the error message.
106106
*/
107107
function validateColor(color, name) {
108108
const valid =
@@ -125,7 +125,7 @@ function validateColor(color, name) {
125125
* setViewportSize() and fails mid-capture with an opaque error, so it is
126126
* cheaper to catch it up front with an actionable message.
127127
*
128-
* @param {Array} viewports - The viewport definitions to validate.
128+
* @param {Array} viewports The viewport definitions to validate.
129129
*/
130130
export function validateViewports(viewports) {
131131
if (!Array.isArray(viewports) || viewports.length === 0) {
@@ -192,7 +192,7 @@ export function validateViewports(viewports) {
192192
* Anything with a scheme, path, or port is rejected up front — silently
193193
* matching nothing would read as "the block isn't working".
194194
*
195-
* @param {*} blockHosts - The raw config value.
195+
* @param {*} blockHosts The raw config value.
196196
* @returns {string[]} Lowercased hostnames with any `*.` prefix removed.
197197
*/
198198
export function normalizeBlockHosts(blockHosts) {
@@ -203,15 +203,15 @@ export function normalizeBlockHosts(blockHosts) {
203203
if (!Array.isArray(blockHosts)) {
204204
throw new Error(
205205
'❌ Invalid "blockHosts": expected an array of hostnames.\n' +
206-
'💡 Use entries like ["challenges.cloudflare.com"].'
206+
'💡 Use entries like ["captcha.example.com"].'
207207
);
208208
}
209209

210210
return blockHosts.map((entry) => {
211211
if (typeof entry !== 'string' || !entry.trim()) {
212212
throw new Error(
213213
`❌ Invalid "blockHosts" entry ${JSON.stringify(entry)}: expected a non-empty string.\n` +
214-
'💡 Use a bare hostname like "challenges.cloudflare.com".'
214+
'💡 Use a bare hostname like "captcha.example.com".'
215215
);
216216
}
217217

@@ -220,7 +220,7 @@ export function normalizeBlockHosts(blockHosts) {
220220
if (/[/:\s]/.test(host)) {
221221
throw new Error(
222222
`❌ Invalid "blockHosts" entry ${JSON.stringify(entry)}: expected a bare hostname (no scheme, port, or path).\n` +
223-
'💡 Use "challenges.cloudflare.com", not "https://challenges.cloudflare.com/".'
223+
'💡 Use "captcha.example.com", not "https://captcha.example.com/".'
224224
);
225225
}
226226

@@ -234,8 +234,8 @@ export function normalizeBlockHosts(blockHosts) {
234234
* A path that is already an absolute URL is returned untouched so that a
235235
* config can point individual entries at a different domain.
236236
*
237-
* @param {string} domain - The normalized domain origin.
238-
* @param {string} pathname - The path or absolute URL.
237+
* @param {string} domain The normalized domain origin.
238+
* @param {string} pathname The path or absolute URL.
239239
* @returns {string} The full URL.
240240
*/
241241
export function buildUrl(domain, pathname) {
@@ -253,8 +253,8 @@ export function buildUrl(domain, pathname) {
253253
* `control`/`compare` surfaces an actionable error instead of silently
254254
* doing nothing and printing a success-looking summary.
255255
*
256-
* @param {Array} targets - The configured targets.
257-
* @param {string[]} [only] - Path keys to keep. Falsy/empty keeps all.
256+
* @param {Array} targets The configured targets.
257+
* @param {string[]} [only] Path keys to keep. Falsy/empty keeps all.
258258
* @returns {Array} The filtered targets.
259259
*/
260260
export function filterTargets(targets, only) {
@@ -277,9 +277,9 @@ export function filterTargets(targets, only) {
277277
/**
278278
* Load and normalize a reglance config file.
279279
*
280-
* @param {object} [options] - Loader options.
281-
* @param {string} [options.configPath] - Path to the config file.
282-
* @param {string} [options.domain] - Domain override (e.g. from a flag).
280+
* @param {object} [options] Loader options.
281+
* @param {string} [options.configPath] Path to the config file.
282+
* @param {string} [options.domain] Domain override (e.g. from a flag).
283283
* @returns {object} The normalized config.
284284
*/
285285
export function loadConfig({ configPath = 'reglance.json', domain } = {}) {
@@ -363,7 +363,7 @@ export function loadConfig({ configPath = 'reglance.json', domain } = {}) {
363363
* Writes a self-contained .gitignore inside the output directory so that
364364
* captures, diffs, and reports never get committed to the host project.
365365
*
366-
* @param {object} config - The normalized config.
366+
* @param {object} config The normalized config.
367367
*/
368368
export function ensureOutputDir(config) {
369369
fs.mkdirSync(config.outputDir, { recursive: true });

src/control.mjs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import { readManifest, writeManifest } from './manifest.mjs';
1313
* when a run promotes fewer captures than expected (leaving older controls in
1414
* place for the rest).
1515
*
16-
* @param {object} config - The normalized config.
17-
* @param {object} [options] - Control options.
18-
* @param {Array} [options.only] - Limit to these target keys.
19-
* @param {string} [options.now] - ISO timestamp override (for tests).
16+
* @param {object} config The normalized config.
17+
* @param {object} [options] Control options.
18+
* @param {Array} [options.only] Limit to these target keys.
19+
* @param {string} [options.now] ISO timestamp override (for tests).
2020
* @returns {{ moved: number, expected: number }} Promotion counts.
2121
*/
2222
export function control(config, options = {}) {

0 commit comments

Comments
 (0)