Repository navigation
Conversation
AST-based checker for CodeceptJS anti-patterns in tests, page objects and helpers: no-fixed-wait, no-sleep, no-only, no-pause, secret-credentials, await-grab, no-actor-in-helper, raw-browser-in-test. `--hook claude` reads a PreToolUse payload, lints the file before and after the edit and blocks only on new error findings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| const ignore = [].concat(config.lint?.ignore || []) | ||
| if (!ignore.length) return false | ||
| const target = path.resolve(file) | ||
| return withoutNodeWarnings(() => ignore.some(pattern => path.matchesGlob(target, path.resolve(root, pattern)))) |
There was a problem hiding this comment.
return withoutNodeWarnings(() => ignore.some(pattern => path.matchesGlob(target, path.resolve(root, pattern))))
path.matchesGlob doesn't exist on Node <20.17/22.5 while engines is >=16: CLI throws TypeError, hook mode silently disables itself. Fall back to minimatch
| id: 'no-actor-in-helper', | ||
| level: 'error', | ||
| check(node, ancestors, ctx) { | ||
| const isActorRef = |
There was a problem hiding this comment.
(node.type === 'Identifier' && node.name === 'I') / if (!isActorRef || !insideHelperClass(ancestors)) return
no-actor-in-helper flags const { I } = this.helpers which its own message recommends. Skip destructuring of this.helpers and non-reference identifiers
| if (seen.has(key)) return | ||
| seen.add(key) | ||
| if (suppressed.get(line)?.has(rule.id)) return | ||
| findings.push({ file, line, column: column + 1, rule: rule.id, level, message, source: ctx.source(target) }) |
There was a problem hiding this comment.
findings.push({ file, line, column: column + 1, rule: rule.id, level, message, source: ctx.source(target) }) --json leaks the plaintext secret in source for secret-credentials findings (e.g. I.fillField('Password', 'hunter2')). Mask the value argument.
One Linter class with a class per rule. Drop the typescript fallback, source maps, ignore config, warning suppression and per-rule fixtures. The hook only checks files the config lints. return I.grab*() is no longer flagged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Adds
npx codeceptjs lint: an AST check (acorn) of tests, page objects and helpers for CodeceptJS anti-patterns. It also runs as a Claude CodePreToolUsehook that blocks an edit which adds a new error.Base branch is
4.x.Code
lib/lint.js: aLinterclass (files(),includes(),lint(),lintFile()) and one small class per rule extendingRule.lib/command/lint.js: CLI output and hook mode.Rules
no-fixed-waitI.wait(5)with a numberno-sleepsetTimeoutoutside helper classesno-onlyScenario.only,Feature.only,Data(...).onlyno-pausepause()secret-credentialsI.fillFieldon a password/token/secret/API key field, or such aprocess.env.*, withoutsecret()await-grabI.grab*()assigned or used withoutawait(returning it is allowed)no-actor-in-helperIinside a class extendingHelperraw-browser-in-testI.use*To/I.executeScriptin a ScenarioLevels can be changed in
lint.rulesin the config. A single case can be suppressed with// codeceptjs-lint-disable-line <rule>or-next-line.Hook
include, helpers). Other files are allowed.I.wait(5)is new.TypeScript is read with Node's
stripTypeScriptTypes. Files withenum/namespaceare reported as parse errors.Test plan
npx mocha test/unit/command: 44 passing. The lint tests check each rule on inline code, plus suppression comments, config levels, TypeScript lines, CommonJS, file collection, the CLI exit code, and hook block/allow cases.test/acceptanceandexamples: no parse failures. Findings are plain-text passwords in examples and onesetTimeoutin a config file.🤖 Generated with Claude Code