Skip to content

vscode: lint rule banning bare vscode.commands.registerCommand outside reg/regCli helpers (enforce #791 convention) #956

Description

@amrmelsayed

Context

#791 introduced a reg/regCli registrar split for VS Code commands in packages/vscode/src/extension.ts. The pattern's appeal is that the registrar name at each call site IS the guard policy — no parallel list to keep in sync. The two helpers:

const reg    = <A>(id: string, handler: (...args: A) => unknown) =>
  vscode.commands.registerCommand(id, handler);
const regCli = <A>(id: string, handler: (...args: A) => unknown) =>
  vscode.commands.registerCommand(id, guard(handler));   // guard = CLI-ready check + setup toast

All 44 command registrations in extension.ts flow through one of these two helpers today.

Problem

The convention is a habit, not an enforced rule. A future contributor adding a command via the older form:

vscode.commands.registerCommand('codev.newThing', () => doThing())

silently bypasses both guards. The CLI-preflight signal that #791 paid for never reaches the new command, and the only signal at review time is grep — easy to miss in a larger PR.

This is a small but real silent-regression class. Worth one-time enforcement.

Proposed change

Add a custom ESLint rule (or no-restricted-syntax selector — simpler if it covers the case) to the vscode package's lint config:

  • Ban: vscode.commands.registerCommand(...) — bare calls.
  • Allow: reg(...), regCli(...) — the two helpers.
  • Allow internally: the helpers themselves (the two definitions in extension.ts that call vscode.commands.registerCommand). Either by scoping the rule to disallow the call outside a function body named reg/regCli, or by exempting extension.ts lines containing the helper definitions, or via an inline eslint-disable-next-line on the two helper bodies (simplest, and visible).

The rule lives in packages/vscode/eslint.config.js (or the workspace config if that's where vscode-package overrides go).

Rule shape (sketch — no-restricted-syntax likely sufficient)

{
  selector: \"CallExpression[callee.object.object.name='vscode'][callee.object.property.name='commands'][callee.property.name='registerCommand']\",
  message: 'Use reg(...) or regCli(...) from extension.ts instead of bare vscode.commands.registerCommand (see #791).',
}

Two eslint-disable-next-line on the helper bodies themselves (visible, intentional escape hatches).

Alternative considered

A full custom rule package (packages/vscode/eslint-rules/no-bare-register-command.js with its own AST visitor) would be more precise but requires standing up custom-rule plumbing in eslint config (loaders, plugin shape). no-restricted-syntax is built-in, zero additional config, and the selector above is precise enough.

Acceptance

  • Adding a vscode.commands.registerCommand('codev.foo', () => ...) call anywhere in packages/vscode/src/ (outside the two helper definitions) fails pnpm lint.
  • Adding the same via reg('codev.foo', () => ...) or regCli('codev.foo', () => ...) lints cleanly.
  • The two existing helper definitions in extension.ts continue to lint cleanly (via eslint-disable-next-line or selector scoping).
  • Lint failure message names the helpers and cites vscode: startup preflight — verify codev CLI installed and version ≥ extension version; guide install/upgrade otherwise #791 so the next contributor finds the rationale fast.
  • No changes to runtime behavior or output bundle.

Why AIR

Small (<50 LOC change, mostly config), no architectural decisions, no UI to test, no plan worth writing. Implement → review fits cleanly.

Related

Metadata

Metadata

Assignees

Labels

area/vscodeArea: VS Code extension

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions