skir-codemirror-plugin 1.0.2 → 1.0.4

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 (42) hide show
  1. package/README.md +15 -6
  2. package/dist/codemirror/create_editor_state.d.ts +4 -2
  3. package/dist/codemirror/create_editor_state.d.ts.map +1 -1
  4. package/dist/codemirror/create_editor_state.js +178 -31
  5. package/dist/codemirror/create_editor_state.js.map +1 -1
  6. package/dist/codemirror/json_linter.d.ts.map +1 -1
  7. package/dist/codemirror/json_linter.js +67 -3
  8. package/dist/codemirror/json_linter.js.map +1 -1
  9. package/dist/codemirror/json_state.d.ts +5 -1
  10. package/dist/codemirror/json_state.d.ts.map +1 -1
  11. package/dist/codemirror/json_state.js +26 -1
  12. package/dist/codemirror/json_state.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/json/json_parser.js +35 -1
  17. package/dist/json/json_parser.js.map +1 -1
  18. package/dist/json/json_parser.test.js +48 -0
  19. package/dist/json/json_parser.test.js.map +1 -1
  20. package/dist/json/schema_validator.d.ts.map +1 -1
  21. package/dist/json/schema_validator.js +22 -14
  22. package/dist/json/schema_validator.js.map +1 -1
  23. package/dist/json/schema_validator.test.js +146 -5
  24. package/dist/json/schema_validator.test.js.map +1 -1
  25. package/dist/json/to_json.d.ts +1 -1
  26. package/dist/json/to_json.d.ts.map +1 -1
  27. package/dist/json/to_json.js +3 -3
  28. package/dist/json/to_json.js.map +1 -1
  29. package/dist/json/types.d.ts +4 -0
  30. package/dist/json/types.d.ts.map +1 -1
  31. package/dist/json/types.js.map +1 -1
  32. package/package.json +4 -6
  33. package/src/codemirror/create_editor_state.ts +272 -31
  34. package/src/codemirror/json_linter.ts +89 -4
  35. package/src/codemirror/json_state.ts +44 -1
  36. package/src/index.ts +1 -0
  37. package/src/json/json_parser.test.ts +51 -0
  38. package/src/json/json_parser.ts +37 -1
  39. package/src/json/schema_validator.test.ts +166 -5
  40. package/src/json/schema_validator.ts +26 -13
  41. package/src/json/to_json.ts +3 -3
  42. package/src/json/types.ts +7 -0
@@ -9,6 +9,7 @@ import { parseJsonValue } from "../json/json_parser";
9
9
  import { validateSchema } from "../json/schema_validator";
10
10
  import type {
11
11
  JsonParseResult,
12
+ RecordDefinition,
12
13
  TypeDefinition,
13
14
  ValidationResult,
14
15
  } from "../json/types";
@@ -17,6 +18,8 @@ export interface JsonState {
17
18
  readonly parseResult: JsonParseResult;
18
19
  readonly validationResult?: ValidationResult;
19
20
  readonly source: string;
21
+ readonly schema: TypeDefinition;
22
+ readonly recordIdToDefinition: { [id: string]: RecordDefinition };
20
23
  }
21
24
 
22
25
  const updateJsonState = StateEffect.define<JsonState>();
@@ -58,7 +61,15 @@ export function ensureJsonState(
58
61
  validationResult = validateSchema(parseResult.value, schema);
59
62
  }
60
63
 
61
- const newState: JsonState = { parseResult, validationResult, source };
64
+ const recordIdToDefinition = indexRecordDefinitions(schema, currentState);
65
+
66
+ const newState: JsonState = {
67
+ parseResult,
68
+ validationResult,
69
+ source,
70
+ schema,
71
+ recordIdToDefinition,
72
+ };
62
73
 
63
74
  // Update the state if it's different
64
75
  if (!currentState || currentState !== newState) {
@@ -139,21 +150,37 @@ export function debouncedJsonParser(schema: TypeDefinition): Extension[] {
139
150
  insert: edit.replacement,
140
151
  }));
