Skip to content

feat(validate): add validate command for import map linting#29

Merged
bennypowers merged 2 commits into
mainfrom
feat/validate
Apr 23, 2026
Merged

feat(validate): add validate command for import map linting#29
bennypowers merged 2 commits into
mainfrom
feat/validate

Conversation

@bennypowers

@bennypowers bennypowers commented Apr 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • New mappa validate command that checks import maps against the WHATWG spec
  • Accepts a file argument or reads from stdin
  • Reports trailing-slash consistency, URL format, and scope key violations
  • Exits 0 if valid, 1 if violations found
  • Supports --format text (default) and --format json for CI

Test plan

  • 8 integration tests covering valid/invalid files, stdin, JSON format, help, missing file
  • make lint passes
  • make test passes (324 tests)

Summary by CodeRabbit

  • New Features

    • Added a new validate CLI command to check import map files (optional file argument or stdin). Supports --format/-f as text (errors to stderr) or json (structured array to stdout). Exits non‑zero when validation errors are present; includes help text.
  • Tests

    • Added comprehensive tests and example fixtures covering valid/invalid inputs and both output formats.

Assisted-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A new Cobra-based CLI subcommand mappa validate reads an import map from a file or stdin, parses it with importmap.Parse, runs im.Validate(), and emits validation results in text or json formats; non-empty validation results cause a non-zero exit.

Changes

Cohort / File(s) Summary
Validate Command Implementation
cmd/validate/validate.go
New exported Cmd Cobra command (Use: "validate [file]") that reads input from file or stdin, parses an import map, runs im.Validate(), and outputs errors as text (stderr) or json (stdout) according to --format flag.
CLI Integration
main.go
Registers validate.Cmd with the root command and imports the new validate package.
Tests
main_test.go
Adds tests covering help visibility, valid/invalid fixtures, stdin handling, --format json behavior (empty vs non-empty JSON arrays), error messages, and missing-file handling.
Test Fixtures
testdata/validate/valid/input.json, testdata/validate/invalid/input.json
New valid and invalid import-map JSON fixtures used by the added tests.
ImportMap JSON Serialization
importmap/importmap.go
Updated ValidationError struct tags to include JSON metadata: Key, Value, Scope use omitempty; Message serialized as message.

Sequence Diagram

sequenceDiagram
    participant User as User/CLI
    participant Cmd as Validate Cmd
    participant FS as File System / Stdin
    participant Parser as importmap.Parse
    participant Validator as importmap.Validator
    participant Output as Formatter

    User->>Cmd: mappa validate [file] --format text|json
    Cmd->>FS: read file or stdin
    FS-->>Cmd: raw JSON
    Cmd->>Parser: parse import map
    Parser-->>Cmd: import map object
    Cmd->>Validator: im.Validate()
    Validator-->>Cmd: []ValidationError
    Cmd->>Output: format (text -> stderr | json -> stdout)
    Output-->>User: formatted results + exit code
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A hop, a check, a little nudge,
I read the map and scan the judge.
From file or pipe, the rabbit spies,
Text or JSON — errors rise.
A tiny thump for safer ties.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(validate): add validate command for import map linting' accurately summarizes the main change: introduction of a new validate CLI command for linting import maps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/validate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Apr 23, 2026

Copy link
Copy Markdown

Build Artifacts

OS x64 arm64
Linux mappa-linux-x64 mappa-linux-arm64
macOS mappa-darwin-x64 mappa-darwin-arm64
Windows mappa-win32-x64 mappa-win32-arm64

Built from cf31f90 @ feat/validate

@bennypowers

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
cmd/validate/validate.go (2)

57-84: Validate --format before doing I/O and parsing.

The format flag is checked at line 82 after reading the file/stdin and parsing JSON. A user running mappa validate huge.json --format yaml does the full read + parse before being told the flag is invalid. Move the flag check to the top of run so invalid usage fails fast.

