Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions docs/lint.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Linting

```sh
jsonschema lint [schemas-or-directories...] [--http/-h] [--fix/-f]
[--json/-j] [--verbose/-v] [--debug/-g]
[--format/-m] [--keep-ordering/-k] [--json/-j] [--verbose/-v] [--debug/-g]
[--resolve/-r <schemas-or-directories> ...]
[--extension/-e <extension>] [--ignore/-i <schemas-or-directories>]
[--exclude/-x <rule-name>] [--only/-o <rule-name>] [--list/-l]
Expand All @@ -30,6 +30,12 @@ automatically fix many of them.

**The `--fix/-f` option is not supported when passing YAML schemas.**

**The `--format/-m` option requires `--fix/-f` to be set and is not supported
for YAML schemas.** When `--format/-m` is set, the output file is always
written with proper formatting (equivalent to running `fmt`), even if there
are no lint issues to fix. Use `--keep-ordering/-k` with `--format/-m` to
preserve key ordering during formatting.

> [!NOTE]
> There are linting rules that require compiling and validating instance
> against the given schema. For example, there is a rule to check that the
Expand Down Expand Up @@ -173,10 +179,16 @@ jsonschema lint path/to/my/schema.json --fix
jsonschema lint path/to/my/schema.json --fix --indentation 4
```

### Fix lint warnings on a single schema while preserving keyword ordering
### Fix lint warnings and format the schema

```sh
jsonschema lint path/to/my/schema.json --fix --format
```

### Fix lint warnings, format, but preserve keyword ordering

```sh
jsonschema lint path/to/my/schema.json --fix --keep-ordering
jsonschema lint path/to/my/schema.json --fix --format --keep-ordering
```

### Print a summary of all enabled rules
Expand Down
34 changes: 32 additions & 2 deletions src/command_lint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include <sourcemeta/blaze/linter.h>

#include <cstdlib> // EXIT_FAILURE
#include <fstream> // std::ofstream
#include <fstream> // std::ofstream, std::ifstream
#include <iostream> // std::cerr, std::cout
#include <numeric> // std::accumulate
#include <sstream> // std::ostringstream
Expand Down Expand Up @@ -181,6 +181,18 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options)
return;
}

const bool format_output{options.contains("format")};
const bool keep_ordering{options.contains("keep-ordering")};

if (format_output && !options.contains("fix")) {
throw OptionConflictError{"The --format option requires --fix to be set"};
}

if (keep_ordering && !format_output) {
throw OptionConflictError{
"The --keep-ordering option requires --format to be set"};
}

bool result{true};
auto errors_array = sourcemeta::core::JSON::make_array();
std::vector<std::uint8_t> scores;
Expand Down Expand Up @@ -255,7 +267,25 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options)
result = false;
}

