Rendered from specs/F17-schema-linting.md — edit it there, not here.
F17 — Schema Linting
Overview
Quality diagnostics for schema files themselves (the extension already validates data files in F03 — this lints the schema). Rules catch the gaps that make schemas hard to consume as team contracts: missing descriptions, unpinned drafts, misspelled keywords, permissive objects. Diagnostics are in-process, debounced, and conservative by default (hints, not warnings) so the feature informs without nagging.
User Stories
- As a schema author, I want to be told which properties lack
descriptions, so the rendered documentation (F01) is actually useful to readers. - As a team lead, I want misspelled keywords (
requird,additionaProperties) flagged, because JSON Schema silently ignores unknown keywords and the mistake ships. - As a maintainer, I want to tune or disable individual rules per workspace.
Functional Requirements
Activation and Lifecycle
- F17-FR-01 Linting MUST apply only to schema files, in
json,jsonc,yaml, andymldocuments. A document counts as a schema file when it is detected by F01-FR-02 (a$schemadeclaration) or its filename matches the*.schema.{json,jsonc,yaml,yml}convention. The filename fallback is required sorequire-schema-declaration(F17-FR-05) can fire on a schema-named file that is missing its$schema— which F01-FR-02 alone could never surface. - F17-FR-02 Diagnostics MUST refresh on open and on change (debounced, reusing the F02 debounce configuration pattern) and MUST be cleared when the document closes or stops being a schema.
- F17-FR-03 A setting
jsonschema.lint.enabled(defaulttrue) MUST gate the whole feature;jsonschema.lint.rules(object ofruleId → "off" | "hint" | "info" | "warning") MUST override individual rule severities.
Rules (initial set)
Each rule has a stable ID, reported as the diagnostic code. Default severities in parentheses.
- F17-FR-04
no-unknown-keywords(warning): a key that is not a valid keyword of the declared draft — nor insideproperties/$defs/other keyword-value positions — MUST be flagged; the message SHOULD suggest the nearest known keyword for likely typos (edit distance 1–2). - F17-FR-05
require-schema-declaration(info): the root MUST declare$schema; when absent the diagnostic offers context on why drafts matter. - F17-FR-06
require-root-id(hint): the root SHOULD declare$idwhen the schema is referenced by other files. - F17-FR-07
require-descriptions(hint): the root and every named property SHOULD carry a non-emptydescription(ortitleat root). - F17-FR-08
explicit-additional-properties(hint): object subschemas withpropertiesSHOULD stateadditionalPropertiesexplicitly. - F17-FR-09
no-duplicate-enum(warning):enumarrays MUST NOT contain duplicate values (deep equality). - F17-FR-10
no-empty-required(warning):requiredMUST NOT list names absent frompropertieswhenadditionalPropertiesisfalse. - F17-FR-11 Every diagnostic MUST carry the precise source range of the offending key/value (via
jsonc-parser/ YAML AST node positions), the rule ID ascode, and severity per configuration.
Quick Fixes
- F17-FR-12 Code actions MUST be offered for: inserting a
$schemadeclaration (QuickPick of drafts, 2020-12 first), inserting"additionalProperties": false, and de-duplicating anenum. Rules without a safe mechanical fix MUST NOT offer one.
Non-Functional Requirements
- F17-NFR-01 Linting MUST run in-process; a 500 KB schema SHOULD lint in under 200 ms. Rule evaluation MUST share one parse per document version.
- F17-NFR-02 Each rule MUST be a pure function over the parsed AST with its own unit-test suite (positive, negative, and edge fixtures; ≥ 80 % coverage, Article V).
- F17-NFR-03 Diagnostics MUST use a dedicated
DiagnosticCollection(source:json-schema-lint) so they never mix with F03 data-validation diagnostics.
Out of Scope
- Custom user-defined rules / Spectral ruleset compatibility (evaluate once the built-in set proves out).
- Auto-fix-on-save.
- Linting data files (F03's domain) or non-schema JSON.
Acceptance Criteria
- Opening a schema with
"requird": ["name"]shows a warning suggestingrequired, positioned on the misspelled key. - A root without
$schemashows an info diagnostic; its quick fix inserts a draft declaration chosen from a QuickPick. - Setting
jsonschema.lint.rules: { "require-descriptions": "off" }removes those hints without affecting other rules. "enum": [1, 1, 2]shows a warning; the quick fix leaves[1, 2].- Diagnostics disappear when the
$schemakey is removed and the file stops being detected as a schema.
Open Questions
- Q1 — Keyword tables per draft (04/06/07/2019-09/2020-12): maintain in-house constants vs. derive from Ajv's vocabularies. Leaning in-house constants (small, testable, no new dependency).
Relation to Existing Specs
- Complements F03 (data validation) — separate diagnostic source (F17-NFR-03).
- Reuses F01-FR-02 schema detection and the F02 debounce pattern.
- S03: debounced, in-process, single parse per version; S06: severity is conveyed by VS Code's standard diagnostic UI (not colour alone).
Requirement traceability
Status of each requirement in specs/traceability.json. Test coverage is auto-discovered from [ID] tags in test titles and is not listed here.
| Requirement | Status | Implementation | Note |
|---|---|---|---|
F17-FR-01 | implemented | src/SchemaLintManager.ts | lints json/jsonc/yaml/yml detected by isJsonSchemaFile OR *.schema.* filename |
F17-FR-02 | implemented | src/SchemaLintManager.ts | lintDocument on open; debounced on change; cleared on close/not-schema |
F17-FR-03 | implemented | src/settings.ts, src/SchemaLintManager.ts | jsonschema.lint.enabled + jsonschema.lint.rules severity overrides (off/hint/info/warning) |
F17-FR-04 | implemented | src/schemaLinter.ts | no-unknown-keywords: union vocabulary; editDistance suggests nearest; skips x- and schema-map positions |
F17-FR-05 | implemented | src/schemaLinter.ts | require-schema-declaration (info) when root lacks $schema |
F17-FR-06 | implemented | src/schemaLinter.ts | require-root-id (hint) when root lacks $id/id |
F17-FR-07 | implemented | src/schemaLinter.ts | require-descriptions (hint) for root and each named property |
F17-FR-08 | implemented | src/schemaLinter.ts | explicit-additional-properties (hint) when properties present without additionalProperties |
F17-FR-09 | implemented | src/schemaLinter.ts | no-duplicate-enum (warning) via deep-equality normalisation |
F17-FR-10 | implemented | src/schemaLinter.ts | no-empty-required (warning) for required names absent from properties when closed |
F17-FR-11 | implemented | src/schemaLinter.ts, src/SchemaLintManager.ts | findings carry jsonc/yaml AST source ranges, ruleId as code, configured severity |
F17-FR-12 | implemented | src/SchemaLintManager.ts | quick fixes: insert $schema (draft QuickPick, 2020-12 first), add additionalProperties:false, dedupe enum |
F17-NFR-01 | implemented | src/schemaLinter.ts | in-process; single parse per lintDocument call |
F17-NFR-02 | implemented | src/schemaLinter.ts | each rule is a pure function over the AST with its own unit suite |
F17-NFR-03 | implemented | src/SchemaLintManager.ts | dedicated DiagnosticCollection source 'json-schema-lint' |