@khanacademy/perseus 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +5 -0
  3. package/dist/es/index.css +1 -436
  4. package/dist/es/index.js +13785 -16058
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +1 -436
  7. package/dist/index.js +3200 -5480
  8. package/dist/index.js.map +1 -1
  9. package/package.json +5 -3
  10. package/src/__tests__/jipt-paragraphs_test.js +34 -0
  11. package/src/__tests__/perseus-markdown_test.jsx +6 -6
  12. package/src/__tests__/renderer_test.jsx +9 -9
  13. package/src/article-renderer.jsx +4 -5
  14. package/src/components/input-with-examples.jsx +4 -5
  15. package/src/components/lint.jsx +2 -1
  16. package/src/components/sortable.jsx +6 -7
  17. package/src/hint-renderer.jsx +3 -4
  18. package/src/hints-renderer.jsx +3 -4
  19. package/src/item-renderer.jsx +4 -5
  20. package/src/jipt-paragraphs.js +16 -8
  21. package/src/jquery.mobile.vmouse.js +2 -1
  22. package/src/perseus-markdown.jsx +3 -3
  23. package/src/renderer.jsx +31 -33
  24. package/src/styles/perseus-renderer.less +1 -1
  25. package/src/{not-gorgon.js → translation-linter.js} +10 -14
  26. package/src/widget-container.jsx +1 -1
  27. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +30 -12
  28. package/src/widgets/__tests__/definition_test.js +18 -33
  29. package/src/widgets/__tests__/grapher.cypress.jsx +2 -2
  30. package/src/widgets/__tests__/interactive-graph.cypress.jsx +2 -2
  31. package/src/widgets/categorizer.jsx +1 -1
  32. package/src/widgets/definition.jsx +36 -71
  33. package/src/widgets/explanation.jsx +1 -1
  34. package/src/widgets/expression.jsx +1 -1
  35. package/src/widgets/graded-group-set.jsx +1 -1
  36. package/src/widgets/graded-group.jsx +1 -1
  37. package/src/widgets/group.jsx +1 -1
  38. package/src/widgets/image.jsx +1 -1
  39. package/src/widgets/input-number.jsx +1 -1
  40. package/src/widgets/matcher.jsx +4 -1
  41. package/src/widgets/matrix.jsx +1 -1
  42. package/src/widgets/numeric-input.jsx +1 -1
  43. package/src/widgets/orderer.jsx +4 -1
  44. package/src/widgets/passage-ref-target.jsx +4 -1
  45. package/src/widgets/passage.jsx +1 -1
  46. package/src/widgets/radio/widget.jsx +1 -1
  47. package/src/widgets/sequence.jsx +4 -1
  48. package/src/widgets/sorter.jsx +4 -1
  49. package/src/widgets/table.jsx +4 -1
  50. package/src/README.md +0 -35
  51. package/src/__tests__/__snapshots__/pure-markdown_test.js.snap +0 -113
  52. package/src/__tests__/pure-markdown_test.js +0 -552
  53. package/src/gorgon/README +0 -40
  54. package/src/gorgon/__tests__/matcher_test.js +0 -497
  55. package/src/gorgon/__tests__/rule_test.js +0 -101
  56. package/src/gorgon/__tests__/rules_test.js +0 -487
  57. package/src/gorgon/__tests__/selector-parser_test.js +0 -52
  58. package/src/gorgon/__tests__/tree-transformer_test.js +0 -446
  59. package/src/gorgon/gorgon.js +0 -290
  60. package/src/gorgon/proptypes.js +0 -29
  61. package/src/gorgon/rule.js +0 -413
  62. package/src/gorgon/rules/absolute-url.js +0 -24
  63. package/src/gorgon/rules/all-rules.js +0 -72
  64. package/src/gorgon/rules/blockquoted-math.js +0 -10
  65. package/src/gorgon/rules/blockquoted-widget.js +0 -10
  66. package/src/gorgon/rules/double-spacing-after-terminal.js +0 -12
  67. package/src/gorgon/rules/extra-content-spacing.js +0 -12
  68. package/src/gorgon/rules/heading-level-1.js +0 -14
  69. package/src/gorgon/rules/heading-level-skip.js +0 -20
  70. package/src/gorgon/rules/heading-sentence-case.js +0 -11
  71. package/src/gorgon/rules/heading-title-case.js +0 -63
  72. package/src/gorgon/rules/image-alt-text.js +0 -21
  73. package/src/gorgon/rules/image-in-table.js +0 -10
  74. package/src/gorgon/rules/image-spaces-around-urls.js +0 -35
  75. package/src/gorgon/rules/image-widget.js +0 -50
  76. package/src/gorgon/rules/link-click-here.js +0 -11
  77. package/src/gorgon/rules/lint-utils.js +0 -48
  78. package/src/gorgon/rules/long-paragraph.js +0 -14
  79. package/src/gorgon/rules/math-adjacent.js +0 -10
  80. package/src/gorgon/rules/math-align-extra-break.js +0 -11
  81. package/src/gorgon/rules/math-align-linebreaks.js +0 -43
  82. package/src/gorgon/rules/math-empty.js +0 -10
  83. package/src/gorgon/rules/math-font-size.js +0 -12
  84. package/src/gorgon/rules/math-frac.js +0 -10
  85. package/src/gorgon/rules/math-nested.js +0 -11
  86. package/src/gorgon/rules/math-starts-with-space.js +0 -12
  87. package/src/gorgon/rules/math-text-empty.js +0 -10
  88. package/src/gorgon/rules/math-without-dollars.js +0 -14
  89. package/src/gorgon/rules/nested-lists.js +0 -11
  90. package/src/gorgon/rules/profanity.js +0 -10
  91. package/src/gorgon/rules/table-missing-cells.js +0 -20
  92. package/src/gorgon/rules/unbalanced-code-delimiters.js +0 -14
  93. package/src/gorgon/rules/unescaped-dollar.js +0 -10
  94. package/src/gorgon/rules/widget-in-table.js +0 -10
  95. package/src/gorgon/selector.js +0 -506
  96. package/src/gorgon/tree-transformer.js +0 -588
  97. package/src/pure-markdown.js +0 -347
