@amritk/lint 0.2.0 → 0.3.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 (68) hide show
  1. package/README.md +18 -0
  2. package/dist/core/glob.d.ts +1 -1
  3. package/dist/core/glob.js +89 -5
  4. package/dist/core/index.d.ts +1 -1
  5. package/dist/core/index.js +1 -1
  6. package/dist/core/jsonpath.d.ts +17 -1
  7. package/dist/core/jsonpath.js +218 -23
  8. package/dist/core/lint.d.ts +15 -8
  9. package/dist/core/lint.js +12 -3
  10. package/dist/core/plugin.d.ts +6 -0
  11. package/dist/core/plugin.js +6 -0
  12. package/dist/core/pointers.js +15 -15
  13. package/dist/core/ruleset.js +0 -0
  14. package/dist/core/runner.d.ts +6 -1
  15. package/dist/core/runner.js +127 -43
  16. package/dist/core/types.d.ts +17 -2
  17. package/dist/core/validate-ruleset.js +16 -0
  18. package/dist/fix/apply.d.ts +8 -2
  19. package/dist/fix/apply.js +68 -18
  20. package/dist/functions/alphabetical.js +40 -13
  21. package/dist/functions/casing.js +27 -5
  22. package/dist/functions/enumeration.d.ts +5 -3
  23. package/dist/functions/enumeration.js +18 -1
  24. package/dist/functions/index.d.ts +1 -0
  25. package/dist/functions/index.js +3 -0
  26. package/dist/functions/length.d.ts +13 -3
  27. package/dist/functions/length.js +11 -3
  28. package/dist/functions/or.d.ts +11 -0
  29. package/dist/functions/or.js +25 -0
  30. package/dist/functions/pattern.d.ts +5 -3
  31. package/dist/functions/pattern.js +42 -9
  32. package/dist/functions/schema.d.ts +13 -0
  33. package/dist/functions/schema.js +95 -2
  34. package/dist/functions/typed-enum.js +7 -1
  35. package/dist/functions/unreferenced-reusable-object.d.ts +7 -1
  36. package/dist/functions/unreferenced-reusable-object.js +18 -3
  37. package/dist/functions/xor.js +8 -1
  38. package/dist/index.js +9 -1
  39. package/dist/parsers/edit-model.d.ts +15 -0
  40. package/dist/parsers/edit-model.js +210 -41
  41. package/dist/parsers/types.d.ts +14 -2
  42. package/dist/parsers/yaml.d.ts +10 -0
  43. package/dist/parsers/yaml.js +174 -26
  44. package/dist/rules/openapi/fixers.js +63 -4
  45. package/dist/rules/openapi/formats.js +11 -4
  46. package/dist/rules/openapi/functions/example-validation.d.ts +16 -3
  47. package/dist/rules/openapi/functions/example-validation.js +102 -39
  48. package/dist/rules/openapi/functions/helpers.d.ts +1 -0
  49. package/dist/rules/openapi/functions/helpers.js +5 -0
  50. package/dist/rules/openapi/functions/index.d.ts +3 -1
  51. package/dist/rules/openapi/functions/index.js +7 -1
  52. package/dist/rules/openapi/functions/oas-additional-operations.js +5 -5
  53. package/dist/rules/openapi/functions/oas-example-external-value.d.ts +11 -0
  54. package/dist/rules/openapi/functions/oas-example-external-value.js +23 -0
  55. package/dist/rules/openapi/functions/oas-no-nullable.d.ts +13 -0
  56. package/dist/rules/openapi/functions/oas-no-nullable.js +22 -0
  57. package/dist/rules/openapi/functions/oas-op-id-unique.js +4 -2
  58. package/dist/rules/openapi/functions/oas-op-params.d.ts +7 -1
  59. package/dist/rules/openapi/functions/oas-op-params.js +35 -10
  60. package/dist/rules/openapi/functions/oas-op-security-defined.js +2 -2
  61. package/dist/rules/openapi/functions/oas-op-success-response.js +6 -1
  62. package/dist/rules/openapi/functions/oas-path-param.d.ts +10 -1
  63. package/dist/rules/openapi/functions/oas-path-param.js +87 -26
  64. package/dist/rules/openapi/functions/oas-server-variables.d.ts +6 -1
  65. package/dist/rules/openapi/functions/oas-server-variables.js +31 -2
  66. package/dist/rules/openapi/functions/oas-unused-component.js +14 -1
  67. package/dist/rules/openapi/oas.js +82 -25
  68. package/package.json +12 -4
