@amritk/lint 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +186 -0
  2. package/package.json +53 -0
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ <div align="center">
2
+
3
+ # @amritk/lint
4
+
5
+ **A fast, format-agnostic JSON/YAML style-guide linter — JSON Schema validation and custom rules, with exact `line:column` findings.**
6
+
7
+ ![status](https://img.shields.io/badge/status-pre--alpha-ef4444?style=flat-square)&nbsp;
8
+ ![version](https://img.shields.io/badge/version-v0.0.0-6366f1?style=flat-square&logo=npm&logoColor=white)&nbsp;
9
+ ![license](https://img.shields.io/badge/license-MIT-22c55e?style=flat-square)&nbsp;
10
+ ![JSON Schema](https://img.shields.io/badge/JSON%20Schema-2020--12-f97316?style=flat-square)&nbsp;
11
+ ![node](https://img.shields.io/badge/node-%E2%89%A520-339933?style=flat-square&logo=node.js&logoColor=white)&nbsp;
12
+ ![vibe coded](https://img.shields.io/badge/vibe-coded-a855f7?style=flat-square)
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ ## Overview
19
+
20
+ `@amritk/lint` lints **any** JSON or YAML document against a ruleset you define. A rule matches nodes with a **JSONPath** (`given`) and runs a **function** (`then`) over each match — structural validation against a **JSON Schema**, style checks (`casing`, `pattern`, `alphabetical`, `length`, …), or your own custom function. Every finding carries an exact `line:column` range, because the parser keeps source positions on every node.
21
+
22
+ It is **format-agnostic**: the engine ships no built-in ruleset and knows nothing about OpenAPI or any other schema — you bring the rules. This is JSON/YAML style-guide linting with JSON Schema and custom rules, and nothing else.
23
+
24
+ The CLI lives in the [`mjst`](../cli) binary as `mjst lint`; this package is the programmatic library behind it.
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @amritk/lint
32
+ # or
33
+ pnpm add @amritk/lint
34
+ # or
35
+ bun add @amritk/lint
36
+ ```
37
+
38
+ For the command line, install [`@amritk/mjst`](../cli) and run `mjst lint` (see [CLI](#cli) below).
39
+
40
+ ---
41
+
42
+ ## Usage
43
+
44
+ ### Lint a document
45
+
46
+ ```ts
47
+ import { lintDocument } from '@amritk/lint'
48
+
49
+ const ruleset = {
50
+ rules: {
51
+ 'require-name': { given: '$', severity: 'error', then: { field: 'name', function: 'truthy' } },
52
+ 'name-kebab': { given: '$.name', severity: 'warn', then: { function: 'casing', functionOptions: { type: 'kebab' } } },
53
+ },
54
+ }
55
+
56
+ const findings = await lintDocument('version: 1\n', { ruleset, source: 'service.yaml' })
57
+ // → [{ code: 'require-name', message: 'The value must be truthy', path: ['name'],
58
+ // severity: 0, source: 'service.yaml', range: { start: { line: 0, character: 0 }, … } }]
59
+ ```
60
+
61
+ `severity` is a `DiagnosticSeverity` — `0` error, `1` warning, `2` info, `3` hint — and `range` is a zero-based `{ line, character }` span you can render a squiggle from.
62
+
63
+ ### Validate against a JSON Schema
64
+
65
+ The built-in `schema` function runs an arbitrary JSON Schema (Draft 2020-12) over the matched node, via [`@amritk/runtime-validators`](../runtime-validators):
66
+
67
+ ```ts
68
+ const ruleset = {
69
+ rules: {
70
+ 'config-schema': {
71
+ given: '$',
72
+ severity: 'error',
73
+ then: { function: 'schema', functionOptions: { schema: { type: 'object', required: ['port'], properties: { port: { type: 'integer' } } } } },
74
+ },
75
+ },
76
+ }
77
+
78
+ await lintDocument('port: not-a-number\n', { ruleset })
79
+ // → a `config-schema` finding pointing at `port`
80
+ ```
81
+
82
+ ### Custom functions and `extends`
83
+
84
+ A ruleset can pull in rules from another file and load custom functions by name (resolved relative to the ruleset that declares them):
85
+
86
+ ```yaml
87
+ # .lint.yaml
88
+ extends:
89
+ - ./base.yaml # a file path or an npm package
90
+ functions: [no-secrets] # loaded from ./functions/no-secrets.{js,cjs,mjs}
91
+ rules:
92
+ no-secrets:
93
+ given: $..*
94
+ severity: error
95
+ then: { function: no-secrets }
96
+ ```
97
+
98
+ ```ts
99
+ import { lintDocument } from '@amritk/lint'
100
+
101
+ await lintDocument(source, { ruleset: definition, rulesetBasePath: '/path/to/config/dir', source: 'doc.yaml' })
102
+ ```
103
+
104
+ A custom function has the signature `(value, options, context) => { message: string, path?: JsonPath }[]`.
105
+
106
+ ### Auto-fix
107
+
108
+ `fixDocument` runs the linter and applies a `FixerRegistry` — fixers keyed by rule `code` that map a finding to a formatting-preserving text edit — to a fixpoint, then re-lints:
109
+
110
+ ```ts
111
+ import { fixDocument, type FixerRegistry } from '@amritk/lint'
112
+
113
+ const fixers: FixerRegistry = {
114
+ 'no-trailing-slash': {
115
+ fix: ({ diagnostic, data }) => {
116
+ const value = (data as Record<string, unknown>)[diagnostic.path[0] as string]
117
+ return typeof value === 'string' ? { op: 'setValue', path: diagnostic.path, value: value.replace(/\/$/, '') } : undefined
118
+ },
119
+ },
120
+ }
121
+
122
+ const { output, applied, remaining } = await fixDocument('host: api.example.com/\n', { ruleset, fixers })
123
+ // output === 'host: api.example.com\n'
124
+ ```
125
+
126
+ The engine ships no built-in fixers (rule codes are yours to define), so the default registry is empty and `fixDocument` is a no-op until you supply one.
127
+
128
+ ### Rendering findings
129
+
130
+ `lintDocument` returns structured `IDiagnostic[]` — each with a `code`, `message`, `path`, `severity`, `source`, and a zero-based `range`. **Rendering is the caller's job**: print them, serialize them to JSON, or map them to whatever your editor or CI consumes. The linter deliberately ships no output "formatter" layer (that is not the same thing as `prettier`/`biome format`, which reformat source).
131
+
132
+ ```ts
133
+ const findings = await lintDocument(source, { ruleset, source: 'doc.yaml' })
134
+ for (const f of findings) {
135
+ const { line, character } = f.range.start
136
+ console.log(`${f.source}:${line + 1}:${character + 1} ${f.code} ${f.message}`)
137
+ }
138
+ ```
139
+
140
+ ### CLI
141
+
142
+ The [`mjst`](../cli) binary exposes the linter as a subcommand, which prints a compact `file:line:col` report:
143
+
144
+ ```bash
145
+ mjst lint "**/*.{yaml,json}" -r .lint.yaml
146
+ ```
147
+
148
+ With no `-r`, it discovers a `.lint.{yaml,yml,json,js,mjs}` ruleset by walking up from each file. The exit code is derived from `--fail-severity` (default `error`). See the [CLI README](../cli/README.md#linting) for the full flag reference.
149
+
150
+ ---
151
+
152
+ ## Ruleset format
153
+
154
+ A ruleset is a plain object (authored as YAML, JSON, or a JS module):
155
+
156
+ | Field | Description |
157
+ | --- | --- |
158
+ | `rules` | Map of `name → rule`. A rule has `given` (one or more JSONPath expressions), `then` (a function to run, or a list), `severity` (`error`/`warn`/`info`/`hint`/`off`), and optional `message`, `description`, `formats`, `recommended`. |
159
+ | `then` | `{ function, field?, functionOptions? }` — `field` narrows the match to a child (`@key` targets the property name). |
160
+ | `extends` | A ruleset (or list) to inherit rules from: a file path or npm package. `[target, 'recommended' \| 'all' \| 'off']` controls what it contributes. |
161
+ | `functions` / `functionsDir` | Custom functions to load by name (default dir `functions/`). |
162
+ | `overrides` | Per-file-glob rule tweaks. |
163
+ | `aliases` | Reusable `given` fragments referenced as `#alias`. |
164
+
165
+ Built-in functions: `alphabetical`, `casing`, `defined`, `enumeration`, `falsy`, `length`, `pattern`, `schema`, `truthy`, `undefined`, `unreferencedReusableObject`, `xor`, `typedEnum`.
166
+
167
+ ---
168
+
169
+ ## API
170
+
171
+ | Export | What it does |
172
+ | --- | --- |
173
+ | `lintDocument(input, options?)` | Parse `input` and lint it against `options.ruleset`; returns `IDiagnostic[]`. |
174
+ | `lintDocumentWithResult(input, options?)` | Like `lintDocument`, but returns `{ diagnostics, output?, pluginData }` (including any plugin's rewritten `output`). |
175
+ | `fixDocument(input, options?)` | Lint and apply `options.fixers` to a fixpoint; returns `{ output, fixed, applied, remaining }`. |
176
+ | `createRuleset(definition?, basePath?)` | Normalize a ruleset definition into a runnable `Ruleset`, layering the built-in functions and resolving `extends`. |
177
+ | `resolveNamedRuleset(name, basePath?)` | Resolve an `extends` reference (file path or npm package) to its definition. |
178
+ | `builtinFunctions` | The registry of built-in rule functions. |
179
+
180
+ The engine internals (`createDocument`, `lint`, `query`, `validateRuleset`, `parseWithPointers`, `createFixPlugin`, `DiagnosticSeverity`, and the rule/diagnostic types) are re-exported from the package root for advanced use.
181
+
182
+ ---
183
+
184
+ ## License
185
+
186
+ MIT
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@amritk/lint",
3
+ "version": "0.0.0",
4
+ "description": "A fast, format-agnostic JSON/YAML style-guide linter with JSON Schema and custom rules.",
5
+ "module": "./dist/index.js",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "author": "amritk",
9
+ "keywords": [
10
+ "lint",
11
+ "linter",
12
+ "json",
13
+ "yaml",
14
+ "json-schema",
15
+ "style-guide",
16
+ "ruleset",
17
+ "typescript",
18
+ "mjst"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/amritk/mjst.git",
23
+ "directory": "packages/lint"
24
+ },
25
+ "homepage": "https://github.com/amritk/mjst/tree/main/packages/lint#readme",
26
+ "bugs": {
27
+ "url": "https://github.com/amritk/mjst/issues"
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "build": "tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f",
37
+ "types:check": "tsgo -p . --noEmit",
38
+ "test": "NODE_ENV=production vitest run --root ../.. packages/lint/"
39
+ },
40
+ "exports": {
41
+ "./package.json": "./package.json",
42
+ ".": {
43
+ "development": "./src/index.ts",
44
+ "types": "./dist/index.d.ts",
45
+ "default": "./dist/index.js"
46
+ }
47
+ },
48
+ "dependencies": {
49
+ "@amritk/runtime-validators": "workspace:*",
50
+ "@amritk/yaml": "workspace:*",
51
+ "jsonc-parser": "^3.3.1"
52
+ }
53
+ }