141
152
 
153
+ const oldState = this.view.state.field(jsonStateField, false);
154
+ const recordIdToDefinition = indexRecordDefinitions(
155
+ schema,
156
+ oldState,
157
+ );
158
+
142
159
  this.view.dispatch({
143
160
  changes,
144
161
  effects: updateJsonState.of({
145
162
  parseResult,
146
163
  validationResult,
147
164
  source,
165
+ schema,
166
+ recordIdToDefinition,
148
167
  }),
149
168
  scrollIntoView: true,
150
169
  });
151
170
  } else {
171
+ const oldState = this.view.state.field(jsonStateField, false);
172
+ const recordIdToDefinition = indexRecordDefinitions(
173
+ schema,
174
+ oldState,
175
+ );
176
+
152
177
  this.view.dispatch({
153
178
  effects: updateJsonState.of({
154
179
  parseResult,
155
180
  validationResult,
156
181
  source,
182
+ schema,
183
+ recordIdToDefinition,
157
184
  }),
158
185
  });
159
186
  }
@@ -168,3 +195,19 @@ export function debouncedJsonParser(schema: TypeDefinition): Extension[] {
168
195
  ),
169
196
  ];
170
197
  }
198
+
199
+ function indexRecordDefinitions(
200
+ schema: TypeDefinition,
201
+ oldState: JsonState | null | undefined,
202
+ ): {
203
+ [id: string]: RecordDefinition;
204
+ } {
205
+ if (schema === oldState?.schema) {
206
+ return oldState.recordIdToDefinition;
207
+ }
208
+ const idToRecordDef: { [id: string]: RecordDefinition } = {};
209
+ for (const recordDef of schema.records) {
210
+ idToRecordDef[recordDef.id] = recordDef;
211
+ }
212
+ return idToRecordDef;
213
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { createEditorState } from "./codemirror/create_editor_state";
2
2
  export type {
3
+ BuiltinThemeName,
3
4
  CreateEditorStateParams,
4
5
  CustomTheme,
5
6
  } from "./codemirror/create_editor_state";
@@ -63,6 +63,18 @@ describe("json_parser", () => {
63
63
  });
64
64
  });
65
65
 