@@ -1,6 +1,16 @@
1
- const OPERATIONS = '$.paths[*][get,put,post,delete,options,head,patch,trace]';
2
- const WEBHOOK_OPERATIONS = '$.webhooks[*][get,put,post,delete,options,head,patch,trace]';
1
+ // `query` is a fixed operation field from OpenAPI 3.2 onward. Including it in the
2
+ // operation selectors is harmless on older versions (which never carry a `query`
3
+ // operation) and lets the operation-scoped rules cover 3.2 `query` operations.
4
+ const OPERATIONS = '$.paths[*][get,put,post,delete,options,head,patch,trace,query]';
5
+ const WEBHOOK_OPERATIONS = '$.webhooks[*][get,put,post,delete,options,head,patch,trace,query]';
3
6
  const OPERATION_ID_URL_SAFE = "^[A-Za-z0-9-._~:/?#\\[\\]@!\\$&'()*+,;=]*$";
7
+ // Matches every Schema Object that carries an inline `example` or `default` and
8
+ // looks like a schema (it has a JSON Schema keyword), while skipping the
9
+ // `properties`/`patternProperties` *maps* — whose keys can coincidentally be
10
+ // named like schema keywords. A single filter given (rather than a `$..example^`
11
+ // / `$..default^` union) keeps each schema node matched exactly once, so the
12
+ // `oasSchemaExample` finding for a schema is never duplicated.
13
+ const SCHEMA_EXAMPLE_GIVEN = "$..[?(@property !== 'properties' && @property !== 'patternProperties' && @ && (@.example !== void 0 || @.default !== void 0) && (@.type || @.enum || @.format || @.$ref || @.properties || @.items || @.allOf || @.anyOf || @.oneOf))]";
4
14
  /** Rules shared across OpenAPI v2 and v3. */
5
15
  const sharedRules = {
6
16
  'contact-properties': {
@@ -56,12 +66,13 @@ const sharedRules = {
56
66
  },
57
67
  'no-eval-in-markdown': {
58
68
  description: 'Markdown descriptions must not contain "eval(".',
59
- given: '$..description',
69
+ // Spectral scans both `description` and `title` markdown fields.
70
+ given: ['$..description', '$..title'],
60
71
  then: { function: 'pattern', functionOptions: { notMatch: 'eval\\(' } },
61
72
  },
62
73
  'no-script-tags-in-markdown': {
63
74
  description: 'Markdown descriptions must not contain <script> tags.',
64
- given: '$..description',
75
+ given: ['$..description', '$..title'],
65
76
  then: { function: 'pattern', functionOptions: { notMatch: '<script' } },
66
77
  },
67
78
  'openapi-tags': {
@@ -166,7 +177,12 @@ const sharedRules = {
166
177
  then: { function: 'typedEnum' },
167
178
  },
168
179
  'array-items': {
169
- description: 'Schemas of type array must define items.',
180
+ // Not part of `spectral:oas` a Loupe extension. `type: array` without
181
+ // `items` is valid in JSON Schema 2020-12 (OpenAPI 3.1/3.2), so this is gated
182
+ // to the versions where `items` is genuinely required (2.0 and 3.0.x) to
183
+ // avoid false positives on valid 3.1+ tuple/unconstrained-array schemas.
184
+ description: 'Schemas of type array must define items (OpenAPI 2.0 / 3.0 only; Loupe extension).',
185
+ formats: ['oas2', 'oas3_0'],
170
186
  given: "$..[?(@ && @.type === 'array')]",
171
187
  severity: 'error',
172
188
  then: { field: 'items', function: 'defined' },
@@ -195,8 +211,15 @@ const oas2Rules = {
195
211
  'oas2-api-schemes': {
196
212
  description: 'OpenAPI schemes must be present and non-empty.',
197
213
  formats: ['oas2'],
198
- given: '$.schemes',
199
- then: { function: 'length', functionOptions: { min: 1 } },
214
+ // Target the root and the `schemes` field so a *missing* `schemes` (not just
215
+ // an empty array) is reported — `given: '$.schemes'` matches nothing when the
216
+ // key is absent, so the rule never fired for a document with no schemes.
217
+ given: '$',
218
+ then: {
219
+ field: 'schemes',
220
+ function: 'schema',
221
+ functionOptions: { schema: { type: 'array', minItems: 1 } },
222
+ },
200
223
  },
201
224
  'oas2-discriminator': {
202
225
  description: 'Discriminator must reference a required property.',
@@ -250,16 +273,18 @@ const oas2Rules = {
250
273
  'oas2-valid-schema-example': {
251
274
  description: 'Schema examples must be valid against their schema.',
252
275
  formats: ['oas2'],
253
- given: '$..example^',
276
+ given: SCHEMA_EXAMPLE_GIVEN,
254
277
  severity: 'error',
255
278
  then: { function: 'oasSchemaExample' },
256
279
  },
257
280
  'oas2-valid-media-example': {
258
281
  description: 'Media type examples must be valid against their schema.',
259
282
  formats: ['oas2'],
260
- given: ['$..responses[*]', '$..parameters[*]'],
283
+ // In OpenAPI 2.0 examples live on the Response Object as a MIME-type → value
284
+ // map alongside a sibling `schema`, so target responses that have both.
285
+ given: '$..responses..[?(@ && @.schema && @.examples)]',
261
286
  severity: 'error',
262
- then: { function: 'oasMediaExample' },
287
+ then: { function: 'oasMediaExample', functionOptions: { oasVersion: 2 } },
263
288
  },
264
289
  'oas2-schema': {
265
290
  description: 'Validate structure of OpenAPI v2 specification.',
@@ -276,14 +301,26 @@ const oas3Rules = {
276
301
  'oas3-api-servers': {
277
302
  description: 'OpenAPI 3 documents must have a non-empty servers array.',
278
303
  formats: ['oas3'],
279
- given: '$.servers',
280
- then: { function: 'length', functionOptions: { min: 1 } },
304
+ // Target the root and the `servers` field so a *missing* `servers` (not just
305
+ // an empty array) is reported — `given: '$.servers'` matches nothing when the
306
+ // key is absent, so the rule never fired for a document with no servers.
307
+ given: '$',
308
+ then: {
309
+ field: 'servers',
310
+ function: 'schema',
311
+ functionOptions: { schema: { type: 'array', minItems: 1, items: { type: 'object' } } },
312
+ },
281
313
  },
282
314
  'oas3-examples-value-or-externalValue': {
283
315
  description: 'Example objects must use either value or externalValue, not both.',
284
316
  formats: ['oas3'],
285
- given: ['$.components.examples[*]', '$..content[*].examples[*]', '$..parameters[*].examples[*]'],
286
- then: { function: 'xor', functionOptions: { properties: ['value', 'externalValue'] } },
317
+ given: [
318
+ '$.components.examples[*]',
319
+ '$..content[*].examples[*]',
320
+ '$..parameters[*].examples[*]',
321
+ '$..headers[*].examples[*]',
322
+ ],
323
+ then: { function: 'oasExampleExternalValue' },
287
324
  },
288
325
  'oas3-operation-security-defined': {
289
326
  description: 'Operation security must reference defined securitySchemes.',
@@ -316,7 +353,16 @@ const oas3Rules = {
316
353
  'oas3-server-variables': {
317
354
  description: 'Server variables must be defined and used.',
318
355
  formats: ['oas3'],
319
- given: '$.servers[*]',
356
+ // Servers can appear at the root, on a path item, on an operation, and on a
357
+ // Link Object, so cover every location rather than just the root servers.
358
+ given: [
359
+ '$.servers[*]',
360
+ '$.paths[*].servers[*]',
361
+ '$.paths[*][*].servers[*]',
362
+ '$..links[*].server',
363
+ '$.webhooks[*].servers[*]',
364
+ '$.webhooks[*][*].servers[*]',
365
+ ],
320
366
  severity: 'error',
321
367
  then: { function: 'oasServerVariables' },
322
368
  },
@@ -337,16 +383,17 @@ const oas3Rules = {
337
383
  'oas3-valid-schema-example': {
338
384
  description: 'Schema examples must be valid against their schema.',
339
385
  formats: ['oas3'],
340
- given: '$..example^',
386
+ given: SCHEMA_EXAMPLE_GIVEN,
341
387
  severity: 'error',
342
388
  then: { function: 'oasSchemaExample' },
343
389
  },
344
390
  'oas3-valid-media-example': {
345
391
  description: 'Media type examples must be valid against their schema.',
346
392
  formats: ['oas3'],
347
- given: '$..content[*]',
393
+ // Spectral validates examples on media types, parameters, and headers.
394
+ given: ['$..content[*]', '$..parameters[*]', '$..headers[*]'],
348
395
  severity: 'error',
349
- then: { function: 'oasMediaExample' },
396
+ then: { function: 'oasMediaExample', functionOptions: { oasVersion: 3 } },
350
397
  },
351
398
  'oas3-schema': {
352
399
  description: 'Validate structure of OpenAPI v3.0.x specification.',
@@ -362,24 +409,29 @@ const oas3Rules = {
362
409
  const oas31Rules = {
363
410
  'oas3_1-servers-in-webhook': {
364
411
  description: 'Webhooks must not define servers.',
365
- formats: ['oas3_1'],
366
- given: WEBHOOK_OPERATIONS,
412
+ // Servers can sit on the webhook Path Item itself (`$.webhooks[*]`) as well as
413
+ // on each operation, so target both. Also applies to 3.2.
414
+ formats: ['oas3_1', 'oas3_2'],
415
+ given: [WEBHOOK_OPERATIONS, '$.webhooks[*]'],
367
416
  then: { field: 'servers', function: 'falsy' },
368
417
  },
369
418
  'oas3_1-callbacks-in-webhook': {
370
419
  description: 'Webhooks must not define callbacks.',
371
- formats: ['oas3_1'],
420
+ formats: ['oas3_1', 'oas3_2'],
372
421
  given: WEBHOOK_OPERATIONS,
373
422
  then: { field: 'callbacks', function: 'falsy' },
374
423
  },
375
424
  'oas3_1-no-nullable': {
376
425
  // `nullable` was removed in OpenAPI 3.1 (JSON Schema 2020-12 uses a `null`
377
426
  // type instead) and stays gone in 3.2 — mirroring the oas2-anyOf/oneOf
378
- // "feature not available in this version" rules.
427
+ // "feature not available in this version" rules. A custom function (targeting
428
+ // the parent of a `nullable` key) is used instead of `$..nullable` + `falsy`
429
+ // so a property literally named `nullable` is not flagged and `nullable: false`
430
+ // still is (so the migration fixer can drop it).
379
431
  description: 'nullable is not available in OpenAPI 3.1 or later; use a "null" type instead.',
380
432
  formats: ['oas3_1', 'oas3_2'],
381
- given: '$..nullable',
382
- then: { function: 'falsy' },
433
+ given: '$..nullable^',
434
+ then: { function: 'oasNoNullable' },
383
435
  },
384
436
  'oas3_1-license-identifier': {
385
437
  // The License Object's `identifier` (SPDX) field was added in 3.1 and is
@@ -474,7 +526,12 @@ const oas32Rules = {
474
526
  // 3.x-wide oas3-examples-value-or-externalValue rule.)
475
527
  description: 'Example object dataValue/serializedValue must not be combined with value or externalValue.',
476
528
  formats: ['oas3_2'],
477
- given: ['$.components.examples[*]', '$..content[*].examples[*]', '$..parameters[*].examples[*]'],
529
+ given: [
530
+ '$.components.examples[*]',
531
+ '$..content[*].examples[*]',
532
+ '$..parameters[*].examples[*]',
533
+ '$..headers[*].examples[*]',
534
+ ],
478
535
  severity: 'error',
479
536
  then: { function: 'oasExampleValue' },
480
537
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/lint",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A fast, format-agnostic JSON/YAML style-guide linter with JSON Schema and custom rules.",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -35,7 +35,9 @@
35
35
  "scripts": {
36
36
  "build": "tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node scripts/copy-schema-json.mjs",
37
37
  "types:check": "tsgo -p . --noEmit",
38
- "test": "NODE_ENV=production vitest run --root ../.. packages/lint/"
38
+ "test": "NODE_ENV=production vitest run --root ../.. packages/lint/",
39
+ "prebench": "bun run --filter='@amritk/runtime-validators' --filter='@amritk/yaml' --filter='@amritk/resolve-refs' build",
40
+ "bench": "bun run ./bench/run.ts"
39
41
  },
40
42
  "exports": {
41
43
  "./package.json": "./package.json",
@@ -49,8 +51,14 @@
49
51
  }
50
52
  },
51
53
  "dependencies": {
52
- "@amritk/runtime-validators": "0.6.4",
53
- "@amritk/yaml": "0.2.3",
54
+ "@amritk/runtime-validators": "0.7.0",
55
+ "@amritk/yaml": "0.3.0",
54
56
  "jsonc-parser": "^3.3.1"
57
+ },
58
+ "devDependencies": {
59
+ "@amritk/resolve-refs": "0.4.0",
60
+ "@stoplight/spectral-core": "^1.23.1",
61
+ "@stoplight/spectral-parsers": "^1.0.5",
62
+ "@stoplight/spectral-rulesets": "^1.22.6"
55
63
  }
56
64
  }