if (copy != entry.second) {
if (format_output) {
if (!keep_ordering) {
sourcemeta::core::format(copy, sourcemeta::core::schema_walker,
custom_resolver, dialect);
}

std::ostringstream expected;
sourcemeta::core::prettify(copy, expected, indentation);
expected << "\n";

std::ifstream current_stream{entry.first};
std::ostringstream current;
current << current_stream.rdbuf();

if (current.str() != expected.str()) {
std::ofstream output{entry.first};
output << expected.str();
}
} else if (copy != entry.second) {
std::ofstream output{entry.first};
sourcemeta::core::prettify(copy, output, indentation);
output << "\n";
Expand Down
9 changes: 7 additions & 2 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,17 @@ Global Options:
Format the input schemas in-place or check they are formatted.
This command does not support YAML schemas yet.

lint [schemas-or-directories...] [--fix/-f] [--extension/-e <extension>]
lint [schemas-or-directories...] [--fix/-f] [--format/-m]
[--keep-ordering/-k] [--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>] [--exclude/-x <rule-name>]
[--only/-o <rule-name>] [--list/-l] [--indentation/-n <spaces>]

Lint the input schemas and potentially fix the reported issues.
The --fix/-f option is not supported when passing YAML schemas.
Use --format/-m with --fix to format the output even when there
are no linting issues.
Use --keep-ordering/-k with --format to preserve key order.
Use --list/-l to print a summary of all enabled rules.
Use --indentation/-n to keep indentation when auto-fixing

bundle <schema.json|.yaml> [--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>] [--without-id/-w]
Expand Down Expand Up @@ -146,6 +149,8 @@ auto jsonschema_main(const std::string &program, const std::string &command,
return EXIT_SUCCESS;
} else if (command == "lint") {
app.flag("fix", {"f"});
app.flag("format", {"m"});
app.flag("keep-ordering", {"k"});
app.flag("list", {"l"});
app.option("extension", {"e"});
app.option("exclude", {"x"});
Expand Down
8 changes: 8 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,14 @@ add_jsonschema_test_unix(lint/fail_draft7_defs_ref_target)
add_jsonschema_test_unix(lint/fail_draft7_defs_ref_target_fix)
add_jsonschema_test_unix(lint/fail_draft4_x_keyword_ref_target)
add_jsonschema_test_unix(lint/fail_draft4_x_keyword_ref_target_fix)
add_jsonschema_test_unix(lint/pass_lint_fix_format)
add_jsonschema_test_unix(lint/pass_lint_fix_format_no_issues)
add_jsonschema_test_unix(lint/pass_lint_fix_format_keep_ordering)
add_jsonschema_test_unix(lint/pass_lint_fix_format_directory)
add_jsonschema_test_unix(lint/fail_lint_fix_format_unfixable)
add_jsonschema_test_unix(lint/fail_lint_format_without_fix)
add_jsonschema_test_unix(lint/fail_lint_keep_ordering_without_format)
add_jsonschema_test_unix(lint/fail_lint_format_yaml)

# Encode
add_jsonschema_test_unix(encode/pass_schema_less)
Expand Down
76 changes: 76 additions & 0 deletions test/lint/fail_lint_fix_format_unfixable.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/bin/sh

set -o errexit
set -o nounset

TMP="$(mktemp -d)"
clean() { rm -rf "$TMP"; }
trap clean EXIT

cat << 'EOF' > "$TMP/schema.json"
{
"title": "Test",
"type": "string",
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "https://example.com"
}
EOF

cd "$TMP"
"$1" lint "$TMP/schema.json" --fix --format >"$TMP/output.txt" 2>&1 \
&& CODE="$?" || CODE="$?"
test "$CODE" = "2" || exit 1

cat << 'EOF' > "$TMP/expected_output.txt"
schema.json:1:1:
Set a non-empty description at the top level of the schema to explain what the definition is about in detail (top_level_description)
at location ""
schema.json:1:1:
Set a non-empty examples array at the top level of the schema to illustrate the expected data (top_level_examples)
at location ""
EOF

diff "$TMP/output.txt" "$TMP/expected_output.txt"

cat << 'EOF' > "$TMP/expected.json"
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "https://example.com",
"title": "Test",
"type": "string"
}
EOF

diff "$TMP/schema.json" "$TMP/expected.json"

# JSON output
"$1" lint "$TMP/schema.json" --fix --format --json >"$TMP/output_json.txt" 2>&1 \
&& CODE="$?" || CODE="$?"
test "$CODE" = "2" || exit 1

cat << EOF > "$TMP/expected_output_json.txt"
{
"valid": false,
"health": 0,
"errors": [
{
"path": "$(realpath "$TMP")/schema.json",
"id": "top_level_description",
"message": "Set a non-empty description at the top level of the schema to explain what the definition is about in detail",
"description": null,
"schemaLocation": "",
"position": [ 1, 1, 6, 1 ]
},
{
"path": "$(realpath "$TMP")/schema.json",
"id": "top_level_examples",
"message": "Set a non-empty examples array at the top level of the schema to illustrate the expected data",
"description": null,
"schemaLocation": "",
"position": [ 1, 1, 6, 1 ]
}
]
}
EOF

diff "$TMP/output_json.txt" "$TMP/expected_output_json.txt"
38 changes: 38 additions & 0 deletions test/lint/fail_lint_format_without_fix.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/sh

set -o errexit
set -o nounset

TMP="$(mktemp -d)"
clean() { rm -rf "$TMP"; }
trap clean EXIT

cat << 'EOF' > "$TMP/schema.json"
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "string"
}
EOF

"$1" lint "$TMP/schema.json" --format >"$TMP/stderr.txt" 2>&1 \
&& CODE="$?" || CODE="$?"
test "$CODE" = "1" || exit 1

cat << EOF > "$TMP/expected.txt"
error: The --format option requires --fix to be set
EOF

diff "$TMP/stderr.txt" "$TMP/expected.txt"

# JSON error
"$1" lint "$TMP/schema.json" --format --json >"$TMP/stdout.txt" 2>&1 \
&& CODE="$?" || CODE="$?"
test "$CODE" = "1" || exit 1

cat << EOF > "$TMP/expected.txt"
{
"error": "The --format option requires --fix to be set"
}
EOF

diff "$TMP/stdout.txt" "$TMP/expected.txt"
38 changes: 38 additions & 0 deletions test/lint/fail_lint_format_yaml.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/sh

set -o errexit
set -o nounset

TMP="$(mktemp -d)"
clean() { rm -rf "$TMP"; }
trap clean EXIT

cat << 'EOF' > "$TMP/schema.yaml"
$schema: http://json-schema.org/draft-06/schema#
type: string
EOF

"$1" lint "$TMP/schema.yaml" --fix --format >"$TMP/stderr.txt" 2>&1 \
&& CODE="$?" || CODE="$?"
test "$CODE" = "1" || exit 1

cat << EOF > "$TMP/expected.txt"
error: The --fix option is not supported for YAML input files
at file path $(realpath "$TMP/schema.yaml")
EOF

diff "$TMP/stderr.txt" "$TMP/expected.txt"

# JSON error
"$1" lint "$TMP/schema.yaml" --fix --format --json >"$TMP/stdout.txt" 2>&1 \
&& CODE="$?" || CODE="$?"
test "$CODE" = "1" || exit 1

cat << EOF > "$TMP/expected.txt"
{
"error": "The --fix option is not supported for YAML input files",
"filePath": "$(realpath "$TMP/schema.yaml")"
}
EOF

diff "$TMP/stdout.txt" "$TMP/expected.txt"
38 changes: 38 additions & 0 deletions test/lint/fail_lint_keep_ordering_without_format.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/sh

set -o errexit
set -o nounset

TMP="$(mktemp -d)"
clean() { rm -rf "$TMP"; }
trap clean EXIT

cat << 'EOF' > "$TMP/schema.json"
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "string"
}
EOF