66
+ it("tracks indent for top-level literal", () => {
67
+ expect(parseJsonValue(" true")).toMatch({
68
+ value: {
69
+ kind: "literal",
70
+ jsonCode: "true",
71
+ type: "boolean",
72
+ indent: 3,
73
+ },
74
+ errors: [],
75
+ });
76
+ });
77
+
66
78
  it("parses empty array", () => {
67
79
  expect(parseJsonValue("[]")).toMatch({
68
80
  value: { kind: "array", values: [] },
@@ -119,6 +131,45 @@ describe("json_parser", () => {
119
131
  });
120
132
  });
121
133
 
134
+ it("tracks indent from line leading spaces for field values", () => {
135
+ const result = parseJsonValue(`{
136
+ "a": 1,
137
+ "b": [
138
+ 2
139
+ ]
140
+ }`);
141
+
142
+ expect(result).toMatch({
143
+ value: {
144
+ kind: "object",
145
+ indent: 0,
146
+ keyValues: {
147
+ a: {
148
+ value: {
149
+ kind: "literal",
150
+ jsonCode: "1",
151
+ indent: 2,
152
+ },
153
+ },
154
+ b: {
155
+ value: {
156
+ kind: "array",
157
+ indent: 2,
158
+ values: [
159
+ {
160
+ kind: "literal",
161
+ jsonCode: "2",
162
+ indent: 4,
163
+ },
164
+ ],
165
+ },
166
+ },
167
+ },
168
+ },
169
+ errors: [],
170
+ });
171
+ });
172
+
122
173
  it("parses arrays with whitespace", () => {
123
174
  expect(parseJsonValue(" [ 1 ] ")).toMatch({
124
175
  value: {
@@ -33,6 +33,7 @@ export function parseJsonValue(input: string): JsonParseResult {
33
33
  interface JsonToken {
34
34
  segment: Segment;
35
35
  jsonCode: string;
36
+ indent: number;
36
37
  }
37
38
 
38
39
  interface JsonTokens {
@@ -43,16 +44,29 @@ interface JsonTokens {
43
44
  function tokenize(input: string): JsonTokens | JsonError {
44
45
  const tokens: JsonToken[] = [];
45
46
  let pos = 0;
47
+ let lineIndent = 0;
48
+ let lineHasContent = false;
46
49
 
47
50
  const whitespaceRegex = /[ \t\r\n]*/y;
48
51
  const tokenRegex =
49
52
  /([[\]{}:,]|(-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)|false|true|null|("(((?=\\)\\(["\\/ bfnrt]|u[0-9a-fA-F]{4}))|[^"\\\0-\x1F\x7F]+)*")|$)/y;
50
53
 
51
54
  while (true) {
55
+ const beforeWhitespacePos = pos;
52
56
  whitespaceRegex.lastIndex = pos;
53
57
  whitespaceRegex.exec(input);
54
58
  pos = whitespaceRegex.lastIndex;
55
59
 
60
+ for (let i = beforeWhitespacePos; i < pos; ++i) {
61
+ const ch = input[i];
62
+ if (ch === "\n") {
63
+ lineIndent = 0;
64
+ lineHasContent = false;
65
+ } else if (!lineHasContent && (ch === " " || ch === "\t")) {
66
+ lineIndent++;
67
+ }
68
+ }
69
+
56
70
  tokenRegex.lastIndex = pos;
57
71
  const tokenMatch = tokenRegex.exec(input);
58
72
  if (tokenMatch) {
@@ -61,9 +75,16 @@ function tokenize(input: string): JsonTokens | JsonError {
61
75
  start: pos,
62
76
  end: pos + tokenText.length,
63
77
  };
64
- const token: JsonToken = { segment, jsonCode: tokenText };
78
+ const token: JsonToken = {
79
+ segment,
80
+ jsonCode: tokenText,
81
+ indent: lineIndent,
82
+ };
65
83
  pos = tokenRegex.lastIndex;
66
84
  tokens.push(token);
85
+ if (tokenText !== "") {
86
+ lineHasContent = true;
87
+ }
67
88
  if (tokenText === "") {
68
89
  return {
69
90
  kind: "tokens",
@@ -96,6 +117,8 @@ class JsonParser {
96
117
  const token = this.peekToken();
97
118
  const firstChar = token.jsonCode ? token.jsonCode[0] : "";
98
119
 
120
+ const { indent } = token;
121
+
99
122
  switch (firstChar) {
100
123
  case "[":
101
124
  return this.parseArray();
@@ -109,6 +132,7 @@ class JsonParser {
109
132
  segment: token.segment,
110
133
  jsonCode: "null",
111
134
  type: "null",
135
+ indent,
112
136
  };
113
137
  case "f":
114
138
  this.nextToken();
@@ -118,6 +142,7 @@ class JsonParser {
118
142
  segment: token.segment,
119
143
  jsonCode: "false",
120
144
  type: "boolean",
145
+ indent,
121
146
  };
122
147
  case "t":
123
148
  this.nextToken();
@@ -127,6 +152,7 @@ class JsonParser {
127
152
  segment: token.segment,
128
153
  jsonCode: "true",
129
154
  type: "boolean",
155
+ indent,
130
156
  };
131
157
  case '"':
132
158
  this.nextToken();
@@ -136,6 +162,7 @@ class JsonParser {
136
162
  segment: token.segment,
137
163
  jsonCode: token.jsonCode,
138
164
  type: "string",
165
+ indent,
139
166
  };
140
167
  case "0":
141
168
  case "1":
@@ -155,6 +182,7 @@ class JsonParser {
155
182
  segment: token.segment,
156
183
  jsonCode: token.jsonCode,
157
184
  type: "number",
185
+ indent,
158
186
  };
159
187
  }
160
188
 
@@ -169,6 +197,7 @@ class JsonParser {
169
197
 
170
198
  private parseArray(): JsonArray {
171
199
  const leftBracket = this.nextToken();
200
+ const { indent } = leftBracket;
172
201
  const values: JsonValue[] = [];
173
202
  while (true) {
174
203
  if (this.peekToken().jsonCode === "]") {
@@ -181,6 +210,7 @@ class JsonParser {
181
210
  end: rightBracket.segment.end,
182
211
  },
183
212
  values,
213
+ indent,
184
214
  };
185
215
  }
186
216
  if (this.peekToken().jsonCode === "}") {
@@ -198,6 +228,7 @@ class JsonParser {
198
228
  end: wrongBracket.segment.end,
199
229
  },
200
230
  values,
231
+ indent,
201
232
  };
202
233
  }
203
234
  if (this.peekToken().jsonCode === "") {
@@ -211,6 +242,7 @@ class JsonParser {
211
242
  end: this.peekToken().segment.start,
212
243
  },
213
244
  values,
245
+ indent,
214
246
  };
215
247
  }
216
248
  const value = this.parseValueOrSkip();
@@ -233,6 +265,7 @@ class JsonParser {
233
265
 
234
266
  private parseObject(): JsonObject {
235
267
  const leftBracket = this.nextToken();
268
+ const { indent } = leftBracket;
236
269
  const keyValues: { [key: string]: JsonKeyValue } = {};
237
270
  const allKeys: JsonKey[] = [];
238
271
  while (true) {
@@ -247,6 +280,7 @@ class JsonParser {
247
280
  },
248
281
  keyValues,
249
282
  allKeys,
283
+ indent,
250
284
  };
251
285
  }
252
286
  if (this.peekToken().jsonCode === "]") {
@@ -265,6 +299,7 @@ class JsonParser {
265
299
  },
266
300
  keyValues,
267
301
  allKeys,
302
+ indent,
268
303
  };
269
304
  }
270
305
  if (this.peekToken().jsonCode === "") {
@@ -279,6 +314,7 @@ class JsonParser {
279
314
  },
280
315
  keyValues,
281
316
  allKeys,
317
+ indent,
282
318
  };
283
319
  }
284
320
  const keyToken = this.peekToken();
@@ -152,7 +152,7 @@ describe("schema_validator", () => {
152
152
  ]);
153
153
  });
154
154
 
155
- it("validates enum (string literal)", () => {
155
+ it("validates enum (string literal accepts lower and upper)", () => {
156
156
  const schema: TypeDefinition = {
157
157
  type: { kind: "record", value: "MyEnum" },
158
158
  records: [
@@ -160,14 +160,100 @@ describe("schema_validator", () => {
160
160
  kind: "enum",
161
161
  id: "MyEnum",
162
162
  variants: [
163
- { name: "First", number: 1 },
164
- { name: "Second", number: 2 },
163
+ { name: "FOO", number: 1 },
164
+ { name: "BAR", number: 2 },
165
165
  ],
166
166
  },
167
167
  ],
168
168
  };
169
- const result = validateSchema(parse('"First"'), schema);
170
- expect(result).toMatch({ errors: [] });
169
+ const lowerResult = validateSchema(parse('"foo"'), schema);
170
+ expect(lowerResult).toMatch({ errors: [] });
171
+
172
+ const upperResult = validateSchema(parse('"FOO"'), schema);
173
+ expect(upperResult).toMatch({ errors: [] });
174
+ });
175
+
176
+ it("rejects mixed-case enum string literal", () => {
177
+ const schema: TypeDefinition = {
178
+ type: { kind: "record", value: "MyEnum" },
179
+ records: [
180
+ {
181
+ kind: "enum",
182
+ id: "MyEnum",
183
+ variants: [{ name: "FOO", number: 1 }],
184
+ },
185
+ ],
186
+ };
187
+
188
+ const result = validateSchema(parse('"Foo"'), schema);
189
+ expect(result.errors).toMatch([
190
+ {
191
+ message: "Unknown variant",
192
+ },
193
+ ]);
194
+ });
195
+
196
+ it("accepts UNKNOWN and unknown enum literals", () => {
197
+ const schema: TypeDefinition = {
198
+ type: { kind: "record", value: "MyEnum" },
199
+ records: [
200
+ {
201
+ kind: "enum",
202
+ id: "MyEnum",
203
+ variants: [{ name: "FOO", number: 1 }],
204
+ },
205
+ ],
206
+ };
207
+
208
+ expect(validateSchema(parse('"UNKNOWN"'), schema)).toMatch({ errors: [] });
209
+ expect(validateSchema(parse('"unknown"'), schema)).toMatch({ errors: [] });
210
+
211
+ const mixedCaseResult = validateSchema(parse('"Unknown"'), schema);
212
+ expect(mixedCaseResult.errors).toMatch([
213
+ {
214
+ message: "Unknown variant",
215
+ },
216
+ ]);
217
+ });
218
+
219
+ it("validates enum object kind in lower and upper", () => {
220
+ const schema: TypeDefinition = {
221
+ type: { kind: "record", value: "MyEnum" },
222
+ records: [
223
+ {
224
+ kind: "enum",
225
+ id: "MyEnum",
226
+ variants: [
227
+ {
228
+ name: "COMPLEX",
229
+ number: 1,
230
+ type: { kind: "primitive", value: "int32" },
231
+ },
232
+ ],
233
+ },
234
+ ],
235
+ };
236
+
237
+ expect(
238
+ validateSchema(parse('{"kind": "complex", "value": 123}'), schema),
239
+ ).toMatch({
240
+ errors: [],
241
+ });
242
+ expect(
243
+ validateSchema(parse('{"kind": "COMPLEX", "value": 123}'), schema),
244
+ ).toMatch({
245
+ errors: [],
246
+ });
247
+
248
+ const mixedCaseResult = validateSchema(
249
+ parse('{"kind": "Complex", "value": 123}'),
250
+ schema,
251
+ );
252
+ expect(mixedCaseResult.errors).toMatch([
253
+ {
254
+ message: "Expected: lowercase or uppercase variant name",
255
+ },
256
+ ]);
171
257
  });
172
258
 
173
259
  it("reports unknown enum variant", () => {
@@ -189,6 +275,81 @@ describe("schema_validator", () => {
189
275
  ]);
190
276
  });
191
277
 
278
+ it("stores enum definition in type hint for UNKNOWN enum literal", () => {
279
+ const schema: TypeDefinition = {
280
+ type: { kind: "record", value: "MyEnum" },
281
+ records: [
282
+ {
283
+ kind: "enum",
284
+ id: "MyEnum",
285
+ variants: [
286
+ { name: "First", number: 1 },
287
+ { name: "Second", number: 2 },
288
+ ],
289
+ },
290
+ ],
291
+ };
292
+
293
+ const result = validateSchema(parse('"UNKNOWN"'), schema);
294
+ expect(result.errors).toMatch([]);
295
+ if (!result.rootTypeHint) {
296
+ throw new Error("Expected root type hint");
297
+ }
298
+ expect(result.rootTypeHint.enumDefinition).toMatch({
299
+ kind: "enum",
300
+ id: "MyEnum",
301
+ variants: [
302
+ { name: "First", number: 1 },
303
+ { name: "Second", number: 2 },
304
+ ],
305
+ });
306
+ });
307
+
308
+ it("stores enum definition in nested UNKNOWN enum field hint", () => {
309
+ const schema: TypeDefinition = {
310
+ type: { kind: "record", value: "MyStruct" },
311
+ records: [
312
+ {
313
+ kind: "struct",
314
+ id: "MyStruct",
315
+ fields: [
316
+ {
317
+ name: "status",
318
+ number: 1,
319
+ type: { kind: "record", value: "MyEnum" },
320
+ },
321
+ ],
322
+ },
323
+ {
324
+ kind: "enum",
325
+ id: "MyEnum",
326
+ variants: [
327
+ { name: "First", number: 1 },
328
+ { name: "Second", number: 2 },
329
+ ],
330
+ },
331
+ ],
332
+ };
333
+
334
+ const result = validateSchema(parse('{"status": "UNKNOWN"}'), schema);
335
+ expect(result.errors).toMatch([]);
336
+ if (!result.rootTypeHint) {
337
+ throw new Error("Expected root type hint");
338
+ }
339
+ if (result.rootTypeHint.childHints.length !== 1) {
340
+ throw new Error("Expected one child type hint");
341
+ }
342
+ const statusHint = result.rootTypeHint.childHints[0];
343
+ expect(statusHint.enumDefinition).toMatch({
344
+ kind: "enum",
345
+ id: "MyEnum",
346
+ variants: [
347
+ { name: "First", number: 1 },
348
+ { name: "Second", number: 2 },
349
+ ],
350
+ });
351
+ });
352
+
192
353
  it("validates enum (object with kind)", () => {
193
354
  const schema: TypeDefinition = {
194
355
  type: { kind: "record", value: "MyEnum" },
@@ -1,6 +1,7 @@
1
1
  import { primitiveSerializer } from "skir-client";
2
2
  import { toJson } from "./to_json";
3
3
  import type {
4
+ EnumDefinition,
4
5
  FieldDefinition,
5
6
  Hint,
6
7
  JsonError,
@@ -60,7 +61,9 @@ class SchemaValidator {
60
61
  const { idToRecordDef, typeHintStack } = this;
61
62
  value.expectedType = schema;
62
63
  // For every call to pushTypeHint() there emust be one call to typeHintStack.pop()
63
- const pushTypeHint = (): void => {
64
+ const pushTypeHint = (options?: {
65
+ enumDefinition?: EnumDefinition;
66
+ }): void => {
64
67
  const typeDesc = getTypeDesc(optionalSchema ?? schema);
65
68
  const typeDoc = getTypeDoc(schema, idToRecordDef);
66
69
  const message = [typeDesc];
@@ -78,6 +81,7 @@ class SchemaValidator {
78
81
  message: message.length === 1 ? message[0] : message,
79
82
  valueContext: { value, path },
80
83
  childHints: [],
84
+ enumDefinition: options?.enumDefinition,
81
85
  };
82
86
  if (typeHintStack.length) {
83
87
  const topOfStack = typeHintStack[typeHintStack.length - 1];
@@ -187,7 +191,8 @@ class SchemaValidator {
187
191
  // Enum
188
192
  const nameToVariantDef: { [name: string]: VariantDefinition } = {};
189
193
  recordDef.variants.forEach((variant) => {
190
- nameToVariantDef[variant.name] = variant;
194
+ nameToVariantDef[variant.name.toLowerCase()] = variant;
195
+ nameToVariantDef[variant.name.toUpperCase()] = variant;
191
196
  });
192
197
  if (value.kind === "object") {
193
198
  pushTypeHint();
@@ -196,8 +201,14 @@ class SchemaValidator {
196
201
  } else if (value.kind === "literal" && value.type === "string") {
197
202
  const name = JSON.parse(value.jsonCode);
198
203
  const fieldDef = nameToVariantDef[name];
199
- if (name === "UNKNOWN" || fieldDef) {
200
- pushTypeHint();
204
+ if (fieldDef && fieldDef.type) {
205
+ this.errors.push({
206
+ kind: "error",
207
+ segment: value.segment,
208
+ message: "Expected: uppercase variant name",
209
+ });
210
+ } else if (name === "unknown" || name === "UNKNOWN" || fieldDef) {
211
+ pushTypeHint({ enumDefinition: recordDef });
201
212
  typeHintStack.pop();
202
213
  } else {
203
214
  this.errors.push({
@@ -246,11 +257,13 @@ class SchemaValidator {
246
257
  return;
247
258
  }
248
259
  const kind: string = JSON.parse(kindKv.value.jsonCode);
249
- if (kind !== kind.toLowerCase()) {
260
+ const isLowercase = kind === kind.toLowerCase();
261
+ const isUppercase = kind === kind.toUpperCase();
262
+ if (!isLowercase && !isUppercase) {
250
263
  this.errors.push({
251
264
  kind: "error",
252
265
  segment: kindKv.value.segment,
253
- message: "Expected: lowercase variant name",
266
+ message: "Expected: lowercase or uppercase variant name",
254
267
  });
255
268
  return;
256
269
  }
@@ -492,13 +505,13 @@ function isFloat(value: JsonValue): boolean {
492
505
  }
493
506
  if (value.type === "number") {
494
507
  return true;
495
- } else if (value.type === "string") {
496
- try {
497
- primitiveSerializer("float64").fromJsonCode(value.jsonCode);
498
- return true;
499
- } catch {
500
- return false;
501
- }
508
+ } else if (
509
+ value.type === "string" &&
510
+ (value.jsonCode === '"NaN"' ||
511
+ value.jsonCode === '"Infinity"' ||
512
+ value.jsonCode === '"-Infinity"')
513
+ ) {
514
+ return true;
502
515
  } else {
503
516
  return false;
504
517
  }
@@ -22,7 +22,7 @@ export function toJson(value: JsonValue): Json {
22
22
  export function makeJsonTemplate(
23
23
  type: TypeSignature,
24
24
  idToRecordDef: { [id: string]: RecordDefinition },
25
- depth?: "depth",
25
+ nested?: "nested",
26
26
  ): Json {
27
27
  switch (type.kind) {
28
28
  case "array": {
@@ -34,13 +34,13 @@ export function makeJsonTemplate(
34
34
  case "record": {
35
35
  const recordDef = idToRecordDef[type.value]!;
36
36
  if (recordDef.kind === "struct") {
37
- if (depth) {
37
+ if (nested) {
38
38
  return {};
39
39
  }
40
40
  return Object.fromEntries(
41
41
  recordDef.fields.map((field) => [
42
42
  field.name,
43
- makeJsonTemplate(field.type!, idToRecordDef, "depth"),
43
+ makeJsonTemplate(field.type!, idToRecordDef, "nested"),
44
44
  ]),
45
45
  );
46
46
  } else {
package/src/json/types.ts CHANGED
@@ -39,6 +39,7 @@ export interface JsonArray {
39
39
  /// From '[' to ']' included.
40
40
  readonly segment: Segment;
41
41
  readonly values: JsonValue[];
42
+ readonly indent: number;
42
43
  expectedType?: TypeSignature;
43
44
  }
44
45
 
@@ -63,6 +64,7 @@ export interface JsonObject {
63
64
  readonly keyValues: { [key: string]: JsonKeyValue };
64
65
  /// Includes "broken" keys which produced a parsing error.
65
66
  readonly allKeys: readonly JsonKey[];
67
+ readonly indent: number;
66
68
  expectedType?: TypeSignature;
67
69
  }
68
70
 
@@ -73,6 +75,7 @@ export interface JsonLiteral {
73
75
  readonly segment: Segment;
74
76
  readonly jsonCode: string;
75
77
  readonly type: "boolean" | "null" | "number" | "string";
78
+ readonly indent: number;
76
79
  expectedType?: TypeSignature;
77
80
  }
78
81
 
@@ -223,6 +226,10 @@ export interface TypeHint {
223
226
  readonly valueContext: JsonValueContext;
224
227
  /// In order. All are included in 'valueContext.value.segment'.
225
228
  readonly childHints: readonly TypeHint[];
229
+ /// Set if the expected type is an enum and the value is a literal string.
230
+ /// In that case, the editor can show a dropdown with the possible variants
231
+ /// of the enum.
232
+ readonly enumDefinition?: EnumDefinition;
226
233
  }
227
234
 
228
235
  export type Hint =