@pajecawav/json2code 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +178 -2
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +126 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/core/config.mjs +53 -0
- package/dist/core/config.mjs.map +1 -0
- package/dist/core/discovery/index.d.mts +10 -0
- package/dist/core/discovery/index.d.mts.map +1 -0
- package/dist/core/discovery/index.mjs +83 -0
- package/dist/core/discovery/index.mjs.map +1 -0
- package/dist/core/generate.d.mts +11 -0
- package/dist/core/generate.d.mts.map +1 -0
- package/dist/core/generate.mjs +89 -0
- package/dist/core/generate.mjs.map +1 -0
- package/dist/core/ir/types.d.mts +100 -0
- package/dist/core/ir/types.d.mts.map +1 -0
- package/dist/core/ir/visit.d.mts +8 -0
- package/dist/core/ir/visit.d.mts.map +1 -0
- package/dist/core/ir/visit.mjs +38 -0
- package/dist/core/ir/visit.mjs.map +1 -0
- package/dist/core/linker/index.d.mts +32 -0
- package/dist/core/linker/index.d.mts.map +1 -0
- package/dist/core/linker/index.mjs +103 -0
- package/dist/core/linker/index.mjs.map +1 -0
- package/dist/core/printer/abstract.d.mts +26 -0
- package/dist/core/printer/abstract.d.mts.map +1 -0
- package/dist/core/types.d.mts +74 -0
- package/dist/core/types.d.mts.map +1 -0
- package/dist/core/types.mjs +27 -0
- package/dist/core/types.mjs.map +1 -0
- package/dist/core/walker/abstract.d.mts +18 -0
- package/dist/core/walker/abstract.d.mts.map +1 -0
- package/dist/core/walker/json-schema.d.mts +10 -0
- package/dist/core/walker/json-schema.d.mts.map +1 -0
- package/dist/core/walker/json-schema.mjs +311 -0
- package/dist/core/walker/json-schema.mjs.map +1 -0
- package/dist/core/walker/ref-resolver.d.mts +22 -0
- package/dist/core/walker/ref-resolver.d.mts.map +1 -0
- package/dist/core/walker/ref-resolver.mjs +66 -0
- package/dist/core/walker/ref-resolver.mjs.map +1 -0
- package/dist/core/writer/index.d.mts +19 -0
- package/dist/core/writer/index.d.mts.map +1 -0
- package/dist/core/writer/index.mjs +84 -0
- package/dist/core/writer/index.mjs.map +1 -0
- package/dist/diagnostics/errors.d.mts +39 -0
- package/dist/diagnostics/errors.d.mts.map +1 -0
- package/dist/diagnostics/errors.mjs +50 -0
- package/dist/diagnostics/errors.mjs.map +1 -0
- package/dist/diagnostics/logger.d.mts +10 -0
- package/dist/diagnostics/logger.d.mts.map +1 -0
- package/dist/diagnostics/logger.mjs +18 -0
- package/dist/diagnostics/logger.mjs.map +1 -0
- package/dist/index.d.mts +24 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +26 -0
- package/dist/index.mjs.map +1 -0
- package/dist/package.mjs +6 -0
- package/dist/package.mjs.map +1 -0
- package/dist/targets/index.d.mts +9 -0
- package/dist/targets/index.d.mts.map +1 -0
- package/dist/targets/index.mjs +30 -0
- package/dist/targets/index.mjs.map +1 -0
- package/dist/targets/shared.mjs +137 -0
- package/dist/targets/shared.mjs.map +1 -0
- package/dist/targets/typescript/printer.d.mts +13 -0
- package/dist/targets/typescript/printer.d.mts.map +1 -0
- package/dist/targets/typescript/printer.mjs +47 -0
- package/dist/targets/typescript/printer.mjs.map +1 -0
- package/dist/targets/valibot/printer.d.mts +21 -0
- package/dist/targets/valibot/printer.d.mts.map +1 -0
- package/dist/targets/valibot/printer.mjs +198 -0
- package/dist/targets/valibot/printer.mjs.map +1 -0
- package/dist/targets/zod/lazy.d.mts +29 -0
- package/dist/targets/zod/lazy.d.mts.map +1 -0
- package/dist/targets/zod/lazy.mjs +100 -0
- package/dist/targets/zod/lazy.mjs.map +1 -0
- package/dist/targets/zod/printer.d.mts +21 -0
- package/dist/targets/zod/printer.d.mts.map +1 -0
- package/dist/targets/zod/printer.mjs +193 -0
- package/dist/targets/zod/printer.mjs.map +1 -0
- package/package.json +71 -3
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { DiagnosticCode, createDiagnostic } from "../../diagnostics/errors.mjs";
|
|
2
|
+
import { visitRefs } from "../ir/visit.mjs";
|
|
3
|
+
import { resolveRef } from "./ref-resolver.mjs";
|
|
4
|
+
//#region src/core/walker/json-schema.ts
|
|
5
|
+
var JsonSchemaWalker = class {
|
|
6
|
+
name = "json-schema";
|
|
7
|
+
async walk(files, ctx) {
|
|
8
|
+
const docIdSet = new Set(files.map((f) => f.docId));
|
|
9
|
+
const diagnostics = ctx.diagnostics;
|
|
10
|
+
const parsedFiles = [];
|
|
11
|
+
for (const file of files) {
|
|
12
|
+
let schema;
|
|
13
|
+
try {
|
|
14
|
+
schema = JSON.parse(file.content);
|
|
15
|
+
} catch (e) {
|
|
16
|
+
diagnostics.push(createDiagnostic(DiagnosticCode.InvalidJson, "error", `Invalid JSON: ${e.message}`, { docId: file.docId }));
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const defNames = /* @__PURE__ */ new Set();
|
|
20
|
+
if (isSchema(schema)) for (const key of ["$defs", "definitions"]) {
|
|
21
|
+
const defs = schema[key];
|
|
22
|
+
if (defs) for (const name of Object.keys(defs)) defNames.add(name);
|
|
23
|
+
}
|
|
24
|
+
parsedFiles.push({
|
|
25
|
+
file,
|
|
26
|
+
schema,
|
|
27
|
+
defNames
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const rootNames = /* @__PURE__ */ new Map();
|
|
31
|
+
for (const pf of parsedFiles) {
|
|
32
|
+
const base = rootExportNameFor(pf.file.docId);
|
|
33
|
+
let finalName = base;
|
|
34
|
+
if (pf.defNames.has(base)) {
|
|
35
|
+
do
|
|
36
|
+
finalName = `${finalName}Root`;
|
|
37
|
+
while (pf.defNames.has(finalName));
|
|
38
|
+
diagnostics.push(createDiagnostic(DiagnosticCode.NameCollision, "warning", `Root export "${base}" collides with a def; renamed to "${finalName}"`, { docId: pf.file.docId }));
|
|
39
|
+
}
|
|
40
|
+
rootNames.set(pf.file.docId, finalName);
|
|
41
|
+
}
|
|
42
|
+
const rootExportNameOf = (docId) => rootNames.get(docId) ?? "Schema";
|
|
43
|
+
const docs = [];
|
|
44
|
+
for (const pf of parsedFiles) {
|
|
45
|
+
const wctx = {
|
|
46
|
+
docId: pf.file.docId,
|
|
47
|
+
docIdSet,
|
|
48
|
+
rootExportNameOf,
|
|
49
|
+
diagnostics,
|
|
50
|
+
logger: ctx.logger
|
|
51
|
+
};
|
|
52
|
+
const exports = /* @__PURE__ */ new Map();
|
|
53
|
+
const exportOrder = [];
|
|
54
|
+
if (isSchema(pf.schema)) for (const key of ["$defs", "definitions"]) {
|
|
55
|
+
const defs = pf.schema[key];
|
|
56
|
+
if (defs) for (const [name, def] of Object.entries(defs)) {
|
|
57
|
+
if (exports.has(name)) {
|
|
58
|
+
diagnostics.push(createDiagnostic(DiagnosticCode.NameCollision, "warning", `Duplicate def name "${name}"`, { docId: pf.file.docId }));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
exports.set(name, toIR(def, wctx));
|
|
62
|
+
exportOrder.push(name);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const rootName = rootNames.get(pf.file.docId) ?? "Schema";
|
|
66
|
+
exports.set(rootName, toIR(pf.schema, wctx));
|
|
67
|
+
exportOrder.push(rootName);
|
|
68
|
+
docs.push({
|
|
69
|
+
docId: pf.file.docId,
|
|
70
|
+
sourcePath: pf.file.path,
|
|
71
|
+
exports,
|
|
72
|
+
exportOrder,
|
|
73
|
+
rootExportName: rootName
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
validateRefs(docs, docIdSet, diagnostics);
|
|
77
|
+
return docs;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
function warn(ctx, message) {
|
|
81
|
+
ctx.diagnostics.push(createDiagnostic(DiagnosticCode.UnknownKeyword, "warning", message, { docId: ctx.docId }));
|
|
82
|
+
}
|
|
83
|
+
function err(ctx, code, message, ref) {
|
|
84
|
+
ctx.diagnostics.push(createDiagnostic(code, "error", message, {
|
|
85
|
+
docId: ctx.docId,
|
|
86
|
+
ref
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
function isSchema(v) {
|
|
90
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
91
|
+
}
|
|
92
|
+
function num(v) {
|
|
93
|
+
return typeof v === "number" ? v : void 0;
|
|
94
|
+
}
|
|
95
|
+
function rootExportNameFor(docId) {
|
|
96
|
+
const slash = docId.lastIndexOf("/");
|
|
97
|
+
const base = slash >= 0 ? docId.slice(slash + 1) : docId;
|
|
98
|
+
const dot = base.lastIndexOf(".");
|
|
99
|
+
return pascalCase(dot > 0 ? base.slice(0, dot) : base) || "Schema";
|
|
100
|
+
}
|
|
101
|
+
function pascalCase(s) {
|
|
102
|
+
return s.split(/[-_.\s/]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
103
|
+
}
|
|
104
|
+
function extractMeta(s) {
|
|
105
|
+
const meta = {};
|
|
106
|
+
if (typeof s.title === "string") meta.title = s.title;
|
|
107
|
+
if (typeof s.description === "string") meta.description = s.description;
|
|
108
|
+
if (s.deprecated === true) meta.deprecated = true;
|
|
109
|
+
if ("default" in s) meta.default = s.default;
|
|
110
|
+
return Object.keys(meta).length === 0 ? void 0 : meta;
|
|
111
|
+
}
|
|
112
|
+
function toIR(schema, ctx) {
|
|
113
|
+
if (typeof schema === "boolean") {
|
|
114
|
+
if (!schema) warn(ctx, "`schema: false` (never) is represented as unknown");
|
|
115
|
+
return { kind: "unknown" };
|
|
116
|
+
}
|
|
117
|
+
if (!isSchema(schema)) {
|
|
118
|
+
warn(ctx, "non-object schema is represented as unknown");
|
|
119
|
+
return { kind: "unknown" };
|
|
120
|
+
}
|
|
121
|
+
const meta = extractMeta(schema);
|
|
122
|
+
if (typeof schema.$ref === "string") {
|
|
123
|
+
const res = resolveRef(schema.$ref, ctx.docId, ctx.docIdSet, ctx.rootExportNameOf);
|
|
124
|
+
if (res.ok) return {
|
|
125
|
+
kind: "ref",
|
|
126
|
+
ref: res.ref,
|
|
127
|
+
meta
|
|
128
|
+
};
|
|
129
|
+
err(ctx, res.code, res.message, schema.$ref);
|
|
130
|
+
return {
|
|
131
|
+
kind: "unknown",
|
|
132
|
+
meta
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if ("const" in schema) {
|
|
136
|
+
const v = schema.const;
|
|
137
|
+
if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") return {
|
|
138
|
+
kind: "literal",
|
|
139
|
+
value: v,
|
|
140
|
+
meta
|
|
141
|
+
};
|
|
142
|
+
warn(ctx, "non-primitive const is represented as unknown");
|
|
143
|
+
return {
|
|
144
|
+
kind: "unknown",
|
|
145
|
+
meta
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (schema.anyOf) return {
|
|
149
|
+
kind: "union",
|
|
150
|
+
variants: schema.anyOf.map((x) => toIR(x, ctx)),
|
|
151
|
+
meta
|
|
152
|
+
};
|
|
153
|
+
if (schema.oneOf) return {
|
|
154
|
+
kind: "union",
|
|
155
|
+
variants: schema.oneOf.map((x) => toIR(x, ctx)),
|
|
156
|
+
meta
|
|
157
|
+
};
|
|
158
|
+
if (schema.allOf) return {
|
|
159
|
+
kind: "intersection",
|
|
160
|
+
variants: schema.allOf.map((x) => toIR(x, ctx)),
|
|
161
|
+
meta
|
|
162
|
+
};
|
|
163
|
+
const t = schema.type;
|
|
164
|
+
if (typeof t === "string") return typeToIR(t, schema, meta, ctx);
|
|
165
|
+
if (Array.isArray(t)) return {
|
|
166
|
+
kind: "union",
|
|
167
|
+
variants: t.map((x) => typeToIR(x, schema, meta, ctx)),
|
|
168
|
+
meta
|
|
169
|
+
};
|
|
170
|
+
if (schema.enum) return enumToIR(schema.enum, meta, ctx);
|
|
171
|
+
if (schema.properties || Array.isArray(schema.required) || schema.additionalProperties !== void 0) return objectToIR(schema, meta, ctx);
|
|
172
|
+
if (schema.items !== void 0 || Array.isArray(schema.prefixItems)) return arrayToIR(schema, meta, ctx);
|
|
173
|
+
warn(ctx, "schema without type/const/enum/$ref/combinators is represented as unknown");
|
|
174
|
+
return {
|
|
175
|
+
kind: "unknown",
|
|
176
|
+
meta
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function typeToIR(typeStr, s, meta, ctx) {
|
|
180
|
+
switch (typeStr) {
|
|
181
|
+
case "string": {
|
|
182
|
+
const enumArr = Array.isArray(s.enum) ? s.enum.filter((v) => typeof v === "string") : void 0;
|
|
183
|
+
return {
|
|
184
|
+
kind: "string",
|
|
185
|
+
meta,
|
|
186
|
+
...enumArr ? { enum: enumArr } : {},
|
|
187
|
+
...typeof s.const === "string" ? { const: s.const } : {},
|
|
188
|
+
...typeof s.pattern === "string" ? { pattern: s.pattern } : {},
|
|
189
|
+
...typeof s.format === "string" ? { format: s.format } : {},
|
|
190
|
+
...num(s.minLength) !== void 0 ? { minLength: num(s.minLength) } : {},
|
|
191
|
+
...num(s.maxLength) !== void 0 ? { maxLength: num(s.maxLength) } : {}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
case "integer":
|
|
195
|
+
case "number": return {
|
|
196
|
+
kind: typeStr,
|
|
197
|
+
meta,
|
|
198
|
+
...Array.isArray(s.enum) ? { enum: s.enum.filter((v) => typeof v === "number") } : {},
|
|
199
|
+
...typeof s.const === "number" ? { const: s.const } : {},
|
|
200
|
+
...num(s.minimum) !== void 0 ? { minimum: num(s.minimum) } : {},
|
|
201
|
+
...num(s.maximum) !== void 0 ? { maximum: num(s.maximum) } : {},
|
|
202
|
+
...typeof s.exclusiveMinimum === "number" ? { exclusiveMinimum: s.exclusiveMinimum } : {},
|
|
203
|
+
...typeof s.exclusiveMaximum === "number" ? { exclusiveMaximum: s.exclusiveMaximum } : {},
|
|
204
|
+
...num(s.multipleOf) !== void 0 ? { multipleOf: num(s.multipleOf) } : {}
|
|
205
|
+
};
|
|
206
|
+
case "boolean": return {
|
|
207
|
+
kind: "boolean",
|
|
208
|
+
meta,
|
|
209
|
+
...typeof s.const === "boolean" ? { const: s.const } : {}
|
|
210
|
+
};
|
|
211
|
+
case "null": return {
|
|
212
|
+
kind: "null",
|
|
213
|
+
meta
|
|
214
|
+
};
|
|
215
|
+
case "array": return arrayToIR(s, meta, ctx);
|
|
216
|
+
case "object": return objectToIR(s, meta, ctx);
|
|
217
|
+
default:
|
|
218
|
+
warn(ctx, `unsupported type "${typeStr}" is represented as unknown`);
|
|
219
|
+
return {
|
|
220
|
+
kind: "unknown",
|
|
221
|
+
meta
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function enumToIR(values, meta, ctx) {
|
|
226
|
+
const variants = [];
|
|
227
|
+
for (const v of values) if (v === null) variants.push({ kind: "null" });
|
|
228
|
+
else if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") variants.push({
|
|
229
|
+
kind: "literal",
|
|
230
|
+
value: v
|
|
231
|
+
});
|
|
232
|
+
else {
|
|
233
|
+
warn(ctx, "non-primitive enum value is represented as unknown");
|
|
234
|
+
variants.push({ kind: "unknown" });
|
|
235
|
+
}
|
|
236
|
+
if (variants.length === 1) {
|
|
237
|
+
const single = variants[0];
|
|
238
|
+
if (single) return single;
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
kind: "union",
|
|
242
|
+
variants,
|
|
243
|
+
meta
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function arrayToIR(s, meta, ctx) {
|
|
247
|
+
if (Array.isArray(s.prefixItems)) return {
|
|
248
|
+
kind: "tuple",
|
|
249
|
+
items: s.prefixItems.map((x) => toIR(x, ctx)),
|
|
250
|
+
additionalItems: isSchema(s.items) ? toIR(s.items, ctx) : void 0,
|
|
251
|
+
meta
|
|
252
|
+
};
|
|
253
|
+
if (Array.isArray(s.items)) return {
|
|
254
|
+
kind: "tuple",
|
|
255
|
+
items: s.items.map((x) => toIR(x, ctx)),
|
|
256
|
+
additionalItems: isSchema(s.additionalItems) ? toIR(s.additionalItems, ctx) : void 0,
|
|
257
|
+
meta
|
|
258
|
+
};
|
|
259
|
+
return {
|
|
260
|
+
kind: "array",
|
|
261
|
+
meta,
|
|
262
|
+
items: isSchema(s.items) ? toIR(s.items, ctx) : void 0,
|
|
263
|
+
...num(s.minItems) !== void 0 ? { minItems: num(s.minItems) } : {},
|
|
264
|
+
...num(s.maxItems) !== void 0 ? { maxItems: num(s.maxItems) } : {},
|
|
265
|
+
...s.uniqueItems === true ? { uniqueItems: true } : {}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function objectToIR(s, meta, ctx) {
|
|
269
|
+
const properties = /* @__PURE__ */ new Map();
|
|
270
|
+
const requiredList = Array.isArray(s.required) ? [...s.required] : [];
|
|
271
|
+
const props = s.properties;
|
|
272
|
+
if (props) for (const [key, sub] of Object.entries(props)) {
|
|
273
|
+
if (sub.required === true && !requiredList.includes(key)) requiredList.push(key);
|
|
274
|
+
properties.set(key, toIR(sub, ctx));
|
|
275
|
+
}
|
|
276
|
+
let additionalProperties;
|
|
277
|
+
if (s.additionalProperties === false) additionalProperties = false;
|
|
278
|
+
else if (isSchema(s.additionalProperties)) additionalProperties = toIR(s.additionalProperties, ctx);
|
|
279
|
+
if (properties.size === 0 && additionalProperties) return {
|
|
280
|
+
kind: "record",
|
|
281
|
+
values: additionalProperties,
|
|
282
|
+
meta
|
|
283
|
+
};
|
|
284
|
+
return {
|
|
285
|
+
kind: "object",
|
|
286
|
+
properties,
|
|
287
|
+
required: requiredList.filter((k) => properties.has(k)),
|
|
288
|
+
additionalProperties,
|
|
289
|
+
meta
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/** Second pass: every {@link Ref} must point to an existing document export. */
|
|
293
|
+
function validateRefs(docs, docIdSet, diagnostics) {
|
|
294
|
+
const exportsByDoc = /* @__PURE__ */ new Map();
|
|
295
|
+
for (const d of docs) exportsByDoc.set(d.docId, new Set(d.exports.keys()));
|
|
296
|
+
for (const doc of docs) for (const [, ir] of doc.exports) visitRefs(ir, (ref) => {
|
|
297
|
+
const refStr = `${ref.docId}#${ref.exportName}`;
|
|
298
|
+
if (!docIdSet.has(ref.docId)) diagnostics.push(createDiagnostic(DiagnosticCode.InvalidRef, "error", `Ref target document not found: ${refStr}`, {
|
|
299
|
+
docId: doc.docId,
|
|
300
|
+
ref: refStr
|
|
301
|
+
}));
|
|
302
|
+
else if (!exportsByDoc.get(ref.docId)?.has(ref.exportName)) diagnostics.push(createDiagnostic(DiagnosticCode.InvalidRef, "error", `Ref target export not found: ${refStr}`, {
|
|
303
|
+
docId: doc.docId,
|
|
304
|
+
ref: refStr
|
|
305
|
+
}));
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
309
|
+
export { JsonSchemaWalker };
|
|
310
|
+
|
|
311
|
+
//# sourceMappingURL=json-schema.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-schema.mjs","names":[],"sources":["../../../src/core/walker/json-schema.ts"],"sourcesContent":["import { createDiagnostic, DiagnosticCode, type Diagnostic } from \"../../diagnostics/errors.js\";\nimport type { Logger } from \"../../diagnostics/logger.js\";\nimport type { IRMeta, IRType, SourceDoc } from \"../ir/types.js\";\nimport { visitRefs } from \"../ir/visit.js\";\nimport type { SchemaFile } from \"../types.js\";\nimport type { AbstractWalker, WalkerContext } from \"./abstract.js\";\nimport { resolveRef } from \"./ref-resolver.js\";\n\n/** Loose shape of a JSON Schema node (we only model what we consume; extras flow through the index signature). */\ninterface JsonSchema {\n\t$ref?: string;\n\t$defs?: Record<string, JsonSchema>;\n\tdefinitions?: Record<string, JsonSchema>;\n\ttype?: string | readonly string[];\n\tenum?: readonly unknown[];\n\tconst?: unknown;\n\ttitle?: string;\n\tdescription?: string;\n\tdeprecated?: boolean;\n\tdefault?: unknown;\n\tpattern?: string;\n\tformat?: string;\n\tminLength?: number;\n\tmaxLength?: number;\n\tminimum?: number;\n\tmaximum?: number;\n\texclusiveMinimum?: number | boolean;\n\texclusiveMaximum?: number | boolean;\n\tmultipleOf?: number;\n\titems?: JsonSchema | readonly JsonSchema[];\n\tprefixItems?: readonly JsonSchema[];\n\tadditionalItems?: JsonSchema | boolean;\n\tminItems?: number;\n\tmaxItems?: number;\n\tuniqueItems?: boolean;\n\tproperties?: Record<string, JsonSchema>;\n\t/** Standard dialect: string[] on the object; some dialects use boolean on property schemas. */\n\trequired?: readonly string[] | boolean;\n\tadditionalProperties?: JsonSchema | boolean;\n\tanyOf?: readonly JsonSchema[];\n\toneOf?: readonly JsonSchema[];\n\tallOf?: readonly JsonSchema[];\n\t[key: string]: unknown;\n}\n\nexport class JsonSchemaWalker implements AbstractWalker {\n\tpublic readonly name = \"json-schema\";\n\n\tpublic async walk(files: readonly SchemaFile[], ctx: WalkerContext): Promise<SourceDoc[]> {\n\t\tconst docIdSet = new Set(files.map(f => f.docId));\n\t\tconst diagnostics = ctx.diagnostics;\n\n\t\t// pass 0: parse every file and precompute final root export names,\n\t\t// so refs to a document root resolve to the real (possibly renamed) export\n\t\tconst parsedFiles: ParsedFile[] = [];\n\t\tfor (const file of files) {\n\t\t\tlet schema: unknown;\n\t\t\ttry {\n\t\t\t\tschema = JSON.parse(file.content);\n\t\t\t} catch (e) {\n\t\t\t\tdiagnostics.push(\n\t\t\t\t\tcreateDiagnostic(\n\t\t\t\t\t\tDiagnosticCode.InvalidJson,\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t`Invalid JSON: ${(e as Error).message}`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdocId: file.docId,\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst defNames = new Set<string>();\n\t\t\tif (isSchema(schema)) {\n\t\t\t\tfor (const key of [\"$defs\", \"definitions\"] as const) {\n\t\t\t\t\tconst defs = schema[key];\n\t\t\t\t\tif (defs) {\n\t\t\t\t\t\tfor (const name of Object.keys(defs)) {\n\t\t\t\t\t\t\tdefNames.add(name);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tparsedFiles.push({ file, schema, defNames });\n\t\t}\n\n\t\tconst rootNames = new Map<string, string>();\n\t\tfor (const pf of parsedFiles) {\n\t\t\tconst base = rootExportNameFor(pf.file.docId);\n\t\t\tlet finalName = base;\n\t\t\tif (pf.defNames.has(base)) {\n\t\t\t\tdo {\n\t\t\t\t\tfinalName = `${finalName}Root`;\n\t\t\t\t} while (pf.defNames.has(finalName));\n\t\t\t\tdiagnostics.push(\n\t\t\t\t\tcreateDiagnostic(\n\t\t\t\t\t\tDiagnosticCode.NameCollision,\n\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t\t`Root export \"${base}\" collides with a def; renamed to \"${finalName}\"`,\n\t\t\t\t\t\t{ docId: pf.file.docId },\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\trootNames.set(pf.file.docId, finalName);\n\t\t}\n\n\t\tconst rootExportNameOf = (docId: string): string => rootNames.get(docId) ?? \"Schema\";\n\n\t\t// pass 1: walk each document into IR\n\t\tconst docs: SourceDoc[] = [];\n\t\tfor (const pf of parsedFiles) {\n\t\t\tconst wctx: WalkCtx = {\n\t\t\t\tdocId: pf.file.docId,\n\t\t\t\tdocIdSet,\n\t\t\t\trootExportNameOf,\n\t\t\t\tdiagnostics,\n\t\t\t\tlogger: ctx.logger,\n\t\t\t};\n\t\t\tconst exports = new Map<string, IRType>();\n\t\t\tconst exportOrder: string[] = [];\n\n\t\t\tif (isSchema(pf.schema)) {\n\t\t\t\tfor (const key of [\"$defs\", \"definitions\"] as const) {\n\t\t\t\t\tconst defs = pf.schema[key];\n\t\t\t\t\tif (defs) {\n\t\t\t\t\t\tfor (const [name, def] of Object.entries(defs)) {\n\t\t\t\t\t\t\tif (exports.has(name)) {\n\t\t\t\t\t\t\t\tdiagnostics.push(\n\t\t\t\t\t\t\t\t\tcreateDiagnostic(\n\t\t\t\t\t\t\t\t\t\tDiagnosticCode.NameCollision,\n\t\t\t\t\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t\t\t\t\t\t`Duplicate def name \"${name}\"`,\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tdocId: pf.file.docId,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\texports.set(name, toIR(def, wctx));\n\t\t\t\t\t\t\texportOrder.push(name);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst rootName = rootNames.get(pf.file.docId) ?? \"Schema\";\n\t\t\texports.set(rootName, toIR(pf.schema, wctx));\n\t\t\texportOrder.push(rootName);\n\n\t\t\tdocs.push({\n\t\t\t\tdocId: pf.file.docId,\n\t\t\t\tsourcePath: pf.file.path,\n\t\t\t\texports,\n\t\t\t\texportOrder,\n\t\t\t\trootExportName: rootName,\n\t\t\t});\n\t\t}\n\n\t\tvalidateRefs(docs, docIdSet, diagnostics);\n\t\treturn docs;\n\t}\n}\n\ninterface ParsedFile {\n\treadonly file: SchemaFile;\n\treadonly schema: unknown;\n\treadonly defNames: ReadonlySet<string>;\n}\n\ninterface WalkCtx {\n\treadonly docId: string;\n\treadonly docIdSet: ReadonlySet<string>;\n\treadonly rootExportNameOf: (docId: string) => string;\n\treadonly diagnostics: Diagnostic[];\n\treadonly logger: Logger;\n}\n\nfunction warn(ctx: WalkCtx, message: string): void {\n\tctx.diagnostics.push(\n\t\tcreateDiagnostic(DiagnosticCode.UnknownKeyword, \"warning\", message, { docId: ctx.docId }),\n\t);\n}\n\nfunction err(ctx: WalkCtx, code: DiagnosticCode, message: string, ref?: string): void {\n\tctx.diagnostics.push(createDiagnostic(code, \"error\", message, { docId: ctx.docId, ref }));\n}\n\nfunction isSchema(v: unknown): v is JsonSchema {\n\treturn typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction num(v: unknown): number | undefined {\n\treturn typeof v === \"number\" ? v : undefined;\n}\n\nfunction rootExportNameFor(docId: string): string {\n\tconst slash = docId.lastIndexOf(\"/\");\n\tconst base = slash >= 0 ? docId.slice(slash + 1) : docId;\n\tconst dot = base.lastIndexOf(\".\");\n\tconst stem = dot > 0 ? base.slice(0, dot) : base;\n\treturn pascalCase(stem) || \"Schema\";\n}\n\nfunction pascalCase(s: string): string {\n\treturn s\n\t\t.split(/[-_.\\s/]+/)\n\t\t.filter(Boolean)\n\t\t.map(w => w.charAt(0).toUpperCase() + w.slice(1))\n\t\t.join(\"\");\n}\n\nfunction extractMeta(s: JsonSchema): IRMeta | undefined {\n\tconst meta: { title?: string; description?: string; deprecated?: boolean; default?: unknown } =\n\t\t{};\n\tif (typeof s.title === \"string\") {\n\t\tmeta.title = s.title;\n\t}\n\tif (typeof s.description === \"string\") {\n\t\tmeta.description = s.description;\n\t}\n\tif (s.deprecated === true) {\n\t\tmeta.deprecated = true;\n\t}\n\tif (\"default\" in s) {\n\t\tmeta.default = s.default;\n\t}\n\treturn Object.keys(meta).length === 0 ? undefined : meta;\n}\n\nfunction toIR(schema: unknown, ctx: WalkCtx): IRType {\n\tif (typeof schema === \"boolean\") {\n\t\tif (!schema) {\n\t\t\twarn(ctx, \"`schema: false` (never) is represented as unknown\");\n\t\t}\n\t\treturn { kind: \"unknown\" };\n\t}\n\tif (!isSchema(schema)) {\n\t\twarn(ctx, \"non-object schema is represented as unknown\");\n\t\treturn { kind: \"unknown\" };\n\t}\n\n\tconst meta = extractMeta(schema);\n\n\tif (typeof schema.$ref === \"string\") {\n\t\tconst res = resolveRef(schema.$ref, ctx.docId, ctx.docIdSet, ctx.rootExportNameOf);\n\t\tif (res.ok) {\n\t\t\treturn { kind: \"ref\", ref: res.ref, meta };\n\t\t}\n\t\terr(ctx, res.code, res.message, schema.$ref);\n\t\treturn { kind: \"unknown\", meta };\n\t}\n\n\tif (\"const\" in schema) {\n\t\tconst v = schema.const;\n\t\tif (\n\t\t\tv === null ||\n\t\t\ttypeof v === \"string\" ||\n\t\t\ttypeof v === \"number\" ||\n\t\t\ttypeof v === \"boolean\"\n\t\t) {\n\t\t\treturn { kind: \"literal\", value: v, meta };\n\t\t}\n\t\twarn(ctx, \"non-primitive const is represented as unknown\");\n\t\treturn { kind: \"unknown\", meta };\n\t}\n\n\tif (schema.anyOf) {\n\t\treturn { kind: \"union\", variants: schema.anyOf.map(x => toIR(x, ctx)), meta };\n\t}\n\tif (schema.oneOf) {\n\t\treturn { kind: \"union\", variants: schema.oneOf.map(x => toIR(x, ctx)), meta };\n\t}\n\tif (schema.allOf) {\n\t\treturn { kind: \"intersection\", variants: schema.allOf.map(x => toIR(x, ctx)), meta };\n\t}\n\n\tconst t = schema.type;\n\tif (typeof t === \"string\") {\n\t\treturn typeToIR(t, schema, meta, ctx);\n\t}\n\tif (Array.isArray(t)) {\n\t\tconst variants = t.map(x => typeToIR(x, schema, meta, ctx));\n\t\treturn { kind: \"union\", variants, meta };\n\t}\n\n\tif (schema.enum) {\n\t\treturn enumToIR(schema.enum, meta, ctx);\n\t}\n\n\t// JSON Schema applicability: `properties`/`required`/`additionalProperties`\n\t// constrain only object instances — infer an object when `type` is omitted.\n\tif (\n\t\tschema.properties ||\n\t\tArray.isArray(schema.required) ||\n\t\tschema.additionalProperties !== undefined\n\t) {\n\t\treturn objectToIR(schema, meta, ctx);\n\t}\n\t// `items`/`prefixItems` constrain only arrays — infer an array likewise.\n\tif (schema.items !== undefined || Array.isArray(schema.prefixItems)) {\n\t\treturn arrayToIR(schema, meta, ctx);\n\t}\n\n\twarn(ctx, \"schema without type/const/enum/$ref/combinators is represented as unknown\");\n\treturn { kind: \"unknown\", meta };\n}\n\nfunction typeToIR(typeStr: string, s: JsonSchema, meta: IRMeta | undefined, ctx: WalkCtx): IRType {\n\tswitch (typeStr) {\n\t\tcase \"string\": {\n\t\t\tconst enumArr = Array.isArray(s.enum)\n\t\t\t\t? s.enum.filter((v): v is string => typeof v === \"string\")\n\t\t\t\t: undefined;\n\t\t\treturn {\n\t\t\t\tkind: \"string\",\n\t\t\t\tmeta,\n\t\t\t\t...(enumArr ? { enum: enumArr } : {}),\n\t\t\t\t...(typeof s.const === \"string\" ? { const: s.const } : {}),\n\t\t\t\t...(typeof s.pattern === \"string\" ? { pattern: s.pattern } : {}),\n\t\t\t\t...(typeof s.format === \"string\" ? { format: s.format } : {}),\n\t\t\t\t...(num(s.minLength) !== undefined ? { minLength: num(s.minLength) } : {}),\n\t\t\t\t...(num(s.maxLength) !== undefined ? { maxLength: num(s.maxLength) } : {}),\n\t\t\t};\n\t\t}\n\t\tcase \"integer\":\n\t\tcase \"number\": {\n\t\t\treturn {\n\t\t\t\tkind: typeStr,\n\t\t\t\tmeta,\n\t\t\t\t...(Array.isArray(s.enum)\n\t\t\t\t\t? { enum: s.enum.filter((v): v is number => typeof v === \"number\") }\n\t\t\t\t\t: {}),\n\t\t\t\t...(typeof s.const === \"number\" ? { const: s.const } : {}),\n\t\t\t\t...(num(s.minimum) !== undefined ? { minimum: num(s.minimum) } : {}),\n\t\t\t\t...(num(s.maximum) !== undefined ? { maximum: num(s.maximum) } : {}),\n\t\t\t\t...(typeof s.exclusiveMinimum === \"number\"\n\t\t\t\t\t? { exclusiveMinimum: s.exclusiveMinimum }\n\t\t\t\t\t: {}),\n\t\t\t\t...(typeof s.exclusiveMaximum === \"number\"\n\t\t\t\t\t? { exclusiveMaximum: s.exclusiveMaximum }\n\t\t\t\t\t: {}),\n\t\t\t\t...(num(s.multipleOf) !== undefined ? { multipleOf: num(s.multipleOf) } : {}),\n\t\t\t};\n\t\t}\n\t\tcase \"boolean\":\n\t\t\treturn {\n\t\t\t\tkind: \"boolean\",\n\t\t\t\tmeta,\n\t\t\t\t...(typeof s.const === \"boolean\" ? { const: s.const } : {}),\n\t\t\t};\n\t\tcase \"null\":\n\t\t\treturn { kind: \"null\", meta };\n\t\tcase \"array\":\n\t\t\treturn arrayToIR(s, meta, ctx);\n\t\tcase \"object\":\n\t\t\treturn objectToIR(s, meta, ctx);\n\t\tdefault:\n\t\t\twarn(ctx, `unsupported type \"${typeStr}\" is represented as unknown`);\n\t\t\treturn { kind: \"unknown\", meta };\n\t}\n}\n\nfunction enumToIR(values: readonly unknown[], meta: IRMeta | undefined, ctx: WalkCtx): IRType {\n\tconst variants: IRType[] = [];\n\tfor (const v of values) {\n\t\tif (v === null) {\n\t\t\tvariants.push({ kind: \"null\" });\n\t\t} else if (typeof v === \"string\" || typeof v === \"number\" || typeof v === \"boolean\") {\n\t\t\tvariants.push({ kind: \"literal\", value: v });\n\t\t} else {\n\t\t\twarn(ctx, \"non-primitive enum value is represented as unknown\");\n\t\t\tvariants.push({ kind: \"unknown\" });\n\t\t}\n\t}\n\tif (variants.length === 1) {\n\t\tconst single = variants[0];\n\t\tif (single) {\n\t\t\treturn single;\n\t\t}\n\t}\n\treturn { kind: \"union\", variants, meta };\n}\n\nfunction arrayToIR(s: JsonSchema, meta: IRMeta | undefined, ctx: WalkCtx): IRType {\n\tif (Array.isArray(s.prefixItems)) {\n\t\tconst items = s.prefixItems.map(x => toIR(x, ctx));\n\t\tconst additionalItems = isSchema(s.items) ? toIR(s.items, ctx) : undefined;\n\t\treturn { kind: \"tuple\", items, additionalItems, meta };\n\t}\n\tif (Array.isArray(s.items)) {\n\t\tconst items = s.items.map(x => toIR(x, ctx));\n\t\tconst additionalItems = isSchema(s.additionalItems)\n\t\t\t? toIR(s.additionalItems, ctx)\n\t\t\t: undefined;\n\t\treturn { kind: \"tuple\", items, additionalItems, meta };\n\t}\n\tconst itemsIR = isSchema(s.items) ? toIR(s.items, ctx) : undefined;\n\treturn {\n\t\tkind: \"array\",\n\t\tmeta,\n\t\titems: itemsIR,\n\t\t...(num(s.minItems) !== undefined ? { minItems: num(s.minItems) } : {}),\n\t\t...(num(s.maxItems) !== undefined ? { maxItems: num(s.maxItems) } : {}),\n\t\t...(s.uniqueItems === true ? { uniqueItems: true } : {}),\n\t};\n}\n\nfunction objectToIR(s: JsonSchema, meta: IRMeta | undefined, ctx: WalkCtx): IRType {\n\tconst properties = new Map<string, IRType>();\n\t// Standard JSON Schema: `required` is a string[] on the object; some dialects use `required: true` on property schemas.\n\tconst requiredList = Array.isArray(s.required) ? [...s.required] : [];\n\n\tconst props = s.properties;\n\tif (props) {\n\t\tfor (const [key, sub] of Object.entries(props)) {\n\t\t\tif (sub.required === true && !requiredList.includes(key)) {\n\t\t\t\trequiredList.push(key);\n\t\t\t}\n\t\t\tproperties.set(key, toIR(sub, ctx));\n\t\t}\n\t}\n\n\tlet additionalProperties: IRType | false | undefined;\n\tif (s.additionalProperties === false) {\n\t\tadditionalProperties = false;\n\t} else if (isSchema(s.additionalProperties)) {\n\t\tadditionalProperties = toIR(s.additionalProperties, ctx);\n\t}\n\n\tif (properties.size === 0 && additionalProperties) {\n\t\treturn { kind: \"record\", values: additionalProperties, meta };\n\t}\n\treturn {\n\t\tkind: \"object\",\n\t\tproperties,\n\t\trequired: requiredList.filter(k => properties.has(k)),\n\t\tadditionalProperties,\n\t\tmeta,\n\t};\n}\n\n/** Second pass: every {@link Ref} must point to an existing document export. */\nfunction validateRefs(\n\tdocs: readonly SourceDoc[],\n\tdocIdSet: ReadonlySet<string>,\n\tdiagnostics: Diagnostic[],\n): void {\n\tconst exportsByDoc = new Map<string, ReadonlySet<string>>();\n\tfor (const d of docs) {\n\t\texportsByDoc.set(d.docId, new Set(d.exports.keys()));\n\t}\n\n\tfor (const doc of docs) {\n\t\tfor (const [, ir] of doc.exports) {\n\t\t\tvisitRefs(ir, ref => {\n\t\t\t\tconst refStr = `${ref.docId}#${ref.exportName}`;\n\t\t\t\tif (!docIdSet.has(ref.docId)) {\n\t\t\t\t\tdiagnostics.push(\n\t\t\t\t\t\tcreateDiagnostic(\n\t\t\t\t\t\t\tDiagnosticCode.InvalidRef,\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t`Ref target document not found: ${refStr}`,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdocId: doc.docId,\n\t\t\t\t\t\t\t\tref: refStr,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconst names = exportsByDoc.get(ref.docId);\n\t\t\t\t\tif (!names?.has(ref.exportName)) {\n\t\t\t\t\t\tdiagnostics.push(\n\t\t\t\t\t\t\tcreateDiagnostic(\n\t\t\t\t\t\t\t\tDiagnosticCode.InvalidRef,\n\t\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t\t`Ref target export not found: ${refStr}`,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tdocId: doc.docId,\n\t\t\t\t\t\t\t\t\tref: refStr,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { DiagnosticCode } from "../../diagnostics/errors.mjs";
|
|
2
|
+
import { Ref } from "../ir/types.mjs";
|
|
3
|
+
//#region src/core/walker/ref-resolver.d.ts
|
|
4
|
+
export type RefResolution = {
|
|
5
|
+
readonly ok: true;
|
|
6
|
+
readonly ref: Ref;
|
|
7
|
+
} | {
|
|
8
|
+
readonly ok: false;
|
|
9
|
+
readonly code: DiagnosticCode;
|
|
10
|
+
readonly message: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a JSON Schema `$ref` into an abstract {@link Ref}.
|
|
14
|
+
*
|
|
15
|
+
* - `#/` and `#` → the root export of the (current or target) document.
|
|
16
|
+
* - `#/$defs/Name` and `#/definitions/Name` → a named export.
|
|
17
|
+
* - `./path.json`, `path.json`, `../x.json` → cross-file, resolved against the current doc id.
|
|
18
|
+
* - `http(s):`, `urn:`, `file:` → unsupported external ref.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveRef(ref: string, currentDocId: string, docIdSet: ReadonlySet<string>, rootExportNameOf: (docId: string) => string): RefResolution;
|
|
21
|
+
//#endregion
|
|
22
|
+
//# sourceMappingURL=ref-resolver.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref-resolver.d.mts","names":[],"sources":["../../../src/core/walker/ref-resolver.ts"],"mappings":";;;YAIY;WACE;WAAmB,KAAK;;WACxB;WAAoB,MAAM;WAAyB;;;;;;;;;;wBAUjD,WACf,aACA,sBACA,UAAU,qBACV,mBAAmB,2BACjB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { DiagnosticCode } from "../../diagnostics/errors.mjs";
|
|
2
|
+
//#region src/core/walker/ref-resolver.ts
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a JSON Schema `$ref` into an abstract {@link Ref}.
|
|
5
|
+
*
|
|
6
|
+
* - `#/` and `#` → the root export of the (current or target) document.
|
|
7
|
+
* - `#/$defs/Name` and `#/definitions/Name` → a named export.
|
|
8
|
+
* - `./path.json`, `path.json`, `../x.json` → cross-file, resolved against the current doc id.
|
|
9
|
+
* - `http(s):`, `urn:`, `file:` → unsupported external ref.
|
|
10
|
+
*/
|
|
11
|
+
function resolveRef(ref, currentDocId, docIdSet, rootExportNameOf) {
|
|
12
|
+
if (/^(https?:|file:|urn:)/i.test(ref)) return {
|
|
13
|
+
ok: false,
|
|
14
|
+
code: DiagnosticCode.ExternalRef,
|
|
15
|
+
message: `External refs are not supported: ${ref}`
|
|
16
|
+
};
|
|
17
|
+
const hashIdx = ref.indexOf("#");
|
|
18
|
+
const pathPart = hashIdx >= 0 ? ref.slice(0, hashIdx) : ref;
|
|
19
|
+
const fragment = hashIdx >= 0 ? ref.slice(hashIdx) : "";
|
|
20
|
+
const targetDocId = pathPart === "" ? currentDocId : normalizeDocId(pathPart, currentDocId);
|
|
21
|
+
if (!docIdSet.has(targetDocId)) return {
|
|
22
|
+
ok: false,
|
|
23
|
+
code: DiagnosticCode.InvalidRef,
|
|
24
|
+
message: `Target schema not found in input: ${ref}`
|
|
25
|
+
};
|
|
26
|
+
const exportName = parseFragment(fragment);
|
|
27
|
+
if (exportName === null) return {
|
|
28
|
+
ok: false,
|
|
29
|
+
code: DiagnosticCode.InvalidRef,
|
|
30
|
+
message: `Unsupported $ref pointer: ${ref}`
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
ok: true,
|
|
34
|
+
ref: {
|
|
35
|
+
docId: targetDocId,
|
|
36
|
+
exportName: exportName === "" ? rootExportNameOf(targetDocId) : exportName
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Parse a fragment like `#`, `#/`, `#/$defs/Foo`, `#/definitions/Foo`. Returns `""` for root, the def name, or `null` if unsupported. */
|
|
41
|
+
function parseFragment(fragment) {
|
|
42
|
+
if (fragment === "" || fragment === "#" || fragment === "#/") return "";
|
|
43
|
+
const match = /^#\/(?:\$defs|definitions)\/([^/]+)$/.exec(fragment);
|
|
44
|
+
if (match) return match[1] ?? null;
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/** Resolve a relative path part against the current doc id (posix). */
|
|
48
|
+
function normalizeDocId(pathPart, currentDocId) {
|
|
49
|
+
const slash = currentDocId.lastIndexOf("/");
|
|
50
|
+
const dir = slash >= 0 ? currentDocId.slice(0, slash) : "";
|
|
51
|
+
const joined = dir ? `${dir}/${pathPart}` : pathPart;
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const seg of joined.split("/")) {
|
|
54
|
+
if (seg === "." || seg === "") continue;
|
|
55
|
+
if (seg === "..") {
|
|
56
|
+
out.pop();
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
out.push(seg);
|
|
60
|
+
}
|
|
61
|
+
return out.join("/");
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { resolveRef };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=ref-resolver.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref-resolver.mjs","names":["DC"],"sources":["../../../src/core/walker/ref-resolver.ts"],"sourcesContent":["import type { DiagnosticCode } from \"../../diagnostics/errors.js\";\nimport { DiagnosticCode as DC } from \"../../diagnostics/errors.js\";\nimport type { Ref } from \"../ir/types.js\";\n\nexport type RefResolution =\n\t| { readonly ok: true; readonly ref: Ref }\n\t| { readonly ok: false; readonly code: DiagnosticCode; readonly message: string };\n\n/**\n * Resolve a JSON Schema `$ref` into an abstract {@link Ref}.\n *\n * - `#/` and `#` → the root export of the (current or target) document.\n * - `#/$defs/Name` and `#/definitions/Name` → a named export.\n * - `./path.json`, `path.json`, `../x.json` → cross-file, resolved against the current doc id.\n * - `http(s):`, `urn:`, `file:` → unsupported external ref.\n */\nexport function resolveRef(\n\tref: string,\n\tcurrentDocId: string,\n\tdocIdSet: ReadonlySet<string>,\n\trootExportNameOf: (docId: string) => string,\n): RefResolution {\n\tif (/^(https?:|file:|urn:)/i.test(ref)) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tcode: DC.ExternalRef,\n\t\t\tmessage: `External refs are not supported: ${ref}`,\n\t\t};\n\t}\n\n\tconst hashIdx = ref.indexOf(\"#\");\n\tconst pathPart = hashIdx >= 0 ? ref.slice(0, hashIdx) : ref;\n\tconst fragment = hashIdx >= 0 ? ref.slice(hashIdx) : \"\";\n\n\tconst targetDocId = pathPart === \"\" ? currentDocId : normalizeDocId(pathPart, currentDocId);\n\tif (!docIdSet.has(targetDocId)) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tcode: DC.InvalidRef,\n\t\t\tmessage: `Target schema not found in input: ${ref}`,\n\t\t};\n\t}\n\n\tconst exportName = parseFragment(fragment);\n\tif (exportName === null) {\n\t\treturn { ok: false, code: DC.InvalidRef, message: `Unsupported $ref pointer: ${ref}` };\n\t}\n\tconst name = exportName === \"\" ? rootExportNameOf(targetDocId) : exportName;\n\treturn { ok: true, ref: { docId: targetDocId, exportName: name } };\n}\n\n/** Parse a fragment like `#`, `#/`, `#/$defs/Foo`, `#/definitions/Foo`. Returns `\"\"` for root, the def name, or `null` if unsupported. */\nfunction parseFragment(fragment: string): string | null {\n\tif (fragment === \"\" || fragment === \"#\" || fragment === \"#/\") {\n\t\treturn \"\";\n\t}\n\tconst match = /^#\\/(?:\\$defs|definitions)\\/([^/]+)$/.exec(fragment);\n\tif (match) {\n\t\tconst name = match[1];\n\t\treturn name ?? null;\n\t}\n\treturn null;\n}\n\n/** Resolve a relative path part against the current doc id (posix). */\nfunction normalizeDocId(pathPart: string, currentDocId: string): string {\n\tconst slash = currentDocId.lastIndexOf(\"/\");\n\tconst dir = slash >= 0 ? currentDocId.slice(0, slash) : \"\";\n\tconst joined = dir ? `${dir}/${pathPart}` : pathPart;\n\tconst out: string[] = [];\n\tfor (const seg of joined.split(\"/\")) {\n\t\tif (seg === \".\" || seg === \"\") {\n\t\t\tcontinue;\n\t\t}\n\t\tif (seg === \"..\") {\n\t\t\tout.pop();\n\t\t\tcontinue;\n\t\t}\n\t\tout.push(seg);\n\t}\n\treturn out.join(\"/\");\n}\n"],"mappings":";;;;;;;;;;AAgBA,SAAgB,WACf,KACA,cACA,UACA,kBACgB;CAChB,IAAI,yBAAyB,KAAK,GAAG,GACpC,OAAO;EACN,IAAI;EACJ,MAAMA,eAAG;EACT,SAAS,oCAAoC;CAC9C;CAGD,MAAM,UAAU,IAAI,QAAQ,GAAG;CAC/B,MAAM,WAAW,WAAW,IAAI,IAAI,MAAM,GAAG,OAAO,IAAI;CACxD,MAAM,WAAW,WAAW,IAAI,IAAI,MAAM,OAAO,IAAI;CAErD,MAAM,cAAc,aAAa,KAAK,eAAe,eAAe,UAAU,YAAY;CAC1F,IAAI,CAAC,SAAS,IAAI,WAAW,GAC5B,OAAO;EACN,IAAI;EACJ,MAAMA,eAAG;EACT,SAAS,qCAAqC;CAC/C;CAGD,MAAM,aAAa,cAAc,QAAQ;CACzC,IAAI,eAAe,MAClB,OAAO;EAAE,IAAI;EAAO,MAAMA,eAAG;EAAY,SAAS,6BAA6B;CAAM;CAGtF,OAAO;EAAE,IAAI;EAAM,KAAK;GAAE,OAAO;GAAa,YADjC,eAAe,KAAK,iBAAiB,WAAW,IAAI;EACF;CAAE;AAClE;;AAGA,SAAS,cAAc,UAAiC;CACvD,IAAI,aAAa,MAAM,aAAa,OAAO,aAAa,MACvD,OAAO;CAER,MAAM,QAAQ,uCAAuC,KAAK,QAAQ;CAClE,IAAI,OAEH,OADa,MAAM,MACJ;CAEhB,OAAO;AACR;;AAGA,SAAS,eAAe,UAAkB,cAA8B;CACvE,MAAM,QAAQ,aAAa,YAAY,GAAG;CAC1C,MAAM,MAAM,SAAS,IAAI,aAAa,MAAM,GAAG,KAAK,IAAI;CACxD,MAAM,SAAS,MAAM,GAAG,IAAI,GAAG,aAAa;CAC5C,MAAM,MAAgB,CAAC;CACvB,KAAK,MAAM,OAAO,OAAO,MAAM,GAAG,GAAG;EACpC,IAAI,QAAQ,OAAO,QAAQ,IAC1B;EAED,IAAI,QAAQ,MAAM;GACjB,IAAI,IAAI;GACR;EACD;EACA,IAAI,KAAK,GAAG;CACb;CACA,OAAO,IAAI,KAAK,GAAG;AACpB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Logger } from "../../diagnostics/logger.mjs";
|
|
2
|
+
import { PrintedFile } from "../types.mjs";
|
|
3
|
+
//#region src/core/writer/index.d.ts
|
|
4
|
+
export interface WriteOptions {
|
|
5
|
+
/** Absolute output root directory. */
|
|
6
|
+
readonly output: string;
|
|
7
|
+
readonly dryRun: boolean;
|
|
8
|
+
readonly clean: boolean;
|
|
9
|
+
readonly logger: Logger;
|
|
10
|
+
/** Header template (`string`, possibly with `{{version}}`/`{{date}}`) or `false` to disable. */
|
|
11
|
+
readonly header: string | false;
|
|
12
|
+
}
|
|
13
|
+
export interface WriteResult {
|
|
14
|
+
readonly written: readonly string[];
|
|
15
|
+
readonly deleted: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function writeFiles(files: readonly PrintedFile[], opts: WriteOptions): Promise<WriteResult>;
|
|
18
|
+
//#endregion
|
|
19
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/writer/index.ts"],"mappings":";;;iBAuBiB;;WAEP;WACA;WACA;WACA,QAAQ;;WAER;;iBAGO;WACP;WACA;;wBAGY,WACrB,gBAAgB,eAChB,MAAM,eACJ,QAAQ"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { version } from "../../package.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
//#region src/core/writer/index.ts
|
|
5
|
+
const VERSION = version;
|
|
6
|
+
/** Marker of the built-in default header; also matches outputs of previous runs. */
|
|
7
|
+
const DEFAULT_MARKER = "// @generated by json2code";
|
|
8
|
+
/**
|
|
9
|
+
* Render the effective header text for a template (or no header at all).
|
|
10
|
+
* Placeholders: `{{version}}` → generator version, `{{date}}` → YYYY-MM-DD.
|
|
11
|
+
*/
|
|
12
|
+
function renderHeader(header) {
|
|
13
|
+
if (header === false || header.trim() === "") return "";
|
|
14
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
15
|
+
return `${header.replaceAll("{{version}}", VERSION).replaceAll("{{date}}", date).replace(/\n+$/, "")}\n\n`;
|
|
16
|
+
}
|
|
17
|
+
async function writeFiles(files, opts) {
|
|
18
|
+
const newPaths = new Set(files.map((f) => f.path));
|
|
19
|
+
const written = [];
|
|
20
|
+
const deleted = [];
|
|
21
|
+
const headerText = renderHeader(opts.header);
|
|
22
|
+
const markers = opts.header === false || opts.header.trim() === "" ? [DEFAULT_MARKER] : [DEFAULT_MARKER, headerText.split("\n", 1)[0] ?? DEFAULT_MARKER];
|
|
23
|
+
if (opts.clean) for (const rel of await findStale(opts.output, newPaths, markers)) {
|
|
24
|
+
if (opts.dryRun) opts.logger.info(`[dry-run] delete ${rel}`);
|
|
25
|
+
else {
|
|
26
|
+
await rm(path.join(opts.output, rel));
|
|
27
|
+
opts.logger.info(`deleted ${rel}`);
|
|
28
|
+
}
|
|
29
|
+
deleted.push(rel);
|
|
30
|
+
}
|
|
31
|
+
for (const file of files) {
|
|
32
|
+
const abs = path.join(opts.output, file.path);
|
|
33
|
+
const content = `${headerText}${file.content}`;
|
|
34
|
+
if (opts.dryRun) opts.logger.info(`[dry-run] write ${file.path}`);
|
|
35
|
+
else {
|
|
36
|
+
await writeIfChanged(abs, content);
|
|
37
|
+
opts.logger.info(`wrote ${file.path}`);
|
|
38
|
+
}
|
|
39
|
+
written.push(file.path);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
written,
|
|
43
|
+
deleted
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function writeIfChanged(abs, content) {
|
|
47
|
+
await mkdir(path.dirname(abs), { recursive: true });
|
|
48
|
+
try {
|
|
49
|
+
if (await readFile(abs, "utf8") === content) return;
|
|
50
|
+
} catch {}
|
|
51
|
+
await writeFile(abs, content);
|
|
52
|
+
}
|
|
53
|
+
async function findStale(output, keep, markers) {
|
|
54
|
+
let isDir;
|
|
55
|
+
try {
|
|
56
|
+
isDir = (await stat(output)).isDirectory();
|
|
57
|
+
} catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
if (!isDir) return [];
|
|
61
|
+
const entries = await readdir(output, {
|
|
62
|
+
recursive: true,
|
|
63
|
+
withFileTypes: true
|
|
64
|
+
});
|
|
65
|
+
const stale = [];
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
if (!entry.isFile() || !entry.name.endsWith(".ts")) continue;
|
|
68
|
+
const full = path.join(entry.parentPath, entry.name);
|
|
69
|
+
const rel = path.relative(output, full).split(path.sep).join("/");
|
|
70
|
+
if (keep.has(rel)) continue;
|
|
71
|
+
let head = "";
|
|
72
|
+
try {
|
|
73
|
+
head = await readFile(full, "utf8");
|
|
74
|
+
} catch {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (markers.some((m) => head.startsWith(m))) stale.push(rel);
|
|
78
|
+
}
|
|
79
|
+
return stale.toSorted();
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { renderHeader, writeFiles };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["pkg.version"],"sources":["../../../src/core/writer/index.ts"],"sourcesContent":["import { mkdir, readFile, readdir, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport pkg from \"../../../package.json\" with { type: \"json\" };\nimport type { Logger } from \"../../diagnostics/logger.js\";\nimport type { PrintedFile } from \"../types.js\";\n\nconst VERSION: string = pkg.version;\n/** Marker of the built-in default header; also matches outputs of previous runs. */\nconst DEFAULT_MARKER = \"// @generated by json2code\";\n\n/**\n * Render the effective header text for a template (or no header at all).\n * Placeholders: `{{version}}` → generator version, `{{date}}` → YYYY-MM-DD.\n */\nexport function renderHeader(header: string | false): string {\n\tif (header === false || header.trim() === \"\") {\n\t\treturn \"\";\n\t}\n\tconst date = new Date().toISOString().slice(0, 10);\n\tconst text = header.replaceAll(\"{{version}}\", VERSION).replaceAll(\"{{date}}\", date);\n\treturn `${text.replace(/\\n+$/, \"\")}\\n\\n`;\n}\n\nexport interface WriteOptions {\n\t/** Absolute output root directory. */\n\treadonly output: string;\n\treadonly dryRun: boolean;\n\treadonly clean: boolean;\n\treadonly logger: Logger;\n\t/** Header template (`string`, possibly with `{{version}}`/`{{date}}`) or `false` to disable. */\n\treadonly header: string | false;\n}\n\nexport interface WriteResult {\n\treadonly written: readonly string[];\n\treadonly deleted: readonly string[];\n}\n\nexport async function writeFiles(\n\tfiles: readonly PrintedFile[],\n\topts: WriteOptions,\n): Promise<WriteResult> {\n\tconst newPaths = new Set(files.map(f => f.path));\n\tconst written: string[] = [];\n\tconst deleted: string[] = [];\n\tconst headerText = renderHeader(opts.header);\n\t// stale detection matches the current header and the default marker (covers older outputs)\n\tconst markers =\n\t\topts.header === false || opts.header.trim() === \"\"\n\t\t\t? [DEFAULT_MARKER]\n\t\t\t: [DEFAULT_MARKER, headerText.split(\"\\n\", 1)[0] ?? DEFAULT_MARKER];\n\n\tif (opts.clean) {\n\t\tfor (const rel of await findStale(opts.output, newPaths, markers)) {\n\t\t\tif (opts.dryRun) {\n\t\t\t\topts.logger.info(`[dry-run] delete ${rel}`);\n\t\t\t} else {\n\t\t\t\tawait rm(path.join(opts.output, rel));\n\t\t\t\topts.logger.info(`deleted ${rel}`);\n\t\t\t}\n\t\t\tdeleted.push(rel);\n\t\t}\n\t}\n\n\tfor (const file of files) {\n\t\tconst abs = path.join(opts.output, file.path);\n\t\tconst content = `${headerText}${file.content}`;\n\t\tif (opts.dryRun) {\n\t\t\topts.logger.info(`[dry-run] write ${file.path}`);\n\t\t} else {\n\t\t\tawait writeIfChanged(abs, content);\n\t\t\topts.logger.info(`wrote ${file.path}`);\n\t\t}\n\t\twritten.push(file.path);\n\t}\n\n\treturn { written, deleted };\n}\n\nasync function writeIfChanged(abs: string, content: string): Promise<void> {\n\tawait mkdir(path.dirname(abs), { recursive: true });\n\ttry {\n\t\tconst existing = await readFile(abs, \"utf8\");\n\t\tif (existing === content) {\n\t\t\treturn;\n\t\t}\n\t} catch {\n\t\t// file doesn't exist yet — fall through to write\n\t}\n\tawait writeFile(abs, content);\n}\n\nasync function findStale(\n\toutput: string,\n\tkeep: ReadonlySet<string>,\n\tmarkers: readonly string[],\n): Promise<string[]> {\n\tlet isDir: boolean;\n\ttry {\n\t\tisDir = (await stat(output)).isDirectory();\n\t} catch {\n\t\treturn [];\n\t}\n\tif (!isDir) {\n\t\treturn [];\n\t}\n\n\tconst entries = await readdir(output, { recursive: true, withFileTypes: true });\n\tconst stale: string[] = [];\n\tfor (const entry of entries) {\n\t\tif (!entry.isFile() || !entry.name.endsWith(\".ts\")) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst full = path.join(entry.parentPath, entry.name);\n\t\tconst rel = path.relative(output, full).split(path.sep).join(\"/\");\n\t\tif (keep.has(rel)) {\n\t\t\tcontinue;\n\t\t}\n\t\tlet head = \"\";\n\t\ttry {\n\t\t\thead = await readFile(full, \"utf8\");\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (markers.some(m => head.startsWith(m))) {\n\t\t\tstale.push(rel);\n\t\t}\n\t}\n\treturn stale.toSorted();\n}\n"],"mappings":";;;;AAMA,MAAM,UAAkBA;;AAExB,MAAM,iBAAiB;;;;;AAMvB,SAAgB,aAAa,QAAgC;CAC5D,IAAI,WAAW,SAAS,OAAO,KAAK,MAAM,IACzC,OAAO;CAER,MAAM,wBAAO,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE;CAEjD,OAAO,GADM,OAAO,WAAW,eAAe,OAAO,CAAC,CAAC,WAAW,YAAY,IACjE,CAAC,CAAC,QAAQ,QAAQ,EAAE,EAAE;AACpC;AAiBA,eAAsB,WACrB,OACA,MACuB;CACvB,MAAM,WAAW,IAAI,IAAI,MAAM,KAAI,MAAK,EAAE,IAAI,CAAC;CAC/C,MAAM,UAAoB,CAAC;CAC3B,MAAM,UAAoB,CAAC;CAC3B,MAAM,aAAa,aAAa,KAAK,MAAM;CAE3C,MAAM,UACL,KAAK,WAAW,SAAS,KAAK,OAAO,KAAK,MAAM,KAC7C,CAAC,cAAc,IACf,CAAC,gBAAgB,WAAW,MAAM,MAAM,CAAC,CAAC,CAAC,MAAM,cAAc;CAEnE,IAAI,KAAK,OACR,KAAK,MAAM,OAAO,MAAM,UAAU,KAAK,QAAQ,UAAU,OAAO,GAAG;EAClE,IAAI,KAAK,QACR,KAAK,OAAO,KAAK,oBAAoB,KAAK;OACpC;GACN,MAAM,GAAG,KAAK,KAAK,KAAK,QAAQ,GAAG,CAAC;GACpC,KAAK,OAAO,KAAK,WAAW,KAAK;EAClC;EACA,QAAQ,KAAK,GAAG;CACjB;CAGD,KAAK,MAAM,QAAQ,OAAO;EACzB,MAAM,MAAM,KAAK,KAAK,KAAK,QAAQ,KAAK,IAAI;EAC5C,MAAM,UAAU,GAAG,aAAa,KAAK;EACrC,IAAI,KAAK,QACR,KAAK,OAAO,KAAK,mBAAmB,KAAK,MAAM;OACzC;GACN,MAAM,eAAe,KAAK,OAAO;GACjC,KAAK,OAAO,KAAK,SAAS,KAAK,MAAM;EACtC;EACA,QAAQ,KAAK,KAAK,IAAI;CACvB;CAEA,OAAO;EAAE;EAAS;CAAQ;AAC3B;AAEA,eAAe,eAAe,KAAa,SAAgC;CAC1E,MAAM,MAAM,KAAK,QAAQ,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;CAClD,IAAI;EAEH,IAAI,MADmB,SAAS,KAAK,MAAM,MAC1B,SAChB;CAEF,QAAQ,CAER;CACA,MAAM,UAAU,KAAK,OAAO;AAC7B;AAEA,eAAe,UACd,QACA,MACA,SACoB;CACpB,IAAI;CACJ,IAAI;EACH,SAAS,MAAM,KAAK,MAAM,EAAA,CAAG,YAAY;CAC1C,QAAQ;EACP,OAAO,CAAC;CACT;CACA,IAAI,CAAC,OACJ,OAAO,CAAC;CAGT,MAAM,UAAU,MAAM,QAAQ,QAAQ;EAAE,WAAW;EAAM,eAAe;CAAK,CAAC;CAC9E,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,SAAS,SAAS;EAC5B,IAAI,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,KAAK,GAChD;EAED,MAAM,OAAO,KAAK,KAAK,MAAM,YAAY,MAAM,IAAI;EACnD,MAAM,MAAM,KAAK,SAAS,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG;EAChE,IAAI,KAAK,IAAI,GAAG,GACf;EAED,IAAI,OAAO;EACX,IAAI;GACH,OAAO,MAAM,SAAS,MAAM,MAAM;EACnC,QAAQ;GACP;EACD;EACA,IAAI,QAAQ,MAAK,MAAK,KAAK,WAAW,CAAC,CAAC,GACvC,MAAM,KAAK,GAAG;CAEhB;CACA,OAAO,MAAM,SAAS;AACvB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/diagnostics/errors.d.ts
|
|
2
|
+
export declare const DiagnosticCode: {
|
|
3
|
+
readonly FileNotFound: "J2C_FILE_NOT_FOUND";
|
|
4
|
+
readonly InvalidJson: "J2C_INVALID_JSON";
|
|
5
|
+
readonly InvalidRef: "J2C_INVALID_REF";
|
|
6
|
+
readonly ExternalRef: "J2C_EXTERNAL_REF";
|
|
7
|
+
readonly NameCollision: "J2C_NAME_COLLISION";
|
|
8
|
+
readonly UnknownKeyword: "J2C_UNKNOWN_KEYWORD";
|
|
9
|
+
readonly OutputDirty: "J2C_OUTPUT_DIRTY";
|
|
10
|
+
};
|
|
11
|
+
export type DiagnosticCode = (typeof DiagnosticCode)[keyof typeof DiagnosticCode];
|
|
12
|
+
export type DiagnosticSeverity = "error" | "warning";
|
|
13
|
+
export interface Diagnostic {
|
|
14
|
+
readonly code: DiagnosticCode;
|
|
15
|
+
readonly severity: DiagnosticSeverity;
|
|
16
|
+
readonly message: string;
|
|
17
|
+
/** doc id (path relative to input root) for display. */
|
|
18
|
+
readonly docId?: string;
|
|
19
|
+
/** the $ref string that caused the diagnostic. */
|
|
20
|
+
readonly ref?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function createDiagnostic(code: DiagnosticCode, severity: DiagnosticSeverity, message: string, location?: {
|
|
23
|
+
docId?: string;
|
|
24
|
+
ref?: string;
|
|
25
|
+
}): Diagnostic;
|
|
26
|
+
/** Format a single diagnostic as `[docId] → [$ref] → message`. */
|
|
27
|
+
export declare function formatDiagnostic(diag: Diagnostic): string;
|
|
28
|
+
/** Aggregated error carrying all collected diagnostics. Thrown once at the end of a run. */
|
|
29
|
+
export declare class Json2CodeError extends Error {
|
|
30
|
+
readonly diagnostics: Diagnostic[];
|
|
31
|
+
constructor(diagnostics: Diagnostic[]);
|
|
32
|
+
}
|
|
33
|
+
/** Separate error-level from warning-level diagnostics. */
|
|
34
|
+
export declare function partitionDiagnostics(diagnostics: readonly Diagnostic[]): {
|
|
35
|
+
errors: Diagnostic[];
|
|
36
|
+
warnings: Diagnostic[];
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
//# sourceMappingURL=errors.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/diagnostics/errors.ts"],"mappings":";qBAAa;WACZ;WACA;WACA;WACA;WACA;WACA;WACA;;YAGW,yBAAyB,6BAA6B;YAEtD;iBAEK;WACP,MAAM;WACN,UAAU;WACV;;WAEA;;WAEA;;wBAGM,iBACf,MAAM,gBACN,UAAU,oBACV,iBACA;EAAa;EAAgB;IAC3B;;wBAKa,iBAAiB,MAAM;;qBAa1B,uBAAuB;WACnB,aAAa;EAE7B,YAAmB,aAAa;;;wBAQjB,qBAAqB,sBAAsB;EAC1D,QAAQ;EACR,UAAU"}
|