"$1" lint "$TMP/schema.json" --fix --keep-ordering >"$TMP/stderr.txt" 2>&1 \
&& CODE="$?" || CODE="$?"
test "$CODE" = "1" || exit 1

cat << EOF > "$TMP/expected.txt"
error: The --keep-ordering option requires --format to be set
EOF

diff "$TMP/stderr.txt" "$TMP/expected.txt"
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 3, 2026

Choose a reason for hiding this comment

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

P2: Add a JSON variant of this failure test. After the text assertion, run the same command with --json and verify the structured error output so JSON error reporting stays consistent.

(Based on your team's feedback about adding JSON variants for failure test cases.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/lint/fail_lint_keep_ordering_without_format.sh, line 25:

<comment>Add a JSON variant of this failure test. After the text assertion, run the same command with --json and verify the structured error output so JSON error reporting stays consistent.

(Based on your team's feedback about adding JSON variants for failure test cases.) </comment>

<file context>
@@ -0,0 +1,25 @@
+error: The --keep-ordering option requires --format to be set
+EOF
+
+diff "$TMP/stderr.txt" "$TMP/expected.txt"
</file context>
Fix with Cubic


# JSON error
"$1" lint "$TMP/schema.json" --fix --keep-ordering --json >"$TMP/stdout.txt" 2>&1 \
&& CODE="$?" || CODE="$?"
test "$CODE" = "1" || exit 1

cat << EOF > "$TMP/expected.txt"
{
"error": "The --keep-ordering option requires --format to be set"
}
EOF

diff "$TMP/stdout.txt" "$TMP/expected.txt"
37 changes: 37 additions & 0 deletions test/lint/pass_lint_fix_format.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/sh

set -o errexit
set -o nounset

TMP="$(mktemp -d)"
clean() { rm -rf "$TMP"; }
trap clean EXIT

cat << 'EOF' > "$TMP/schema.json"
{
"title": "Test",
"description": "Test schema",
"type": "string",
"examples": [ "foo" ],
"$schema": "http://json-schema.org/draft-06/schema#",
"const": "foo"
}
EOF

"$1" lint "$TMP/schema.json" --fix --format > "$TMP/output.txt" 2>&1

cat << 'EOF' > "$TMP/expected_output.txt"
EOF
diff "$TMP/output.txt" "$TMP/expected_output.txt"

cat << 'EOF' > "$TMP/expected.json"
{
"$schema": "http://json-schema.org/draft-06/schema#",
"title": "Test",
"description": "Test schema",
"examples": [ "foo" ],
"const": "foo"
}
EOF

diff "$TMP/schema.json" "$TMP/expected.json"
Loading