-
Notifications
You must be signed in to change notification settings - Fork 549
feat(tree): Add schema-agnostic tree traversal APIs #24723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f176d1b
1e83e40
98d8370
678c3d1
c4db625
1f889d5
a660773
31711ec
3a91603
4f746f8
3a65e66
3f71684
8e6cf78
4a9c7fa
adde8a2
205da98
90f1421
25edf40
ab3b51c
a83bf23
b4dc251
5f2a1fa
efe6128
6b81132
ab5e998
cc3f3c4
5df2ff8
1db1c8a
9028ff1
3c945fe
32c338a
0bb00a6
036281d
e7b991a
4fb6a55
d46f898
93749da
08bb642
971068b
9d78702
7727527
46edc57
35d620c
42dd810
c336dad
855821f
fcfad7b
3fda363
cb84ba9
379309b
ebc416c
f704420
5f7abb0
6939f06
78b2726
1dffc26
515c9d2
51b560d
bbf311f
a6c47aa
43e35cf
9c9fa80
08ebd14
87a1053
3ec4fcf
97135f9
b5fe9e3
84c1eed
3100139
72c6a88
37a0207
9f550dc
304b7a6
d2bc8c4
345e662
191f9d1
7861fc5
3779201
6acc1c9
84dd650
a0fe526
cefe00e
9f76176
bbe7246
35bf511
31ae6fd
2b8a98e
18675ed
466d6bc
3bae5c1
5f5d4fd
14cdf4d
4b3886c
74d60ef
c4dfa55
914ca40
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
--- | ||
"@fluidframework/tree": minor | ||
"fluid-framework": minor | ||
"__section": tree | ||
--- | ||
Add TreeAlpha.child and TreeAlpha.children APIs for generic tree traversal | ||
|
||
#### TreeAlpha.child | ||
Check warning on line 8 in .changeset/seven-toes-smell.md
|
||
|
||
Access a child node or value of a `TreeNode` by its property key. | ||
|
||
```typescript | ||
class MyObject extends schemaFactory.object("MyObject", { | ||
foo: schemaFactory.string; | ||
bar: schemaFactory.optional(schemaFactory.string); | ||
}) {} | ||
|
||
const myObject = new MyObject({ | ||
foo: "Hello world!" | ||
}); | ||
|
||
const foo = TreeAlpha.child(myObject, "foo"); // "Hello world!" | ||
const bar = TreeAlpha.child(myObject, "bar"); // undefined | ||
const baz = TreeAlpha.child(myObject, "baz"); // undefined | ||
``` | ||
|
||
```typescript | ||
class MyArray extends schemaFactory.array("MyArray", schemaFactory.string) {} | ||
|
||
const myArray = new MyArray("Hello", "World"); | ||
|
||
const child0 = TreeAlpha.child(myArray, 0); // "Hello" | ||
const child1 = TreeAlpha.child(myArray, 1); // "World | ||
const child2 = TreeAlpha.child(myArray, 2); // undefined | ||
``` | ||
|
||
#### TreeAlpha.children | ||
Check warning on line 37 in .changeset/seven-toes-smell.md
|
||
|
||
Get all child nodes / values of a `TreeNode`, keyed by their property keys. | ||
|
||
```typescript | ||
class MyObject extends schemaFactory.object("MyObject", { | ||
foo: schemaFactory.string; | ||
bar: schemaFactory.optional(schemaFactory.string); | ||
baz: schemaFactory.optional(schemaFactory.number); | ||
}) {} | ||
|
||
const myObject = new MyObject({ | ||
foo: "Hello world!", | ||
baz: 42, | ||
}); | ||
|
||
const children = TreeAlpha.children(myObject); // [["foo", "Hello world!"], ["baz", 42]] | ||
``` | ||
|
||
```typescript | ||
class MyArray extends schemaFactory.array("MyArray", schemaFactory.string) {} | ||
|
||
const myArray = new MyArray("Hello", "World"); | ||
|
||
const children = TreeAlpha.children(myObject); // [[0, "Hello"], [1, "World"]] | ||
``` |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -3,13 +3,19 @@ | |||||||||||||||||||||||||||
* Licensed under the MIT License. | ||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
import { assert, fail } from "@fluidframework/core-utils/internal"; | ||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||
assert, | ||||||||||||||||||||||||||||
debugAssert, | ||||||||||||||||||||||||||||
fail, | ||||||||||||||||||||||||||||
unreachableCase, | ||||||||||||||||||||||||||||
} from "@fluidframework/core-utils/internal"; | ||||||||||||||||||||||||||||
import { createIdCompressor } from "@fluidframework/id-compressor/internal"; | ||||||||||||||||||||||||||||
import { UsageError } from "@fluidframework/telemetry-utils/internal"; | ||||||||||||||||||||||||||||
import type { IFluidHandle } from "@fluidframework/core-interfaces"; | ||||||||||||||||||||||||||||
import type { IIdCompressor } from "@fluidframework/id-compressor"; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||
asIndex, | ||||||||||||||||||||||||||||
getKernel, | ||||||||||||||||||||||||||||
type TreeNode, | ||||||||||||||||||||||||||||
type Unhydrated, | ||||||||||||||||||||||||||||
|
@@ -40,15 +46,20 @@ import { | |||||||||||||||||||||||||||
getIdentifierFromNode, | ||||||||||||||||||||||||||||
unhydratedFlexTreeFromInsertable, | ||||||||||||||||||||||||||||
getOrCreateNodeFromInnerNode, | ||||||||||||||||||||||||||||
getOrCreateNodeFromInnerUnboxedNode, | ||||||||||||||||||||||||||||
getOrCreateInnerNode, | ||||||||||||||||||||||||||||
NodeKind, | ||||||||||||||||||||||||||||
tryGetTreeNodeForField, | ||||||||||||||||||||||||||||
isObjectNodeSchema, | ||||||||||||||||||||||||||||
} from "../simple-tree/index.js"; | ||||||||||||||||||||||||||||
import { extractFromOpaque, type JsonCompatible } from "../util/index.js"; | ||||||||||||||||||||||||||||
import { brand, extractFromOpaque, type JsonCompatible } from "../util/index.js"; | ||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||
FluidClientVersion, | ||||||||||||||||||||||||||||
noopValidator, | ||||||||||||||||||||||||||||
type ICodecOptions, | ||||||||||||||||||||||||||||
type CodecWriteOptions, | ||||||||||||||||||||||||||||
} from "../codec/index.js"; | ||||||||||||||||||||||||||||
import type { ITreeCursorSynchronous } from "../core/index.js"; | ||||||||||||||||||||||||||||
import { EmptyKey, type ITreeCursorSynchronous } from "../core/index.js"; | ||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||
cursorForMapTreeField, | ||||||||||||||||||||||||||||
defaultSchemaPolicy, | ||||||||||||||||||||||||||||
|
@@ -60,6 +71,7 @@ import { | |||||||||||||||||||||||||||
type FieldBatchEncodingContext, | ||||||||||||||||||||||||||||
fluidVersionToFieldBatchCodecWriteVersion, | ||||||||||||||||||||||||||||
type LocalNodeIdentifier, | ||||||||||||||||||||||||||||
type FlexTreeSequenceField, | ||||||||||||||||||||||||||||
} from "../feature-libraries/index.js"; | ||||||||||||||||||||||||||||
import { independentInitializedView, type ViewContent } from "./independentView.js"; | ||||||||||||||||||||||||||||
import { SchematizingSimpleTreeView, ViewSlot } from "./schematizingTreeView.js"; | ||||||||||||||||||||||||||||
|
@@ -343,6 +355,48 @@ export interface TreeAlpha { | |||||||||||||||||||||||||||
* Otherwise, this returns the key of the field that it is under (a `string`). | ||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||
key2(node: TreeNode): string | number | undefined; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||
* Gets the child of the given node with the given property key if a child exists under that key. | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* @remarks {@link SchemaFactoryObjectOptions.allowUnknownOptionalFields | Unknown optional fields} of Object nodes will not be returned by this method. | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* @param node - The parent node whose child is being requested. | ||||||||||||||||||||||||||||
* @param key - The property key under the node under which the child is being requested. | ||||||||||||||||||||||||||||
* For Object nodes, this is the developer-facing "property key", not the "{@link SimpleObjectFieldSchema.storedKey | stored keys}". | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* @returns The child node or leaf value under the given key, or `undefined` if no such child exists. | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* @see {@link (TreeAlpha:interface).key2} | ||||||||||||||||||||||||||||
* @see {@link (TreeNodeApi:interface).parent} | ||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||
child(node: TreeNode, key: string | number): TreeNode | TreeLeafValue | undefined; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||
* Gets the children of the provided node, paired with their property keys under the node. | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* @remarks | ||||||||||||||||||||||||||||
* No guarantees are made regarding the order of the children in the returned array. | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curiosity: when accessing the children of an array node, I imagine we probably do return them in order? No need to make it part of the contract though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In practice, that appears to be true, yes. |
||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* Optional properties of Object nodes with no value are not included in the result. | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* {@link SchemaFactoryObjectOptions.allowUnknownOptionalFields | Unknown optional fields} of Object nodes are not included in the result. | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* @param node - The node whose children are being requested. | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* @returns | ||||||||||||||||||||||||||||
* An array of pairs of the form `[propertyKey, child]`. | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* For Array nodes, the `propertyKey` is the index of the child in the array. | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* For Object nodes, the returned `propertyKey`s are the developer-facing "property keys", not the "{@link SimpleObjectFieldSchema.storedKey | stored keys}". | ||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||
* @see {@link (TreeAlpha:interface).key2} | ||||||||||||||||||||||||||||
* @see {@link (TreeNodeApi:interface).parent} | ||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||
children( | ||||||||||||||||||||||||||||
node: TreeNode, | ||||||||||||||||||||||||||||
): Iterable<[propertyKey: string | number, child: TreeNode | TreeLeafValue]>; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||
|
@@ -492,6 +546,134 @@ export const TreeAlpha: TreeAlpha = { | |||||||||||||||||||||||||||
const parentSchema = treeNodeApi.schema(parent); | ||||||||||||||||||||||||||||
return getPropertyKeyFromStoredKey(parentSchema, storedKey); | ||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
child: ( | ||||||||||||||||||||||||||||
node: TreeNode, | ||||||||||||||||||||||||||||
propertyKey: string | number, | ||||||||||||||||||||||||||||
): TreeNode | TreeLeafValue | undefined => { | ||||||||||||||||||||||||||||
const flexNode = getOrCreateInnerNode(node); | ||||||||||||||||||||||||||||
debugAssert( | ||||||||||||||||||||||||||||
() => !flexNode.context.isDisposed() || "The provided tree node has been disposed.", | ||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
const schema = treeNodeApi.schema(node); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
switch (schema.kind) { | ||||||||||||||||||||||||||||
case NodeKind.Array: { | ||||||||||||||||||||||||||||
const sequence = flexNode.tryGetField(EmptyKey) as FlexTreeSequenceField | undefined; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// Empty sequence - cannot have children. | ||||||||||||||||||||||||||||
if (sequence === undefined) { | ||||||||||||||||||||||||||||
return undefined; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
const index = | ||||||||||||||||||||||||||||
typeof propertyKey === "number" | ||||||||||||||||||||||||||||
? propertyKey | ||||||||||||||||||||||||||||
: asIndex(propertyKey, Number.POSITIVE_INFINITY); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// If the key is not a valid index, then there is no corresponding child. | ||||||||||||||||||||||||||||
if (index === undefined) { | ||||||||||||||||||||||||||||
return undefined; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
Comment on lines
+570
to
+578
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me that we should be tolerating strings containing numbers to be used as keys into Arays here. From reading our APi docs and types, I would not expect this to be supported.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @CraigMacomber I can remove this support, but I'm wondering how consistent we've been in this regard. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I don't think that's what we want. POJO arrays allow both (implicitly numbers), and POJO objects allow both (implicitly strings), so it seems like we should follow suit. Maps are an exception here. And maybe we want to only allow string keys for those. But since we strictly support string keys in our maps, matching the policy for Objects may make more sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we just preserve current behavior? I think the current behavior is that numbers are allowed for arrays, but not for objects and maps. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that number keys are allowed when accessing Object node properties (though not Map node properties). E.g. class TestObject extends schema.object("TestObject", {
"0": schema.string,
}) {}
const test = new TestObject({
0: "Hello world!"
});
const zero = test[0]; // "Hello world!" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, what I have is consistent with our standard property access for Arrays and Objects. And this is consistent with POJO. So, I'm pretty strongly convinced that deviating from this would be a bad idea. With the exception of Map nodes. I think there is room for longer term discussion about maybe allowing use of numeric keys with our maps, since they strictly support string keys. But for now, that isn't supported, so it makes the most sense to be consistent with that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is literally impossible to implement arrays without supporting string keys, since JavaScript does not support numeric keys: they are stringified. The property key received by a proxy can never be a number: it is always a string, even when the user provides a number. Our arrays support string keys exactly the same as JavaScript arrays, meaning we use typescript to tell you to pass in a number, which JS converts to a string behind the scenes. We do not support strings in places where not supporting strings is possible, for example in "at". Our code for converting string indexes back to numbers exists to make indexing with numbers work: the fact indexing with strings works is an unfortunate side effect of this very questionable language. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For example ["x"]["0"] gives "x" just like ["x"][0] does, and the proxy can't tell those two lookups apart as both search for a member under the key "0". Note that this also gives "x": There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think my main point can be summed up by the following: Array nodes can be indexed using string-formatted integer keys, so I don't think it makes sense for them not to work via class MyArray extends schemaFactory.array("my-array", schemaFactory.string) {}
const myArray = new MyArray(["Hello world"]);
myArray["0"]; // "Hello world"
Tree.child(myArray, "0"); // Should match
class MyObject extends schema.object("MyObject", {
"1": SchemaFactory.optional(schema.string),
}) {}
const myObject = new MyObject({ 1: "Hello world" });
myObject[1]; // "Hello world"
TreeAlpha.child(myObject, 1); // Should match Maps are a different story, and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW, @Josmithr and I talked offline and I think the idea of consistency with POJO is compelling. I strongly disagree with the idea of possibly wanting maps to support something similar, as I think a very important property of maps is that keys that compare equal should look up the same value, and keys that do not should not. If we break that contract, I think this would be a massive bug pit, and I don't see any offsetting reason to do so. Setting that aside, I'll vote in favor of consistency with POJO :) |
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
const childFlexTree = sequence.at(index); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
// No child at the given index. | ||||||||||||||||||||||||||||
if (childFlexTree === undefined) { | ||||||||||||||||||||||||||||
return undefined; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
return getOrCreateNodeFromInnerUnboxedNode(childFlexTree); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
case NodeKind.Map: | ||||||||||||||||||||||||||||
if (typeof propertyKey !== "string") { | ||||||||||||||||||||||||||||
// Map nodes only support string keys. | ||||||||||||||||||||||||||||
return undefined; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
// Fall through | ||||||||||||||||||||||||||||
case NodeKind.Object: { | ||||||||||||||||||||||||||||
let storedKey: string | number = propertyKey; | ||||||||||||||||||||||||||||
if (isObjectNodeSchema(schema)) { | ||||||||||||||||||||||||||||
const fieldSchema = schema.fields.get(String(propertyKey)); | ||||||||||||||||||||||||||||
if (fieldSchema === undefined) { | ||||||||||||||||||||||||||||
return undefined; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
storedKey = fieldSchema.storedKey; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
const field = flexNode.tryGetField(brand(String(storedKey))); | ||||||||||||||||||||||||||||
if (field !== undefined) { | ||||||||||||||||||||||||||||
return tryGetTreeNodeForField(field); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
return undefined; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
case NodeKind.Leaf: { | ||||||||||||||||||||||||||||
fail("Leaf schema associated with non-leaf tree node."); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
default: { | ||||||||||||||||||||||||||||
unreachableCase(schema.kind); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
children(node: TreeNode): [propertyKey: string | number, child: TreeNode | TreeLeafValue][] { | ||||||||||||||||||||||||||||
const flexNode = getOrCreateInnerNode(node); | ||||||||||||||||||||||||||||
debugAssert( | ||||||||||||||||||||||||||||
() => !flexNode.context.isDisposed() || "The provided tree node has been disposed.", | ||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
const schema = treeNodeApi.schema(node); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
const result: [string | number, TreeNode | TreeLeafValue][] = []; | ||||||||||||||||||||||||||||
switch (schema.kind) { | ||||||||||||||||||||||||||||
case NodeKind.Array: { | ||||||||||||||||||||||||||||
const sequence = flexNode.tryGetField(EmptyKey) as FlexTreeSequenceField | undefined; | ||||||||||||||||||||||||||||
if (sequence === undefined) { | ||||||||||||||||||||||||||||
break; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
for (let index = 0; index < sequence.length; index++) { | ||||||||||||||||||||||||||||
const childFlexTree = sequence.at(index); | ||||||||||||||||||||||||||||
assert(childFlexTree !== undefined, "Sequence child was undefined."); | ||||||||||||||||||||||||||||
const childTree = getOrCreateNodeFromInnerUnboxedNode(childFlexTree); | ||||||||||||||||||||||||||||
result.push([index, childTree]); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
break; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
case NodeKind.Map: { | ||||||||||||||||||||||||||||
for (const [key, flexField] of flexNode.fields) { | ||||||||||||||||||||||||||||
const childTreeNode = tryGetTreeNodeForField(flexField); | ||||||||||||||||||||||||||||
if (childTreeNode !== undefined) { | ||||||||||||||||||||||||||||
result.push([key, childTreeNode]); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
break; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
case NodeKind.Object: { | ||||||||||||||||||||||||||||
assert(isObjectNodeSchema(schema), "Expected object schema."); | ||||||||||||||||||||||||||||
for (const [propertyKey, fieldSchema] of schema.fields) { | ||||||||||||||||||||||||||||
const storedKey = fieldSchema.storedKey; | ||||||||||||||||||||||||||||
const flexField = flexNode.tryGetField(brand(String(storedKey))); | ||||||||||||||||||||||||||||
if (flexField !== undefined) { | ||||||||||||||||||||||||||||
const childTreeNode = tryGetTreeNodeForField(flexField); | ||||||||||||||||||||||||||||
assert(childTreeNode !== undefined, "Expected child tree node for field."); | ||||||||||||||||||||||||||||
result.push([propertyKey, childTreeNode]); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
break; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
case NodeKind.Leaf: { | ||||||||||||||||||||||||||||
fail("Leaf schema associated with non-leaf tree node."); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
default: { | ||||||||||||||||||||||||||||
unreachableCase(schema.kind); | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
return result; | ||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
function exportConcise( | ||||||||||||||||||||||||||||
|
Uh oh!
There was an error while loading. Please reload this page.