@@ -1,588 +0,0 @@
1
- // @flow
2
- /**
3
- * TreeTransformer is a class for traversing and transforming trees. Create a
4
- * TreeTransformer by passing the root node of the tree to the
5
- * constructor. Then traverse that tree by calling the traverse() method. The
6
- * argument to traverse() is a callback function that will be called once for
7
- * each node in the tree. This is a post-order depth-first traversal: the
8
- * callback is not called on the a way down, but on the way back up. That is,
9
- * the children of a node are traversed before the node itself is.
10
- *
11
- * The traversal callback function is passed three arguments, the node being
12
- * traversed, a TraversalState object, and the concatentated text content of
13
- * the node and all of its descendants. The TraversalState object is the most
14
- * most interesting argument: it has methods for querying the ancestors and
15
- * siblings of the node, and for deleting or replacing the node. These
16
- * transformation methods are why this class is a tree transformer and not
17
- * just a tree traverser.
18
- *
19
- * A typical tree traversal looks like this:
20
- *
21
- * new TreeTransformer(root).traverse((node, state, content) => {
22
- * let parent = state.parent();
23
- * let previous = state.previousSibling();
24
- * // etc.
25
- * });
26
- *
27
- * The traverse() method descends through nodes and arrays of nodes and calls
28
- * the traverse callback on each node on the way back up to the root of the
29
- * tree. (Note that it only calls the callback on the nodes themselves, not
30
- * any arrays that contain nodes.) A node is loosely defined as any object
31
- * with a string-valued `type` property. Objects that do not have a type
32
- * property are assumed to not be part of the tree and are not traversed. When
33
- * traversing an array, all elements of the array are examined, and any that
34
- * are nodes or arrays are recursively traversed. When traversing a node, all
35
- * properties of the object are examined and any node or array values are
36
- * recursively traversed. In typical parse trees, the children of a node are
37
- * in a `children` or `content` array, but this class is designed to handle
38
- * more general trees. The Perseus markdown parser, for example, produces
39
- * nodes of type "table" that have children in the `header` and `cells`
40
- * properties.
41
- *
42
- * CAUTION: the traverse() method does not make any attempt to detect
43
- * cycles. If you call it on a cyclic graph instead of a tree, it will cause
44
- * infinite recursion (or, more likely, a stack overflow).
45
- *
46
- * TODO(davidflanagan): it probably wouldn't be hard to detect cycles: when
47
- * pushing a new node onto the containers stack we could just check that it
48
- * isn't already there.
49
- *
50
- * If a node has a text-valued `content` property, it is taken to be the
51
- * plain-text content of the node. The traverse() method concatenates these
52
- * content strings and passes them to the traversal callback for each
53
- * node. This means that the callback has access the full text content of its
54
- * node and all of the nodes descendants.
55
- *
56
- * See the TraversalState class for more information on what information and
57
- * methods are available to the traversal callback.
58
- **/
59
-
60
- import {Errors} from "../logging/log.js";
61
- import {PerseusError} from "../perseus-error.js";
62
-
63
- // TreeNode is the type of a node in a parse tree. The only real requirement is
64
- // that every node has a string-valued `type` property
65
- export type TreeNode = {type: string, ...};
66
-
67
- // TraversalCallback is the type of the callback function passed to the
68
- // traverse() method. It is invoked with node, state, and content arguments
69
- // and is expected to return nothing.
70
- export type TraversalCallback = (
71
- node: TreeNode,
72
- state: TraversalState,
73
- content: string,
74
- ) => void;
75
-
76
- // This is the TreeTransformer class described in detail at the
77
- // top of this file.
78
- export default class TreeTransformer {
79
- root: TreeNode;
80
-
81
- // To create a tree transformer, just pass the root node of the tree
82
- constructor(root: TreeNode) {
83
- this.root = root;
84
- }
85
-
86
- // A utility function for determing whether an arbitrary value is a node
87
- static isNode(n: any): boolean {
88
- return n && typeof n === "object" && typeof n.type === "string";
89
- }
90
-
91
- // Determines whether a value is a node with type "text" and has
92
- // a text-valued `content` property.
93
- static isTextNode(n: any): boolean {
94
- return (
95
- TreeTransformer.isNode(n) &&
96
- n.type === "text" &&
97
- typeof n.content === "string"
98
- );
99
- }
100
-
101
- // This is the main entry point for the traverse() method. See the comment
102
- // at the top of this file for a detailed description. Note that this
103
- // method just creates a new TraversalState object to use for this
104
- // traversal and then invokes the internal _traverse() method to begin the
105
- // recursion.
106
- traverse(f: TraversalCallback): void {
107
- this._traverse(this.root, new TraversalState(this.root), f);
108
- }
109
-
110
- // Do a post-order traversal of node and its descendants, invoking the
111
- // callback function f() once for each node and returning the concatenated
112
- // text content of the node and its descendants. f() is passed three
113
- // arguments: the current node, a TraversalState object representing the
114
- // current state of the traversal, and a string that holds the
115
- // concatenated text of the node and its descendants.
116
- //
117
- // This private method holds all the traversal logic and implementation
118
- // details. Note that this method uses the TraversalState object to store
119
- // information about the structure of the tree.
120
- _traverse(
121
- // eslint-disable-next-line flowtype/no-mutable-array
122
- n: TreeNode | Array<TreeNode>,
123
- state: TraversalState,
124
- f: TraversalCallback,
125
- ): string {
126
- let content = "";
127
- if (TreeTransformer.isNode(n)) {
128
- // If we were called on a node object, then we handle it
129
- // this way.
130
- const node = ((n: any): TreeNode); // safe cast; we just tested
131
-
132
- // Put the node on the stack before recursing on its children
133
- state._containers.push(node);
134
- state._ancestors.push(node);
135
-
136
- // Record the node's text content if it has any.
137
- // Usually this is for nodes with a type property of "text",
138
- // but other nodes types like "math" may also have content.
139
- // TODO(mdr): We found a new Flow error when upgrading:
140
- // "node.content (property `content` is missing in `TreeNode` [1].)"
141
- // $FlowFixMe[prop-missing](0.57.3->0.75.0)
142
- if (typeof node.content === "string") {
143
- content = node.content;
144
- }
145
-
146
- // Recurse on the node. If there was content above, then there
147
- // probably won't be any children to recurse on, but we check
148
- // anyway.
149
- //
150
- // If we wanted to make the traversal completely specific to the
151
- // actual Perseus parse trees that we'll be dealing with we could
152
- // put a switch statement here to dispatch on the node type
153
- // property with specific recursion steps for each known type of
154
- // node.
155
- const keys = Object.keys(node);
156
- keys.forEach((key) => {
157
- // Never recurse on the type property
158
- if (key === "type") {
159
- return;
160
- }
161
- // Ignore properties that are null or primitive and only
162
- // recurse on objects and arrays. Note that we don't do a
163
- // isNode() check here. That is done in the recursive call to
164
- // _traverse(). Note that the recursive call on each child
165
- // returns the text content of the child and we add that
166
- // content to the content for this node. Also note that we
167
- // push the name of the property we're recursing over onto a
168
- // TraversalState stack.
169
- const value = node[key];
170
- if (value && typeof value === "object") {
171
- state._indexes.push(key);
172
- content += this._traverse(value, state, f);
173
- state._indexes.pop();
174
- }
175
- });
176
-
177
- // Restore the stacks after recursing on the children
178
- state._currentNode = state._ancestors.pop();
179
- state._containers.pop();
180
-
181
- // And finally call the traversal callback for this node. Note
182
- // that this is post-order traversal. We call the callback on the
183
- // way back up the tree, not on the way down. That way we already
184
- // know all the content contained within the node.
185
- f(node, state, content);
186
- } else if (Array.isArray(n)) {
187
- // If we were called on an array instead of a node, then
188
- // this is the code we use to recurse.
189
- const nodes = n;
190
-
191
- // Push the array onto the stack. This will allow the
192
- // TraversalState object to locate siblings of this node.
193
- state._containers.push(nodes);
194
-
195
- // Now loop through this array and recurse on each element in it.
196
- // Before recursing on an element, we push its array index on a
197
- // TraversalState stack so that the TraversalState sibling methods
198
- // can work. Note that TraversalState methods can alter the length
199
- // of the array, and change the index of the current node, so we
200
- // are careful here to test the array length on each iteration and
201
- // to reset the index when we pop the stack. Also note that we
202
- // concatentate the text content of the children.
203
- let index = 0;
204
- while (index < nodes.length) {
205
- state._indexes.push(index);
206
- content += this._traverse(nodes[index], state, f);
207
- // Casting to convince Flow that this is a number
208
- index = ((state._indexes.pop(): any): number) + 1;
209
- }
210
-
211
- // Pop the array off the stack. Note, however, that we do not call
212
- // the traversal callback on the array. That function is only
213
- // called for nodes, not arrays of nodes.
214
- state._containers.pop();
215
- }
216
-
217
- // The _traverse() method always returns the text content of
218
- // this node and its children. This is the one piece of state that
219
- // is not tracked in the TraversalState object.
220
- return content;
221
- }
222
- }
223
-
224
- // An instance of this class is passed to the callback function for
225
- // each node traversed. The class itself is not exported, but its
226
- // methods define the API available to the traversal callback.
227
-
228
- /**
229
- * This class represents the state of a tree traversal. An instance is created
230
- * by the traverse() method of the TreeTransformer class to maintain the state
231
- * for that traversal, and the instance is passed to the traversal callback
232
- * function for each node that is traversed. This class is not intended to be
233
- * instantiated directly, but is exported so that its type can be used for
234
- * Flow annotaions.
235
- **/
236
- export class TraversalState {
237
- // The root node of the tree being traversed
238
- root: TreeNode;
239
-
240
- // These are internal state properties. Use the accessor methods defined
241
- // below instead of using these properties directly. Note that the
242
- // _containers and _indexes stacks can have two different types of
243
- // elements, depending on whether we just recursed on an array or on a
244
- // node. This is hard for Flow to deal with, so you'll see a number of
245
- // Flow casts through the any type when working with these two properties.
246
- _currentNode: ?TreeNode;
247
- // eslint-disable-next-line flowtype/no-mutable-array
248
- _containers: Stack<TreeNode | Array<TreeNode>>;
249
- _indexes: Stack<string | number>;
250
- _ancestors: Stack<TreeNode>;
251
-
252
- // The constructor just stores the root node and creates empty stacks.
253
- constructor(root: TreeNode) {
254
- this.root = root;
255
-
256
- // When the callback is called, this property will hold the
257
- // node that is currently being traversed.
258
- this._currentNode = null;
259
-
260
- // This is a stack of the objects and arrays that we've
261
- // traversed through before reaching the currentNode.
262
- // It is different than the ancestors array.
263
- this._containers = new Stack();
264
-
265
- // This stack has the same number of elements as the _containers
266
- // stack. The last element of this._indexes[] is the index of
267
- // the current node in the object or array that is the last element
268
- // of this._containers[]. If the last element of this._containers[] is
269
- // an array, then the last element of this stack will be a number.
270
- // Otherwise if the last container is an object, then the last index
271
- // will be a string property name.
272
- this._indexes = new Stack();
273
-
274
- // This is a stack of the ancestor nodes of the current one.
275
- // It is different than the containers[] stack because it only
276
- // includes nodes, not arrays.
277
- this._ancestors = new Stack();
278
- }
279
-
280
- /**
281
- * Return the current node in the traversal. Any time the traversal
282
- * callback is called, this method will return the name value as the
283
- * first argument to the callback.
284
- */
285
- currentNode(): TreeNode {
286
- return this._currentNode || this.root;
287
- }
288
-
289
- /**
290
- * Return the parent of the current node, if there is one, or null.
291
- */
292
- parent(): ?TreeNode {
293
- return this._ancestors.top();
294
- }
295
-
296
- /**
297
- * Return an array of ancestor nodes. The first element of this array is
298
- * the same as this.parent() and the last element is the root node. If we
299
- * are currently at the root node, the the returned array will be empty.
300
- * This method makes a copy of the internal state, so modifications to the
301
- * returned array have no effect on the traversal.
302
- */
303
- ancestors(): $ReadOnlyArray<TreeNode> {
304
- return this._ancestors.values();
305
- }
306
-
307
- /**
308
- * Return the next sibling of this node, if it has one, or null otherwise.
309
- */
310
- nextSibling(): ?TreeNode {
311
- const siblings = this._containers.top();
312
-
313
- // If we're at the root of the tree or if the parent is an
314
- // object instead of an array, then there are no siblings.
315
- if (!siblings || !Array.isArray(siblings)) {
316
- return null;
317
- }
318
-
319
- // The top index is a number because the top container is an array
320
- const index = ((this._indexes.top(): any): number);
321
- if (siblings.length > index + 1) {
322
- return siblings[index + 1];
323
- }
324
- return null; // There is no next sibling
325
- }
326
-
327
- /**
328
- * Return the previous sibling of this node, if it has one, or null
329
- * otherwise.
330
- */
331
- previousSibling(): ?TreeNode {
332
- const siblings = this._containers.top();
333
-
334
- // If we're at the root of the tree or if the parent is an
335
- // object instead of an array, then there are no siblings.
336
- if (!siblings || !Array.isArray(siblings)) {
337
- return null;
338
- }
339
-
340
- // The top index is a number because the top container is an array
341
- const index = ((this._indexes.top(): any): number);
342
- if (index > 0) {
343
- return siblings[index - 1];
344
- }
345
- return null; // There is no previous sibling
346
- }
347
-
348
- /**
349
- * Remove the next sibling node (if there is one) from the tree. Returns
350
- * the removed sibling or null. This method makes it easy to traverse a
351
- * tree and concatenate adjacent text nodes into a single node.
352
- */
353
- removeNextSibling(): ?TreeNode {
354
- const siblings = this._containers.top();
355
- if (siblings && Array.isArray(siblings)) {
356
- // top index is a number because top container is an array
357
- const index = ((this._indexes.top(): any): number);
358
- if (siblings.length > index + 1) {
359
- return siblings.splice(index + 1, 1)[0];
360
- }
361
- }
362
- return null;
363
- }
364
-
365
- /**
366
- * Replace the current node in the tree with the specified nodes. If no
367
- * nodes are passed, this is a node deletion. If one node (or array) is
368
- * passed, this is a 1-for-1 replacement. If more than one node is passed
369
- * then this is a combination of deletion and insertion. The new node or
370
- * nodes will not be traversed, so this method can safely be used to
371
- * reparent the current node node beneath a new parent.
372
- *
373
- * This method throws an error if you attempt to replace the root node of
374
- * the tree.
375
- */
376
- replace(...replacements: $ReadOnlyArray<TreeNode>): void {
377
- const parent = this._containers.top();
378
- if (!parent) {
379
- throw new PerseusError(
380
- "Can't replace the root of the tree",
381
- Errors.Internal,
382
- );
383
- }
384
-
385
- // The top of the container stack is either an array or an object
386
- // and the top of the indexes stack is a corresponding array index
387
- // or object property. This is hard for Flow, so we have to do some
388
- // unsafe casting and be careful when we use which cast version
389
- if (Array.isArray(parent)) {
390
- const index = ((this._indexes.top(): any): number);
391
- // For an array parent we just splice the new nodes in
392
- parent.splice(index, 1, ...replacements);
393
- // Adjust the index to account for the changed array length.
394
- // We don't want to traverse any of the newly inserted nodes.
395
- this._indexes.pop();
396
- this._indexes.push(index + replacements.length - 1);
397
- } else {
398
- const property = ((this._indexes.top(): any): string);
399
- // For an object parent we care how many new nodes there are
400
- if (replacements.length === 0) {
401
- // Deletion
402
- delete parent[property];
403
- } else if (replacements.length === 1) {
404
- // Replacement
405
- parent[property] = replacements[0];
406
- } else {
407
- // Replace one node with an array of nodes
408
- parent[property] = replacements;
409
- }
410
- }
411
- }
412
-
413
- /**
414
- * Returns true if the current node has a previous sibling and false
415
- * otherwise. If this method returns false, then previousSibling() will
416
- * return null, and goToPreviousSibling() will throw an error.
417
- */
418
- hasPreviousSibling(): boolean {
419
- return (
420
- Array.isArray(this._containers.top()) &&
421
- ((this._indexes.top(): any): number) > 0
422
- );
423
- }
424
-
425
- /**
426
- * Modify this traversal state object to have the state it would have had
427
- * when visiting the previous sibling. Note that you may want to use
428
- * clone() to make a copy before modifying the state object like this.
429
- * This mutator method is not typically used during ordinary tree
430
- * traversals, but is used by the Selector class for matching multi-node
431
- * selectors.
432
- */
433
- goToPreviousSibling(): void {
434
- if (!this.hasPreviousSibling()) {
435
- throw new PerseusError(
436
- "goToPreviousSibling(): node has no previous sibling",
437
- Errors.Internal,
438
- );
439
- }
440
-
441
- this._currentNode = this.previousSibling();
442
- // Since we know that we have a previous sibling, we know that
443
- // the value on top of the stack is a number, but we have to do
444
- // this unsafe cast because Flow doesn't know that.
445
- const index = ((this._indexes.pop(): any): number);
446
- this._indexes.push(index - 1);
447
- }
448
-
449
- /**
450
- * Returns true if the current node has an ancestor and false otherwise.
451
- * If this method returns false, then the parent() method will return
452
- * null and goToParent() will throw an error
453
- */
454
- hasParent(): boolean {
455
- return this._ancestors.size() !== 0;
456
- }
457
-
458
- /**
459
- * Modify this object to look like it will look when we (later) visit the
460
- * parent node of this node. You should not modify the instance passed to
461
- * the tree traversal callback. Instead, make a copy with the clone()
462
- * method and modify that. This mutator method is not typically used
463
- * during ordinary tree traversals, but is used by the Selector class for
464
- * matching multi-node selectors that involve parent and ancestor
465
- * selectors.
466
- */
467
- goToParent(): void {
468
- if (!this.hasParent()) {
469
- throw new PerseusError(
470
- "goToParent(): node has no ancestor",
471
- Errors.NotAllowed,
472
- );
473
- }
474
-
475
- this._currentNode = this._ancestors.pop();
476
-
477
- // We need to pop the containers and indexes stacks at least once
478
- // and more as needed until we restore the invariant that
479
- // this._containers.top()[this.indexes.top()] === this._currentNode
480
- //
481
- while (
482
- this._containers.size() &&
483
- // This is safe, but easier to just disable flow than do casts
484
- // $FlowFixMe[incompatible-use]
485
- this._containers.top()[this._indexes.top()] !== this._currentNode
486
- ) {
487
- this._containers.pop();
488
- this._indexes.pop();
489
- }
490
- }
491
-
492
- /**
493
- * Return a new TraversalState object that is a copy of this one.
494
- * This method is useful in conjunction with the mutating methods
495
- * goToParent() and goToPreviousSibling().
496
- */
497
- clone(): TraversalState {
498
- const clone = new TraversalState(this.root);
499
- clone._currentNode = this._currentNode;
500
- clone._containers = this._containers.clone();
501
- clone._indexes = this._indexes.clone();
502
- clone._ancestors = this._ancestors.clone();
503
- return clone;
504
- }
505
-
506
- /**
507
- * Returns true if this TraversalState object is equal to that
508
- * TraversalState object, or false otherwise. This method exists
509
- * primarily for use by our unit tests.
510
- */
511
- equals(that: TraversalState): boolean {
512
- return (
513
- this.root === that.root &&
514
- this._currentNode === that._currentNode &&
515
- this._containers.equals(that._containers) &&
516
- this._indexes.equals(that._indexes) &&
517
- this._ancestors.equals(that._ancestors)
518
- );
519
- }
520
- }
521
-
522
- /**
523
- * This class is an internal utility that just treats an array as a stack
524
- * and gives us a top() method so we don't have to write expressions like
525
- * `ancestors[ancestors.length-1]`. The values() method automatically
526
- * copies the internal array so we don't have to worry about client code
527
- * modifying our internal stacks. The use of this Stack abstraction makes
528
- * the TraversalState class simpler in a number of places.
529
- */
530
- class Stack<T> {
531
- // eslint-disable-next-line flowtype/no-mutable-array
532
- stack: Array<T>;
533
-
534
- constructor(array: ?$ReadOnlyArray<T>) {
535
- this.stack = array ? array.slice(0) : [];
536
- }
537
-
538
- /** Push a value onto the stack. */
539
- push(v: T): void {
540
- this.stack.push(v);
541
- }
542
-
543
- /** Pop a value off of the stack. */
544
- pop(): T {
545
- return this.stack.pop();
546
- }
547
-
548
- /** Return the top value of the stack without popping it. */
549
- top(): T {
550
- return this.stack[this.stack.length - 1];
551
- }
552
-
553
- /** Return a copy of the stack as an array */
554
- values(): $ReadOnlyArray<T> {
555
- return this.stack.slice(0);
556
- }
557
-
558
- /** Return the number of elements in the stack */
559
- size(): number {
560
- return this.stack.length;
561
- }
562
-
563
- /** Return a string representation of the stack */
564
- toString(): string {
565
- return this.stack.toString();
566
- }
567
-
568
- /** Return a shallow copy of the stack */
569
- clone(): Stack<T> {
570
- return new Stack(this.stack);
571
- }
572
-
573
- /**
574
- * Compare this stack to another and return true if the contents of
575
- * the two arrays are the same.
576
- */
577
- equals(that: Stack<T>): boolean {
578
- if (!that || !that.stack || that.stack.length !== this.stack.length) {
579
- return false;
580
- }
581
- for (let i = 0; i < this.stack.length; i++) {
582
- if (this.stack[i] !== that.stack[i]) {
583
- return false;
584
- }
585
- }
586
- return true;
587
- }
588
- }