♻️ Proposed reorder
 func run(cmd *cobra.Command, args []string) error {
+	format, _ := cmd.Flags().GetString("format")
+	if format != "text" && format != "json" {
+		return fmt.Errorf("invalid format %q: must be 'text' or 'json'", format)
+	}
+
 	var data []byte
 	var err error
@@
 	errs := im.Validate()
-
-	format, _ := cmd.Flags().GetString("format")
-	if format != "text" && format != "json" {
-		return fmt.Errorf("invalid format %q: must be 'text' or 'json'", format)
-	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/validate/validate.go` around lines 57 - 84, Move the format validation to
the start of run so invalid --format values are rejected before any I/O or
parsing; specifically, call cmd.Flags().GetString("format") and validate it
("text" or "json") at the top of run (before the osfs.ReadFile / io.ReadAll and
before importmap.Parse and im.Validate) and return the same error message if
invalid. This keeps the rest of the logic (file/stdin read, importmap.Parse,
im.Validate) unchanged but gated by the early check of format.

98-100: Returning an error for "violations found" double-reports.

In text mode each violation is already printed to stderr at line 94, and in JSON mode the structured array is on stdout. Returning fmt.Errorf("import map has %d validation error(s)", …) here causes cobra to additionally emit Error: import map has N validation error(s) to stderr, which in JSON mode is extra noise alongside the machine-readable stdout. Consider using os.Exit(1) from a small wrapper (or cmd.SilenceErrors = true plus SilenceUsage = true) so the non-zero exit is signaled without an extra human-readable line muddying the JSON workflow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/validate/validate.go` around lines 98 - 100, The code currently returns
fmt.Errorf(...) when validation fails (the len(errs) > 0 branch in validate.go),
which causes cobra to print an extra human-readable Error line and pollutes JSON
output; change this to signal failure without returning an error: either (a)
call os.Exit(1) directly after printing the violations in the same function
(keep the existing stderr/stdout prints and then exit), or (b) silence cobra by
setting the Cobra command fields SilenceErrors = true and SilenceUsage = true on
the command that runs the validate logic so you can continue returning without
cobra emitting the additional Error line; update the branch that references errs
and fmt.Errorf to use one of these approaches (referencing the validate
function/errs variable and fmt.Errorf occurrence to locate the change).
main_test.go (2)

855-896: Move inline stdin JSON into fixture files.

TestValidateStdin and TestValidateStdinInvalid hand-roll import-map JSON as raw string literals. The equivalent content already exists (or can trivially be added) under testdata/validate/...; reuse those fixtures and pipe them via os.Open / cmd.Stdin = f so stdin and file paths share the same inputs.

As per coding guidelines: "Don't inline source code in tests; use fixture files instead with input files and expected output."

♻️ Example refactor
-	cmd := exec.Command(binary, "validate")
-	cmd.Stdin = strings.NewReader(`{
-  "imports": {
-    "lit": "/node_modules/lit/index.js"
-  }
-}`)
+	f, err := os.Open(filepath.Join("testdata", "validate", "valid", "input.json"))
+	if err != nil {
+		t.Fatalf("open fixture: %v", err)
+	}
+	defer f.Close()
+	cmd := exec.Command(binary, "validate")
+	cmd.Stdin = f
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main_test.go` around lines 855 - 896, Replace the inline JSON string literals
in TestValidateStdin and TestValidateStdinInvalid by opening the corresponding
fixture files and piping them into the command via cmd.Stdin (use os.Open to get
an *os.File, assign it to cmd.Stdin, and defer file.Close()), so both tests
reuse the existing test fixtures instead of raw string literals; update the
tests to open the appropriate valid/invalid fixture before running
exec.Command(..., "validate") and keep the existing stdout/stderr checks
unchanged.

898-941: JSON-mode tests rely on exported Go field names; consider tightening.

Line 918 asserts e["Message"] != nil, which matches the current ValidationError struct tags (none — fields serialize with their Go names). If anyone later adds json:"message" tags (typical Go convention for public JSON output), these tests silently break. Either add explicit JSON tags to ValidationError now and switch the tests to lowercase keys, or pin the wire format with a golden file under testdata/validate/invalid/expected.json and reuse compareOrUpdateGolden to guard against accidental schema drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main_test.go` around lines 898 - 941, The tests TestValidateFormatJSON and
TestValidateFormatJSONValid rely on Go-exported field names (checking
e["Message"]) which will break if ValidationError is later tagged with
json:"message"; update the wire contract by either (A) adding explicit JSON tags
to the ValidationError struct (e.g., `Message` → `json:"message"`) and change
the tests to check the lowercase key "message", or (B) pin the output with a
golden file for the JSON-mode case (create
testdata/validate/invalid/expected.json) and have TestValidateFormatJSON use
compareOrUpdateGolden to compare stdout against that golden file instead of
indexing map keys; pick one approach and update the referenced symbols
(ValidationError, TestValidateFormatJSON, TestValidateFormatJSONValid, and
compareOrUpdateGolden) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/validate/validate.go`:
- Around line 32-51: The Cobra command Cmd currently returns errors from run
which causes Cobra to print both the error and full usage; to avoid the noisy
usage dump on validation failures set Cmd.SilenceUsage = true (and optionally
Cmd.SilenceErrors = true) so only the error output is printed; update the Cmd
declaration (the variable named Cmd in this file) to enable these flags so run
can still return fmt.Errorf("import map has %d validation error(s)", ...)
without Cobra dumping usage to stderr.

In `@testdata/validate/invalid/input.json`:
- Around line 4-5: The fixture includes "bare" and "empty" keys that aren't
validated by importmap.Validate()/validateSpecifierMap, so either update the
validation logic or trim the fixture: add URL and empty-value checks inside
importmap.Validate() (the validateSpecifierMap path) to produce ValidationError
for non-absolute/invalid URLs and zero-length specifier values (validate "bare"
and "empty"), or remove those keys from testdata/validate/invalid/input.json so
the fixture only contains cases validateSpecifierMap currently triggers (e.g.,
trailing-slash cases); target the Validate()/validateSpecifierMap functions as
the location to implement the new checks if you choose to extend validation.

---

Nitpick comments:
In `@cmd/validate/validate.go`:
- Around line 57-84: Move the format validation to the start of run so invalid
--format values are rejected before any I/O or parsing; specifically, call
cmd.Flags().GetString("format") and validate it ("text" or "json") at the top of
run (before the osfs.ReadFile / io.ReadAll and before importmap.Parse and
im.Validate) and return the same error message if invalid. This keeps the rest
of the logic (file/stdin read, importmap.Parse, im.Validate) unchanged but gated
by the early check of format.
- Around line 98-100: The code currently returns fmt.Errorf(...) when validation
fails (the len(errs) > 0 branch in validate.go), which causes cobra to print an
extra human-readable Error line and pollutes JSON output; change this to signal
failure without returning an error: either (a) call os.Exit(1) directly after
printing the violations in the same function (keep the existing stderr/stdout
prints and then exit), or (b) silence cobra by setting the Cobra command fields
SilenceErrors = true and SilenceUsage = true on the command that runs the
validate logic so you can continue returning without cobra emitting the
additional Error line; update the branch that references errs and fmt.Errorf to
use one of these approaches (referencing the validate function/errs variable and
fmt.Errorf occurrence to locate the change).

In `@main_test.go`:
- Around line 855-896: Replace the inline JSON string literals in
TestValidateStdin and TestValidateStdinInvalid by opening the corresponding
fixture files and piping them into the command via cmd.Stdin (use os.Open to get
an *os.File, assign it to cmd.Stdin, and defer file.Close()), so both tests
reuse the existing test fixtures instead of raw string literals; update the
tests to open the appropriate valid/invalid fixture before running
exec.Command(..., "validate") and keep the existing stdout/stderr checks
unchanged.
- Around line 898-941: The tests TestValidateFormatJSON and
TestValidateFormatJSONValid rely on Go-exported field names (checking
e["Message"]) which will break if ValidationError is later tagged with
json:"message"; update the wire contract by either (A) adding explicit JSON tags
to the ValidationError struct (e.g., `Message` → `json:"message"`) and change
the tests to check the lowercase key "message", or (B) pin the output with a
golden file for the JSON-mode case (create
testdata/validate/invalid/expected.json) and have TestValidateFormatJSON use
compareOrUpdateGolden to compare stdout against that golden file instead of
indexing map keys; pick one approach and update the referenced symbols
(ValidationError, TestValidateFormatJSON, TestValidateFormatJSONValid, and
compareOrUpdateGolden) accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6ffbf397-d9c0-40ac-ace3-e4d0e12b8215

📥 Commits

Reviewing files that changed from the base of the PR and between bc56238 and 36b4035.

📒 Files selected for processing (5)
  • cmd/validate/validate.go
  • main.go
  • main_test.go
  • testdata/validate/invalid/input.json
  • testdata/validate/valid/input.json

Comment thread cmd/validate/validate.go
Comment thread testdata/validate/invalid/input.json
- SilenceUsage on validate command, SilenceErrors only for validation
  failures (not operational errors like missing files)
- Move format validation before I/O
- Add json tags to ValidationError for stable wire format
- Use fixture files for stdin tests instead of inline JSON
- Update JSON field checks from "Message" to "message"

Assisted-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bennypowers

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@bennypowers

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
main_test.go (1)

855-898: Optional: extract a stdin helper to reduce duplication.

TestValidateStdin and TestValidateStdinInvalid share identical setup (build command, open fixture, wire stdin/stdout/stderr buffers, run). Consider a small helper like runCLIStdin(t, stdinPath string, args ...string) (stdout, stderr string, exitCode int) to mirror runCLI and keep these tests focused on assertions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main_test.go` around lines 855 - 898, Tests TestValidateStdin and
TestValidateStdinInvalid duplicate CLI setup; extract a helper runCLIStdin(t,
stdinPath string, args ...string) (stdout, stderr string, exitCode int) that
encapsulates building the binary, opening the fixture file, wiring
cmd.Stdin/stdout/stderr, running the command and returning captured outputs and
exit code; update both TestValidateStdin and TestValidateStdinInvalid to call
runCLIStdin (preserving assertions about exit code and stderr content) and
reference the helper name when modifying the tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@main_test.go`:
- Around line 855-898: Tests TestValidateStdin and TestValidateStdinInvalid
duplicate CLI setup; extract a helper runCLIStdin(t, stdinPath string, args
...string) (stdout, stderr string, exitCode int) that encapsulates building the
binary, opening the fixture file, wiring cmd.Stdin/stdout/stderr, running the
command and returning captured outputs and exit code; update both
TestValidateStdin and TestValidateStdinInvalid to call runCLIStdin (preserving
assertions about exit code and stderr content) and reference the helper name
when modifying the tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a9b92995-bb81-4af3-ae92-050b8122a8c5

📥 Commits

Reviewing files that changed from the base of the PR and between 36b4035 and 25f374b.

📒 Files selected for processing (3)
  • cmd/validate/validate.go
  • importmap/importmap.go
  • main_test.go
✅ Files skipped from review due to trivial changes (1)
  • importmap/importmap.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/validate/validate.go

@bennypowers bennypowers merged commit 9b240fb into main Apr 23, 2026
11 checks passed
@bennypowers bennypowers deleted the feat/validate branch April 23, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant