@amritk/lint 0.3.2 → 0.4.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 (119) hide show
  1. package/AI.md +55 -0
  2. package/dist/core/document.js +12 -10
  3. package/dist/core/formats.js +10 -13
  4. package/dist/core/glob.js +96 -118
  5. package/dist/core/index.d.ts +0 -1
  6. package/dist/core/index.js +30 -11
  7. package/dist/core/jsonpath.js +487 -561
  8. package/dist/core/lint.js +78 -85
  9. package/dist/core/plugin.js +21 -29
  10. package/dist/core/pointers.js +107 -151
  11. package/dist/core/ruleset.js +0 -0
  12. package/dist/core/runner.js +214 -272
  13. package/dist/core/types.js +4 -1
  14. package/dist/core/validate-ruleset.js +98 -112
  15. package/dist/fix/apply.d.ts +1 -1
  16. package/dist/fix/apply.js +58 -96
  17. package/dist/fix/index.js +7 -2
  18. package/dist/fix/plugin.js +15 -20
  19. package/dist/fix/types.d.ts +1 -1
  20. package/dist/functions/alphabetical.d.ts +1 -1
  21. package/dist/functions/alphabetical.js +39 -50
  22. package/dist/functions/casing.d.ts +1 -1
  23. package/dist/functions/casing.js +39 -45
  24. package/dist/functions/defined.d.ts +1 -1
  25. package/dist/functions/defined.js +7 -5
  26. package/dist/functions/enumeration.d.ts +1 -1
  27. package/dist/functions/enumeration.js +15 -25
  28. package/dist/functions/falsy.d.ts +1 -1
  29. package/dist/functions/falsy.js +7 -5
  30. package/dist/functions/index.d.ts +1 -1
  31. package/dist/functions/index.js +60 -44
  32. package/dist/functions/length.d.ts +1 -1
  33. package/dist/functions/length.js +22 -32
  34. package/dist/functions/or.d.ts +1 -1
  35. package/dist/functions/or.js +18 -24
  36. package/dist/functions/pattern.d.ts +1 -1
  37. package/dist/functions/pattern.js +39 -49
  38. package/dist/functions/schema.d.ts +1 -1
  39. package/dist/functions/schema.js +93 -119
  40. package/dist/functions/truthy.d.ts +1 -1
  41. package/dist/functions/truthy.js +7 -5
  42. package/dist/functions/typed-enum.d.ts +1 -1
  43. package/dist/functions/typed-enum.js +33 -36
  44. package/dist/functions/undefined.d.ts +1 -1
  45. package/dist/functions/undefined.js +7 -8
  46. package/dist/functions/unreferenced-reusable-object.d.ts +1 -1
  47. package/dist/functions/unreferenced-reusable-object.js +35 -47
  48. package/dist/functions/xor.d.ts +1 -1
  49. package/dist/functions/xor.js +13 -16
  50. package/dist/index.d.ts +17 -4
  51. package/dist/index.js +149 -164
  52. package/dist/parsers/edit-model.js +316 -444
  53. package/dist/parsers/index.d.ts +1 -1
  54. package/dist/parsers/index.js +23 -19
  55. package/dist/parsers/json.js +39 -37
  56. package/dist/parsers/lines.js +23 -26
  57. package/dist/parsers/types.js +9 -7
  58. package/dist/parsers/yaml.js +137 -204
  59. package/dist/rules/openapi/fixers.js +166 -221
  60. package/dist/rules/openapi/formats.js +26 -27
  61. package/dist/rules/openapi/functions/example-validation.d.ts +1 -1
  62. package/dist/rules/openapi/functions/example-validation.js +98 -138
  63. package/dist/rules/openapi/functions/helpers.js +8 -10
  64. package/dist/rules/openapi/functions/index.d.ts +1 -1
  65. package/dist/rules/openapi/functions/index.js +98 -72
  66. package/dist/rules/openapi/functions/oas-additional-operations.d.ts +1 -1
  67. package/dist/rules/openapi/functions/oas-additional-operations.js +16 -22
  68. package/dist/rules/openapi/functions/oas-discriminator.d.ts +1 -1
  69. package/dist/rules/openapi/functions/oas-discriminator.js +24 -22
  70. package/dist/rules/openapi/functions/oas-example-external-value.d.ts +1 -1
  71. package/dist/rules/openapi/functions/oas-example-external-value.js +13 -21
  72. package/dist/rules/openapi/functions/oas-example-value.d.ts +1 -1
  73. package/dist/rules/openapi/functions/oas-example-value.js +24 -27
  74. package/dist/rules/openapi/functions/oas-mutually-exclusive.d.ts +1 -1
  75. package/dist/rules/openapi/functions/oas-mutually-exclusive.js +15 -19
  76. package/dist/rules/openapi/functions/oas-no-nullable.d.ts +1 -1
  77. package/dist/rules/openapi/functions/oas-no-nullable.js +13 -21
  78. package/dist/rules/openapi/functions/oas-op-form-data-consume-check.d.ts +1 -1
  79. package/dist/rules/openapi/functions/oas-op-form-data-consume-check.js +21 -19
  80. package/dist/rules/openapi/functions/oas-op-id-unique.d.ts +1 -1
  81. package/dist/rules/openapi/functions/oas-op-id-unique.js +27 -27
  82. package/dist/rules/openapi/functions/oas-op-params.d.ts +1 -1
  83. package/dist/rules/openapi/functions/oas-op-params.js +36 -42
  84. package/dist/rules/openapi/functions/oas-op-security-defined.d.ts +1 -1
  85. package/dist/rules/openapi/functions/oas-op-security-defined.js +40 -39
  86. package/dist/rules/openapi/functions/oas-op-success-response.d.ts +1 -1
  87. package/dist/rules/openapi/functions/oas-op-success-response.js +11 -13
  88. package/dist/rules/openapi/functions/oas-path-param.d.ts +1 -1
  89. package/dist/rules/openapi/functions/oas-path-param.js +75 -96
  90. package/dist/rules/openapi/functions/oas-schema-example-deprecated.d.ts +1 -1
  91. package/dist/rules/openapi/functions/oas-schema-example-deprecated.js +33 -40
  92. package/dist/rules/openapi/functions/oas-schema.d.ts +1 -1
  93. package/dist/rules/openapi/functions/oas-schema.js +9 -14
  94. package/dist/rules/openapi/functions/oas-server-name-unique.d.ts +1 -1
  95. package/dist/rules/openapi/functions/oas-server-name-unique.js +21 -19
  96. package/dist/rules/openapi/functions/oas-server-variables.d.ts +1 -1
  97. package/dist/rules/openapi/functions/oas-server-variables.js +45 -49
  98. package/dist/rules/openapi/functions/oas-tag-defined.d.ts +1 -1
  99. package/dist/rules/openapi/functions/oas-tag-defined.js +20 -20
  100. package/dist/rules/openapi/functions/oas-tag-kind.d.ts +1 -1
  101. package/dist/rules/openapi/functions/oas-tag-kind.js +16 -16
  102. package/dist/rules/openapi/functions/oas-tag-parent-defined.d.ts +1 -1
  103. package/dist/rules/openapi/functions/oas-tag-parent-defined.js +40 -43
  104. package/dist/rules/openapi/functions/oas-tags-unique.d.ts +1 -1
  105. package/dist/rules/openapi/functions/oas-tags-unique.js +18 -16
  106. package/dist/rules/openapi/functions/oas-unused-component.d.ts +1 -1
  107. package/dist/rules/openapi/functions/oas-unused-component.js +48 -58
  108. package/dist/rules/openapi/functions/ref-siblings.d.ts +1 -1
  109. package/dist/rules/openapi/functions/ref-siblings.js +13 -11
  110. package/dist/rules/openapi/index.d.ts +2 -1
  111. package/dist/rules/openapi/index.js +99 -117
  112. package/dist/rules/openapi/oas.d.ts +1 -1
  113. package/dist/rules/openapi/oas.js +524 -536
  114. package/dist/rules/openapi/schemas/index.js +17 -33
  115. package/dist/rules/openapi/schemas/oas20.json +1 -1592
  116. package/dist/rules/openapi/schemas/oas30.json +1 -1651
  117. package/dist/rules/openapi/schemas/oas31.json +1 -1412
  118. package/dist/rules/openapi/schemas/oas32.json +1 -1684
  119. package/package.json +11 -6
package/dist/core/lint.js CHANGED
@@ -1,91 +1,84 @@
1
- import { detectFormat } from '../parsers/index.js';
2
- import { createDocument } from './document.js';
3
- import { runPlugins } from './plugin.js';
4
- import { createLinter } from './runner.js';
5
- import { DiagnosticSeverity } from './types.js';
1
+ import { detectFormat } from "../parsers/index.js";
2
+ import { createDocument } from "./document.js";
3
+ import { runPlugins } from "./plugin.js";
4
+ import { createLinter } from "./runner.js";
5
+ import { DiagnosticSeverity } from "./types.js";
6
6
  const toParserSeverity = (value) => {
7
- if (value === undefined)
8
- return undefined;
9
- if (typeof value === 'number')
10
- return value;
11
- if (value === 'off')
12
- return 'off';
13
- const names = {
14
- error: DiagnosticSeverity.Error,
15
- warn: DiagnosticSeverity.Warning,
16
- info: DiagnosticSeverity.Information,
17
- hint: DiagnosticSeverity.Hint,
18
- };
19
- return names[value];
7
+ if (value === void 0)
8
+ return void 0;
9
+ if (typeof value === "number")
10
+ return value;
11
+ if (value === "off")
12
+ return "off";
13
+ const names = {
14
+ error: DiagnosticSeverity.Error,
15
+ warn: DiagnosticSeverity.Warning,
16
+ info: DiagnosticSeverity.Information,
17
+ hint: DiagnosticSeverity.Hint
18
+ };
19
+ return names[value];
20
20
  };
21
21
  const byPosition = (a, b) => {
22
- const sa = a.source ?? '';
23
- const sb = b.source ?? '';
24
- if (sa !== sb)
25
- return sa < sb ? -1 : 1;
26
- return a.range.start.line - b.range.start.line || a.range.start.character - b.range.start.character;
22
+ const sa = a.source ?? "";
23
+ const sb = b.source ?? "";
24
+ if (sa !== sb)
25
+ return sa < sb ? -1 : 1;
26
+ return a.range.start.line - b.range.start.line || a.range.start.character - b.range.start.character;
27
27
  };
28
- /**
29
- * Lints `input` against a normalized `ruleset`, returning the full
30
- * {@link LintResult} (findings plus anything the configured `plugins` produced).
31
- * This is the format-agnostic pipeline — parse with source maps → resolve (via
32
- * the injected {@link LintResolver}) → run the ruleset → run plugins. A higher
33
- * level entry point (such as `@amritk/lint`) supplies the ruleset, and
34
- * optionally a resolver and format-skip predicate; {@link lint} is a thin
35
- * wrapper returning just the diagnostics.
36
- */
37
- export const lintWithResult = async (input, options) => {
38
- const { ruleset, resolve, skip, plugins, ...documentOptions } = options;
39
- const duplicateKeys = toParserSeverity(ruleset.parserOptions?.duplicateKeys);
40
- const incompatibleValues = toParserSeverity(ruleset.parserOptions?.incompatibleValues);
41
- const document = createDocument(input, {
42
- ...documentOptions,
43
- ...(duplicateKeys !== undefined ? { duplicateKeys } : {}),
44
- ...(incompatibleValues !== undefined ? { incompatibleValues } : {}),
45
- });
46
- // Skip unrecognized-format documents before paying for resolution or the run.
47
- if (skip?.(document.data))
48
- return { diagnostics: [], pluginData: {} };
49
- let resolved = document.data;
50
- let sources;
51
- let resolverDiagnostics = [];
52
- if (resolve) {
53
- const result = await resolve(document, { input });
54
- resolved = result.resolved;
55
- sources = result.sources;
56
- if (result.diagnostics)
57
- resolverDiagnostics = result.diagnostics;
58
- }
59
- const ruleResults = await createLinter(ruleset).run(document, { resolved, ...(sources ? { sources } : {}) });
60
- const parserResults = document.diagnostics.map((diagnostic) => {
61
- const result = {
62
- code: 'parser',
63
- message: diagnostic.message,
64
- path: diagnostic.path ?? [],
65
- severity: diagnostic.severity,
66
- range: diagnostic.range,
67
- };
68
- if (document.source !== undefined)
69
- result.source = document.source;
70
- return result;
71
- });
72
- const diagnostics = [...parserResults, ...resolverDiagnostics, ...ruleResults].sort(byPosition);
73
- if (!plugins || plugins.length === 0)
74
- return { diagnostics, pluginData: {} };
75
- const format = documentOptions.format ?? detectFormat(input);
76
- const run = runPlugins(plugins, diagnostics, {
77
- input,
78
- format,
79
- document,
80
- resolved,
81
- ruleset,
82
- ...(documentOptions.source !== undefined ? { source: documentOptions.source } : {}),
83
- });
84
- return {
85
- diagnostics: run.diagnostics,
86
- pluginData: run.data,
87
- ...(run.output !== undefined ? { output: run.output } : {}),
28
+ const lintWithResult = async (input, options) => {
29
+ const { ruleset, resolve, skip, plugins, ...documentOptions } = options;
30
+ const duplicateKeys = toParserSeverity(ruleset.parserOptions?.duplicateKeys);
31
+ const incompatibleValues = toParserSeverity(ruleset.parserOptions?.incompatibleValues);
32
+ const document = createDocument(input, {
33
+ ...documentOptions,
34
+ ...duplicateKeys !== void 0 ? { duplicateKeys } : {},
35
+ ...incompatibleValues !== void 0 ? { incompatibleValues } : {}
36
+ });
37
+ if (skip?.(document.data))
38
+ return { diagnostics: [], pluginData: {} };
39
+ let resolved = document.data;
40
+ let sources;
41
+ let resolverDiagnostics = [];
42
+ if (resolve) {
43
+ const result = await resolve(document, { input });
44
+ resolved = result.resolved;
45
+ sources = result.sources;
46
+ if (result.diagnostics)
47
+ resolverDiagnostics = result.diagnostics;
48
+ }
49
+ const ruleResults = await createLinter(ruleset).run(document, { resolved, ...sources ? { sources } : {} });
50
+ const parserResults = document.diagnostics.map((diagnostic) => {
51
+ const result = {
52
+ code: "parser",
53
+ message: diagnostic.message,
54
+ path: diagnostic.path ?? [],
55
+ severity: diagnostic.severity,
56
+ range: diagnostic.range
88
57
  };
58
+ if (document.source !== void 0)
59
+ result.source = document.source;
60
+ return result;
61
+ });
62
+ const diagnostics = [...parserResults, ...resolverDiagnostics, ...ruleResults].sort(byPosition);
63
+ if (!plugins || plugins.length === 0)
64
+ return { diagnostics, pluginData: {} };
65
+ const format = documentOptions.format ?? detectFormat(input);
66
+ const run = runPlugins(plugins, diagnostics, {
67
+ input,
68
+ format,
69
+ document,
70
+ resolved,
71
+ ruleset,
72
+ ...documentOptions.source !== void 0 ? { source: documentOptions.source } : {}
73
+ });
74
+ return {
75
+ diagnostics: run.diagnostics,
76
+ pluginData: run.data,
77
+ ...run.output !== void 0 ? { output: run.output } : {}
78
+ };
79
+ };
80
+ const lint = async (input, options) => (await lintWithResult(input, options)).diagnostics;
81
+ export {
82
+ lint,
83
+ lintWithResult
89
84
  };
90
- /** Lints `input` against a normalized `ruleset` and returns just the findings. */
91
- export const lint = async (input, options) => (await lintWithResult(input, options)).diagnostics;
@@ -1,31 +1,23 @@
1
- /**
2
- * Runs `plugins` in order over `diagnostics`. Each plugin sees the (possibly
3
- * already transformed) diagnostics from the previous one; when a plugin returns
4
- * `output`, later plugins and the context see that rewritten text as `input`.
5
- *
6
- * Note: only `input` (and the diagnostics) are refreshed between plugins. The
7
- * `document` and `resolved` tree in the context are the *originally* parsed ones,
8
- * so after a plugin rewrites `output` those become stale relative to the new
9
- * text. A plugin that must operate on the up-to-date parsed tree should re-parse
10
- * `context.input` itself rather than trust `document`/`resolved`.
11
- */
12
- export const runPlugins = (plugins, diagnostics, context) => {
13
- let currentDiagnostics = diagnostics;
14
- let currentInput = context.input;
15
- let output;
16
- const data = {};
17
- for (const plugin of plugins) {
18
- const result = plugin.afterLint?.(currentDiagnostics, { ...context, input: currentInput });
19
- if (!result)
20
- continue;
21
- if (result.diagnostics)
22
- currentDiagnostics = result.diagnostics;
23
- if (result.output !== undefined) {
24
- output = result.output;
25
- currentInput = result.output;
26
- }
27
- if (result.data !== undefined)
28
- data[plugin.name] = result.data;
1
+ const runPlugins = (plugins, diagnostics, context) => {
2
+ let currentDiagnostics = diagnostics;
3
+ let currentInput = context.input;
4
+ let output;
5
+ const data = {};
6
+ for (const plugin of plugins) {
7
+ const result = plugin.afterLint?.(currentDiagnostics, { ...context, input: currentInput });
8
+ if (!result)
9
+ continue;
10
+ if (result.diagnostics)
11
+ currentDiagnostics = result.diagnostics;
12
+ if (result.output !== void 0) {
13
+ output = result.output;
14
+ currentInput = result.output;
29
15
  }
30
- return { diagnostics: currentDiagnostics, data, ...(output !== undefined ? { output } : {}) };
16
+ if (result.data !== void 0)
17
+ data[plugin.name] = result.data;
18
+ }
19
+ return { diagnostics: currentDiagnostics, data, ...output !== void 0 ? { output } : {} };
20
+ };
21
+ export {
22
+ runPlugins
31
23
  };
@@ -1,169 +1,125 @@
1
- import { dirname, resolve as resolvePath } from 'node:path';
2
- const isContainer = (value) => typeof value === 'object' && value !== null;
3
- /**
4
- * Decodes one JSON-pointer segment the way `getByPointer` does: percent-escapes
5
- * first (pointers arrive inside URI-reference `$ref`s), then the JSON-pointer
6
- * escapes `~1`/`~0`, then all-digit segments become numbers. Shared by both the
7
- * `#/...` and fragment parsers so they decode identically.
8
- */
1
+ import { dirname, resolve as resolvePath } from "node:path";
2
+ const isContainer = (value) => typeof value === "object" && value !== null;
9
3
  const decodeSegment = (segment) => {
10
- let decoded = segment;
11
- try {
12
- decoded = decodeURIComponent(segment);
13
- }
14
- catch {
15
- // Leave invalid percent-escapes as-is rather than throwing.
16
- }
17
- decoded = decoded.replace(/~1/g, '/').replace(/~0/g, '~');
18
- return /^\d+$/.test(decoded) ? Number(decoded) : decoded;
4
+ let decoded = segment;
5
+ try {
6
+ decoded = decodeURIComponent(segment);
7
+ } catch {
8
+ }
9
+ decoded = decoded.replace(/~1/g, "/").replace(/~0/g, "~");
10
+ return /^\d+$/.test(decoded) ? Number(decoded) : decoded;
19
11
  };
20
- /** Parses an internal JSON pointer (`#/a/b`) into a path; returns undefined for external refs. */
21
- export const pointerToPath = (pointer) => {
22
- if (pointer === '#')
23
- return [];
24
- if (!pointer.startsWith('#/'))
25
- return undefined;
26
- return pointer.slice(2).split('/').map(decodeSegment);
12
+ const pointerToPath = (pointer) => {
13
+ if (pointer === "#")
14
+ return [];
15
+ if (!pointer.startsWith("#/"))
16
+ return void 0;
17
+ return pointer.slice(2).split("/").map(decodeSegment);
27
18
  };
28
19
  const isRemote = (location) => /^https?:\/\//i.test(location);
29
- /**
30
- * Resolves the location of `ref` (its file/URL part) relative to `base`, exactly
31
- * as `@amritk/resolve-refs` does, so the keys we look up in the source set match
32
- * the documents the resolver loaded.
33
- */
34
20
  const joinLocation = (base, ref) => {
35
- if (isRemote(ref))
36
- return ref;
37
- if (isRemote(base))
38
- return new URL(ref, base).href;
39
- return resolvePath(dirname(base), ref);
21
+ if (isRemote(ref))
22
+ return ref;
23
+ if (isRemote(base))
24
+ return new URL(ref, base).href;
25
+ return resolvePath(dirname(base), ref);
40
26
  };
41
- /** Splits a `$ref` into its document part and JSON-pointer fragment (without `#`). */
42
27
  const splitRef = (ref) => {
43
- const hashIndex = ref.indexOf('#');
44
- return {
45
- filePart: hashIndex === -1 ? ref : ref.slice(0, hashIndex),
46
- fragment: hashIndex === -1 ? '' : ref.slice(hashIndex + 1),
47
- };
28
+ const hashIndex = ref.indexOf("#");
29
+ return {
30
+ filePart: hashIndex === -1 ? ref : ref.slice(0, hashIndex),
31
+ fragment: hashIndex === -1 ? "" : ref.slice(hashIndex + 1)
32
+ };
48
33
  };
49
- /**
50
- * Parses a JSON-pointer fragment (the part after `#`, e.g. `/a/b`) into a path,
51
- * mirroring `getByPointer`'s decoding (`%XX` then `~1`/`~0`) so cross-file
52
- * pointers resolve to the same nodes the resolver inlined.
53
- */
54
34
  const fragmentToPath = (fragment) => {
55
- if (fragment === '' || fragment === '/')
56
- return [];
57
- return fragment.replace(/^\//, '').split('/').map(decodeSegment);
35
+ if (fragment === "" || fragment === "/")
36
+ return [];
37
+ return fragment.replace(/^\//, "").split("/").map(decodeSegment);
58
38
  };
59
39
  const getAtPath = (root, path) => {
60
- let node = root;
61
- for (const segment of path) {
62
- if (!isContainer(node))
63
- return undefined;
64
- node = node[segment];
65
- }
66
- return node;
40
+ let node = root;
41
+ for (const segment of path) {
42
+ if (!isContainer(node))
43
+ return void 0;
44
+ node = node[segment];
45
+ }
46
+ return node;
67
47
  };
68
- /**
69
- * Translates a path into the *resolved* (dereferenced) document back into the
70
- * equivalent path in the *original* source document, following internal `$ref`s.
71
- * This lets findings produced against the resolved tree resolve to the exact
72
- * line:column of the original `$ref` target. External refs stop the walk at the
73
- * `$ref` site.
74
- */
75
- export const resolveSourcePath = (root, path) => {
76
- let originalPath = [];
77
- let node = root;
78
- const followRefs = () => {
79
- let guard = 0;
80
- while (isContainer(node) && !Array.isArray(node) && typeof node['$ref'] === 'string' && guard++ < 100) {
81
- const target = pointerToPath(node['$ref']);
82
- if (!target)
83
- return;
84
- originalPath = [...target];
85
- node = getAtPath(root, target);
86
- }
87
- };
88
- followRefs();
89
- for (const segment of path) {
90
- if (!isContainer(node))
91
- break;
92
- originalPath.push(segment);
93
- node = node[segment];
94
- followRefs();
48
+ const resolveSourcePath = (root, path) => {
49
+ let originalPath = [];
50
+ let node = root;
51
+ const followRefs = () => {
52
+ let guard = 0;
53
+ while (isContainer(node) && !Array.isArray(node) && typeof node["$ref"] === "string" && guard++ < 100) {
54
+ const target = pointerToPath(node["$ref"]);
55
+ if (!target)
56
+ return;
57
+ originalPath = [...target];
58
+ node = getAtPath(root, target);
95
59
  }
96
- return originalPath;
97
- };
98
- /**
99
- * Cross-document generalization of {@link resolveSourcePath}: translates a path
100
- * in the *resolved* (dereferenced) tree back to the document it actually came
101
- * from and the path within it, following both internal (`#/...`) and external
102
- * (other-file / remote) `$ref`s through the {@link IDocumentRegistry}. This is
103
- * what lets a finding on a node inlined from `./pet.yaml` report `pet.yaml`'s own
104
- * line:column instead of the `$ref` site in the root.
105
- *
106
- * The walk re-derives the resolver's traversal over the *unresolved* documents,
107
- * so a `$ref` whose target file is not in the registry (e.g. it failed to load)
108
- * stops the walk at the last known location rather than guessing. Prefer
109
- * {@link resolveSourceOriginFromMap} when the resolver supplies an origin map —
110
- * it avoids re-deriving this traversal.
111
- */
112
- export const resolveSourceOrigin = (registry, path) => {
113
- let location = registry.rootLocation;
114
- let root = registry.get(location)?.data;
115
- let originalPath = [];
116
- let node = root;
117
- const followRefs = () => {
118
- let guard = 0;
119
- while (isContainer(node) && !Array.isArray(node) && typeof node['$ref'] === 'string' && guard++ < 1000) {
120
- const { filePart, fragment } = splitRef(node['$ref']);
121
- if (filePart !== '') {
122
- const target = joinLocation(location, filePart);
123
- const doc = registry.get(target);
124
- if (!doc)
125
- return;
126
- location = target;
127
- root = doc.data;
128
- }
129
- originalPath = fragmentToPath(fragment);
130
- node = getAtPath(root, originalPath);
131
- }
132
- };
60
+ };
61
+ followRefs();
62
+ for (const segment of path) {
63
+ if (!isContainer(node))
64
+ break;
65
+ originalPath.push(segment);
66
+ node = node[segment];
133
67
  followRefs();
134
- for (const segment of path) {
135
- if (!isContainer(node))
136
- break;
137
- originalPath.push(segment);
138
- node = node[segment];
139
- followRefs();
68
+ }
69
+ return originalPath;
70
+ };
71
+ const resolveSourceOrigin = (registry, path) => {
72
+ let location = registry.rootLocation;
73
+ let root = registry.get(location)?.data;
74
+ let originalPath = [];
75
+ let node = root;
76
+ const followRefs = () => {
77
+ let guard = 0;
78
+ while (isContainer(node) && !Array.isArray(node) && typeof node["$ref"] === "string" && guard++ < 1e3) {
79
+ const { filePart, fragment } = splitRef(node["$ref"]);
80
+ if (filePart !== "") {
81
+ const target = joinLocation(location, filePart);
82
+ const doc = registry.get(target);
83
+ if (!doc)
84
+ return;
85
+ location = target;
86
+ root = doc.data;
87
+ }
88
+ originalPath = fragmentToPath(fragment);
89
+ node = getAtPath(root, originalPath);
140
90
  }
141
- return { location, path: originalPath };
91
+ };
92
+ followRefs();
93
+ for (const segment of path) {
94
+ if (!isContainer(node))
95
+ break;
96
+ originalPath.push(segment);
97
+ node = node[segment];
98
+ followRefs();
99
+ }
100
+ return { location, path: originalPath };
142
101
  };
143
- /**
144
- * Single-walk equivalent of {@link resolveSourceOrigin} that uses the resolver's
145
- * per-node origin map instead of re-walking the unresolved documents. We descend
146
- * the *resolved* tree along `path`; whenever we step onto a node the resolver
147
- * stamped (it was inlined from a `$ref`), we re-base onto that node's origin
148
- * document and in-file path, then keep appending the remaining segments. Nodes
149
- * straight from the root carry no stamp, so they stay attributed to the root.
150
- */
151
- export const resolveSourceOriginFromMap = (resolved, origins, rootLocation, path) => {
152
- let location = rootLocation;
153
- let inFilePath = [];
154
- let node = resolved;
155
- for (const segment of path) {
156
- if (!isContainer(node))
157
- break;
158
- node = node[segment];
159
- const stamp = isContainer(node) ? origins.get(node) : undefined;
160
- if (stamp) {
161
- location = stamp.location;
162
- inFilePath = [...stamp.pointer];
163
- }
164
- else {
165
- inFilePath.push(segment);
166
- }
102
+ const resolveSourceOriginFromMap = (resolved, origins, rootLocation, path) => {
103
+ let location = rootLocation;
104
+ let inFilePath = [];
105
+ let node = resolved;
106
+ for (const segment of path) {
107
+ if (!isContainer(node))
108
+ break;
109
+ node = node[segment];
110
+ const stamp = isContainer(node) ? origins.get(node) : void 0;
111
+ if (stamp) {
112
+ location = stamp.location;
113
+ inFilePath = [...stamp.pointer];
114
+ } else {
115
+ inFilePath.push(segment);
167
116
  }
168
- return { location, path: inFilePath };
117
+ }
118
+ return { location, path: inFilePath };
119
+ };
120
+ export {
121
+ pointerToPath,
122
+ resolveSourceOrigin,
123
+ resolveSourceOriginFromMap,
124
+ resolveSourcePath
169
125
  };
Binary file