@khanacademy/perseus-linter 0.2.5 → 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 (133) hide show
  1. package/.eslintrc.js +1 -0
  2. package/CHANGELOG.md +11 -0
  3. package/dist/es/index.js +277 -407
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.d.ts +7 -2
  6. package/dist/index.js +281 -398
  7. package/dist/index.js.flow +18 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/proptypes.d.ts +9 -0
  10. package/dist/proptypes.js.flow +17 -0
  11. package/dist/rule.d.ts +170 -0
  12. package/dist/rule.js.flow +86 -0
  13. package/dist/rules/absolute-url.d.ts +3 -0
  14. package/dist/rules/absolute-url.js.flow +9 -0
  15. package/dist/rules/all-rules.d.ts +2 -0
  16. package/dist/rules/all-rules.js.flow +9 -0
  17. package/dist/rules/blockquoted-math.d.ts +3 -0
  18. package/dist/rules/blockquoted-math.js.flow +9 -0
  19. package/dist/rules/blockquoted-widget.d.ts +3 -0
  20. package/dist/rules/blockquoted-widget.js.flow +9 -0
  21. package/dist/rules/double-spacing-after-terminal.d.ts +3 -0
  22. package/dist/rules/double-spacing-after-terminal.js.flow +9 -0
  23. package/dist/rules/extra-content-spacing.d.ts +3 -0
  24. package/dist/rules/extra-content-spacing.js.flow +9 -0
  25. package/dist/rules/heading-level-1.d.ts +3 -0
  26. package/dist/rules/heading-level-1.js.flow +9 -0
  27. package/dist/rules/heading-level-skip.d.ts +3 -0
  28. package/dist/rules/heading-level-skip.js.flow +9 -0
  29. package/dist/rules/heading-sentence-case.d.ts +3 -0
  30. package/dist/rules/heading-sentence-case.js.flow +9 -0
  31. package/dist/rules/heading-title-case.d.ts +3 -0
  32. package/dist/rules/heading-title-case.js.flow +9 -0
  33. package/dist/rules/image-alt-text.d.ts +3 -0
  34. package/dist/rules/image-alt-text.js.flow +9 -0
  35. package/dist/rules/image-in-table.d.ts +3 -0
  36. package/dist/rules/image-in-table.js.flow +9 -0
  37. package/dist/rules/image-spaces-around-urls.d.ts +3 -0
  38. package/dist/rules/image-spaces-around-urls.js.flow +9 -0
  39. package/dist/rules/image-widget.d.ts +3 -0
  40. package/dist/rules/image-widget.js.flow +9 -0
  41. package/dist/rules/link-click-here.d.ts +3 -0
  42. package/dist/rules/link-click-here.js.flow +9 -0
  43. package/dist/rules/lint-utils.d.ts +2 -0
  44. package/dist/rules/lint-utils.js.flow +8 -0
  45. package/dist/rules/long-paragraph.d.ts +3 -0
  46. package/dist/rules/long-paragraph.js.flow +9 -0
  47. package/dist/rules/math-adjacent.d.ts +3 -0
  48. package/dist/rules/math-adjacent.js.flow +9 -0
  49. package/dist/rules/math-align-extra-break.d.ts +3 -0
  50. package/dist/rules/math-align-extra-break.js.flow +9 -0
  51. package/dist/rules/math-align-linebreaks.d.ts +3 -0
  52. package/dist/rules/math-align-linebreaks.js.flow +9 -0
  53. package/dist/rules/math-empty.d.ts +3 -0
  54. package/dist/rules/math-empty.js.flow +9 -0
  55. package/dist/rules/math-font-size.d.ts +3 -0
  56. package/dist/rules/math-font-size.js.flow +9 -0
  57. package/dist/rules/math-frac.d.ts +3 -0
  58. package/dist/rules/math-frac.js.flow +9 -0
  59. package/dist/rules/math-nested.d.ts +3 -0
  60. package/dist/rules/math-nested.js.flow +9 -0
  61. package/dist/rules/math-starts-with-space.d.ts +3 -0
  62. package/dist/rules/math-starts-with-space.js.flow +9 -0
  63. package/dist/rules/math-text-empty.d.ts +3 -0
  64. package/dist/rules/math-text-empty.js.flow +9 -0
  65. package/dist/rules/math-without-dollars.d.ts +3 -0
  66. package/dist/rules/math-without-dollars.js.flow +9 -0
  67. package/dist/rules/nested-lists.d.ts +3 -0
  68. package/dist/rules/nested-lists.js.flow +9 -0
  69. package/dist/rules/profanity.d.ts +3 -0
  70. package/dist/rules/profanity.js.flow +9 -0
  71. package/dist/rules/table-missing-cells.d.ts +3 -0
  72. package/dist/rules/table-missing-cells.js.flow +9 -0
  73. package/dist/rules/unbalanced-code-delimiters.d.ts +3 -0
  74. package/dist/rules/unbalanced-code-delimiters.js.flow +9 -0
  75. package/dist/rules/unescaped-dollar.d.ts +3 -0
  76. package/dist/rules/unescaped-dollar.js.flow +9 -0
  77. package/dist/rules/widget-in-table.d.ts +3 -0
  78. package/dist/rules/widget-in-table.js.flow +9 -0
  79. package/dist/selector.d.ts +108 -0
  80. package/dist/selector.js.flow +31 -0
  81. package/dist/tree-transformer.d.ts +205 -0
  82. package/dist/tree-transformer.js.flow +253 -0
  83. package/dist/types.d.ts +6 -0
  84. package/dist/types.js.flow +12 -0
  85. package/package.json +4 -4
  86. package/src/__tests__/{matcher_test.js → matcher.test.ts} +60 -60
  87. package/src/__tests__/{rule_test.js → rule.test.ts} +13 -5
  88. package/src/__tests__/{rules_test.js → rules.test.ts} +99 -39
  89. package/src/__tests__/{selector-parser_test.js → selector-parser.test.ts} +1 -2
  90. package/src/__tests__/{tree-transformer_test.js → tree-transformer.test.ts} +39 -41
  91. package/src/{index.js → index.ts} +21 -23
  92. package/src/{proptypes.js → proptypes.ts} +4 -14
  93. package/src/{rule.js → rule.ts} +45 -38
  94. package/src/rules/{absolute-url.js → absolute-url.ts} +4 -5
  95. package/src/rules/all-rules.ts +71 -0
  96. package/src/rules/{blockquoted-math.js → blockquoted-math.ts} +3 -4
  97. package/src/rules/{blockquoted-widget.js → blockquoted-widget.ts} +3 -4
  98. package/src/rules/{double-spacing-after-terminal.js → double-spacing-after-terminal.ts} +3 -4
  99. package/src/rules/{extra-content-spacing.js → extra-content-spacing.ts} +3 -4
  100. package/src/rules/{heading-level-1.js → heading-level-1.ts} +3 -4
  101. package/src/rules/{heading-level-skip.js → heading-level-skip.ts} +3 -4
  102. package/src/rules/{heading-sentence-case.js → heading-sentence-case.ts} +3 -4
  103. package/src/rules/{heading-title-case.js → heading-title-case.ts} +11 -6
  104. package/src/rules/{image-alt-text.js → image-alt-text.ts} +3 -4
  105. package/src/rules/{image-in-table.js → image-in-table.ts} +3 -4
  106. package/src/rules/{image-spaces-around-urls.js → image-spaces-around-urls.ts} +3 -4
  107. package/src/rules/{image-widget.js → image-widget.ts} +3 -4
  108. package/src/rules/{link-click-here.js → link-click-here.ts} +3 -4
  109. package/src/rules/{lint-utils.js → lint-utils.ts} +1 -2
  110. package/src/rules/{long-paragraph.js → long-paragraph.ts} +3 -4
  111. package/src/rules/{math-adjacent.js → math-adjacent.ts} +3 -4
  112. package/src/rules/{math-align-extra-break.js → math-align-extra-break.ts} +3 -4
  113. package/src/rules/{math-align-linebreaks.js → math-align-linebreaks.ts} +3 -4
  114. package/src/rules/{math-empty.js → math-empty.ts} +3 -4
  115. package/src/rules/{math-font-size.js → math-font-size.ts} +3 -4
  116. package/src/rules/{math-frac.js → math-frac.ts} +3 -4
  117. package/src/rules/{math-nested.js → math-nested.ts} +3 -4
  118. package/src/rules/{math-starts-with-space.js → math-starts-with-space.ts} +3 -4
  119. package/src/rules/{math-text-empty.js → math-text-empty.ts} +3 -4
  120. package/src/rules/{math-without-dollars.js → math-without-dollars.ts} +3 -4
  121. package/src/rules/{nested-lists.js → nested-lists.ts} +3 -4
  122. package/src/rules/{profanity.js → profanity.ts} +3 -4
  123. package/src/rules/{table-missing-cells.js → table-missing-cells.ts} +3 -4
  124. package/src/rules/{unbalanced-code-delimiters.js → unbalanced-code-delimiters.ts} +3 -4
  125. package/src/rules/{unescaped-dollar.js → unescaped-dollar.ts} +3 -4
  126. package/src/rules/{widget-in-table.js → widget-in-table.ts} +3 -4
  127. package/src/{selector.js → selector.ts} +12 -13
  128. package/src/{tree-transformer.js → tree-transformer.ts} +24 -24
  129. package/src/types.ts +7 -0
  130. package/tsconfig.json +12 -0
  131. package/tsconfig.tsbuildinfo +1 -0
  132. package/src/rules/all-rules.js +0 -72
  133. package/src/types.js +0 -10
package/dist/es/index.js CHANGED
@@ -3,17 +3,14 @@ import PropTypes from 'prop-types';
3
3
 
4
4
  function ownKeys(object, enumerableOnly) {
5
5
  var keys = Object.keys(object);
6
-
7
6
  if (Object.getOwnPropertySymbols) {
8
7
  var symbols = Object.getOwnPropertySymbols(object);
9
8
  enumerableOnly && (symbols = symbols.filter(function (sym) {
10
9
  return Object.getOwnPropertyDescriptor(object, sym).enumerable;
11
10
  })), keys.push.apply(keys, symbols);
12
11
  }
13
-
14
12
  return keys;
15
13
  }
16
-
17
14
  function _objectSpread2(target) {
18
15
  for (var i = 1; i < arguments.length; i++) {
19
16
  var source = null != arguments[i] ? arguments[i] : {};
@@ -23,11 +20,10 @@ function _objectSpread2(target) {
23
20
  Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
24
21
  });
25
22
  }
26
-
27
23
  return target;
28
24
  }
29
-
30
25
  function _defineProperty(obj, key, value) {
26
+ key = _toPropertyKey(key);
31
27
  if (key in obj) {
32
28
  Object.defineProperty(obj, key, {
33
29
  value: value,
@@ -38,9 +34,22 @@ function _defineProperty(obj, key, value) {
38
34
  } else {
39
35
  obj[key] = value;
40
36
  }
41
-
42
37
  return obj;
43
38
  }
39
+ function _toPrimitive(input, hint) {
40
+ if (typeof input !== "object" || input === null) return input;
41
+ var prim = input[Symbol.toPrimitive];
42
+ if (prim !== undefined) {
43
+ var res = prim.call(input, hint || "default");
44
+ if (typeof res !== "object") return res;
45
+ throw new TypeError("@@toPrimitive must return a primitive value.");
46
+ }
47
+ return (hint === "string" ? String : Number)(input);
48
+ }
49
+ function _toPropertyKey(arg) {
50
+ var key = _toPrimitive(arg, "string");
51
+ return typeof key === "symbol" ? key : String(key);
52
+ }
44
53
 
45
54
  /**
46
55
  * This is the base class for all Selector types. The key method that all
@@ -53,27 +62,25 @@ class Selector {
53
62
  static parse(selectorText) {
54
63
  return new Parser(selectorText).parse();
55
64
  }
65
+
56
66
  /**
57
67
  * Return an array of the nodes that matched or null if no match.
58
68
  * This is the base class so we just throw an exception. All Selector
59
69
  * subclasses must provide an implementation of this method.
60
70
  */
61
-
62
-
63
71
  match(state) {
64
72
  throw new PerseusError("Selector subclasses must implement match()", Errors.NotAllowed);
65
73
  }
74
+
66
75
  /**
67
76
  * Selector subclasses all define a toString() method primarily
68
77
  * because it makes it easy to write parser tests.
69
78
  */
70
-
71
-
72
79
  toString() {
73
80
  return "Unknown selector class";
74
81
  }
75
-
76
82
  }
83
+
77
84
  /**
78
85
  * This class implements a parser for the selector grammar. Pass the source
79
86
  * text to the Parser() constructor, and then call the parse() method to
@@ -83,98 +90,96 @@ class Selector {
83
90
  * This class is not exported, and you don't need to use it directly.
84
91
  * Instead call the static Selector.parse() method.
85
92
  */
86
-
87
93
  class Parser {
88
94
  // We do lexing with a simple regular expression
89
95
  // The array of tokens
90
96
  // Which token in the array we're looking at now
97
+
91
98
  constructor(s) {
92
99
  _defineProperty(this, "tokens", void 0);
93
-
94
100
  _defineProperty(this, "tokenIndex", void 0);
95
-
96
101
  // Normalize whitespace:
97
102
  // - remove leading and trailing whitespace
98
103
  // - replace runs of whitespace with single space characters
99
- s = s.trim().replace(/\s+/g, " "); // Convert the string to an array of tokens. Note that the TOKENS
104
+ s = s.trim().replace(/\s+/g, " ");
105
+ // Convert the string to an array of tokens. Note that the TOKENS
100
106
  // pattern ignores spaces that do not appear before identifiers
101
107
  // or the * wildcard.
102
-
103
108
  this.tokens = s.match(Parser.TOKENS) || [];
104
109
  this.tokenIndex = 0;
105
- } // Return the next token or the empty string if there are no more
106
-
110
+ }
107
111
 
112
+ // Return the next token or the empty string if there are no more
108
113
  nextToken() {
109
114
  return this.tokens[this.tokenIndex] || "";
110
- } // Increment the token index to "consume" the token we were looking at
111
- // and move on to the next one.
112
-
115
+ }
113
116
 
117
+ // Increment the token index to "consume" the token we were looking at
118
+ // and move on to the next one.
114
119
  consume() {
115
120
  this.tokenIndex++;
116
- } // Return true if the current token is an identifier or false otherwise
117
-
121
+ }
118
122
 
123
+ // Return true if the current token is an identifier or false otherwise
119
124
  isIdentifier() {
120
125
  // The Parser.TOKENS regexp ensures that we only have to check
121
126
  // the first character of a token to know what kind of token it is.
122
127
  var c = this.tokens[this.tokenIndex][0];
123
128
  return c >= "a" && c <= "z" || c >= "A" && c <= "Z";
124
- } // Consume space tokens until the next token is not a space.
125
-
129
+ }
126
130
 
131
+ // Consume space tokens until the next token is not a space.
127
132
  skipSpace() {
128
133
  while (this.nextToken() === " ") {
129
134
  this.consume();
130
135
  }
131
- } // Parse a comma-separated sequence of tree selectors. This is the
136
+ }
137
+
138
+ // Parse a comma-separated sequence of tree selectors. This is the
132
139
  // entry point for the Parser class and the only method that clients
133
140
  // ever need to call.
134
-
135
-
136
141
  parse() {
137
142
  // We expect at least one tree selector
138
- var ts = this.parseTreeSelector(); // Now see what's next
143
+ var ts = this.parseTreeSelector();
139
144
 
140
- var token = this.nextToken(); // If there is no next token then we're done parsing and can return
141
- // the tree selector object we got above
145
+ // Now see what's next
146
+ var token = this.nextToken();
142
147
 
148
+ // If there is no next token then we're done parsing and can return
149
+ // the tree selector object we got above
143
150
  if (!token) {
144
151
  return ts;
145
- } // Otherwise, there is more go come and we're going to need a
146
- // list of tree selectors
147
-
152
+ }
148
153
 
154
+ // Otherwise, there is more go come and we're going to need a
155
+ // list of tree selectors
149
156
  var treeSelectors = [ts];
150
-
151
157
  while (token) {
152
158
  // The only character we allow after a tree selector is a comma
153
159
  if (token === ",") {
154
160
  this.consume();
155
161
  } else {
156
162
  throw new ParseError("Expected comma");
157
- } // And if we saw a comma, then it must be followed by another
158
- // tree selector
159
-
163
+ }
160
164
 
165
+ // And if we saw a comma, then it must be followed by another
166
+ // tree selector
161
167
  treeSelectors.push(this.parseTreeSelector());
162
168
  token = this.nextToken();
163
- } // If we parsed more than one tree selector, return them in a
164
- // SelectorList object.
165
-
169
+ }
166
170
 
171
+ // If we parsed more than one tree selector, return them in a
172
+ // SelectorList object.
167
173
  return new SelectorList(treeSelectors);
168
- } // Parse a sequence of node selectors linked together with
169
- // hierarchy combinators: space, >, + and ~.
170
-
174
+ }
171
175
 
176
+ // Parse a sequence of node selectors linked together with
177
+ // hierarchy combinators: space, >, + and ~.
172
178
  parseTreeSelector() {
173
179
  this.skipSpace(); // Ignore space after a comma, for example
174
- // A tree selector must begin with a node selector
175
180
 
181
+ // A tree selector must begin with a node selector
176
182
  var ns = this.parseNodeSelector();
177
-
178
183
  for (;;) {
179
184
  // Now check the next token. If there is none, or if it is a
180
185
  // comma, then we're done with the treeSelector. Otherwise
@@ -184,7 +189,6 @@ class Parser {
184
189
  // combine the current node selector with the new node selector
185
190
  // using a Selector subclass that depends on the combinator.
186
191
  var token = this.nextToken();
187
-
188
192
  if (!token || token === ",") {
189
193
  break;
190
194
  } else if (token === " ") {
@@ -203,303 +207,240 @@ class Parser {
203
207
  throw new ParseError("Unexpected token: " + token);
204
208
  }
205
209
  }
206
-
207
210
  return ns;
208
- } // Parse a single node selector.
211
+ }
212
+
213
+ // Parse a single node selector.
209
214
  // For now, this is just a node type or a wildcard.
210
215
  //
211
216
  // TODO(davidflanagan): we may need to extend this with attribute
212
217
  // selectors like 'heading[level=3]', or with pseudo-classes like
213
218
  // paragraph:first-child
214
-
215
-
216
219
  parseNodeSelector() {
217
220
  // First, skip any whitespace
218
221
  this.skipSpace();
219
222
  var t = this.nextToken();
220
-
221
223
  if (t === "*") {
222
224
  this.consume();
223
225
  return new AnyNode();
224
226
  }
225
-
226
227
  if (this.isIdentifier()) {
227
228
  this.consume();
228
229
  return new TypeSelector(t);
229
230
  }
230
-
231
231
  throw new ParseError("Expected node type");
232
232
  }
233
+ }
233
234
 
234
- } // We break the input string into tokens with this regexp. Token types
235
+ // We break the input string into tokens with this regexp. Token types
235
236
  // are identifiers, integers, punctuation and spaces. Note that spaces
236
237
  // tokens are only returned when they appear before an identifier or
237
238
  // wildcard token and are otherwise omitted.
238
-
239
-
240
239
  _defineProperty(Parser, "TOKENS", void 0);
241
-
242
240
  Parser.TOKENS = /([a-zA-Z][\w-]*)|(\d+)|[^\s]|(\s(?=[a-zA-Z\*]))/g;
241
+
243
242
  /**
244
243
  * This is a trivial Error subclass that the Parser uses to signal parse errors
245
244
  */
246
-
247
245
  class ParseError extends Error {
248
246
  constructor(message) {
249
247
  super(message);
250
248
  }
251
-
252
249
  }
250
+
253
251
  /**
254
252
  * This Selector subclass is a list of selectors. It matches a node if any of
255
253
  * the selectors on the list matches the node. It considers the selectors in
256
254
  * order, and returns the array of nodes returned by whichever one matches
257
255
  * first.
258
256
  */
259
-
260
-
261
257
  class SelectorList extends Selector {
262
258
  constructor(selectors) {
263
259
  super();
264
-
265
260
  _defineProperty(this, "selectors", void 0);
266
-
267
261
  this.selectors = selectors;
268
262
  }
269
-
270
263
  match(state) {
271
264
  for (var i = 0; i < this.selectors.length; i++) {
272
265
  var s = this.selectors[i];
273
266
  var result = s.match(state);
274
-
275
267
  if (result) {
276
268
  return result;
277
269
  }
278
270
  }
279
-
280
271
  return null;
281
272
  }
282
-
283
273
  toString() {
284
274
  var result = "";
285
-
286
275
  for (var i = 0; i < this.selectors.length; i++) {
287
276
  result += i > 0 ? ", " : "";
288
277
  result += this.selectors[i].toString();
289
278
  }
290
-
291
279
  return result;
292
280
  }
293
-
294
281
  }
282
+
295
283
  /**
296
284
  * This trivial Selector subclass implements the '*' wildcard and
297
285
  * matches any node.
298
286
  */
299
-
300
-
301
287
  class AnyNode extends Selector {
302
288
  match(state) {
303
289
  return [state.currentNode()];
304
290
  }
305
-
306
291
  toString() {
307
292
  return "*";
308
293
  }
309
-
310
294
  }
295
+
311
296
  /**
312
297
  * This selector subclass implements the <IDENTIFIER> part of the grammar.
313
298
  * it matches any node whose `type` property is a specified string
314
299
  */
315
-
316
-
317
300
  class TypeSelector extends Selector {
318
301
  constructor(type) {
319
302
  super();
320
-
321
303
  _defineProperty(this, "type", void 0);
322
-
323
304
  this.type = type;
324
305
  }
325
-
326
306
  match(state) {
327
307
  var node = state.currentNode();
328
-
329
308
  if (node.type === this.type) {
330
309
  return [node];
331
310
  }
332
-
333
311
  return null;
334
312
  }
335
-
336
313
  toString() {
337
314
  return this.type;
338
315
  }
339
-
340
316
  }
317
+
341
318
  /**
342
319
  * This selector subclass is the superclass of the classes that implement
343
320
  * matching for the four combinators. It defines left and right properties for
344
321
  * the two selectors that are to be combined, but does not define a match
345
322
  * method.
346
323
  */
347
-
348
-
349
324
  class SelectorCombinator extends Selector {
350
325
  constructor(left, right) {
351
326
  super();
352
-
353
327
  _defineProperty(this, "left", void 0);
354
-
355
328
  _defineProperty(this, "right", void 0);
356
-
357
329
  this.left = left;
358
330
  this.right = right;
359
331
  }
360
-
361
332
  }
333
+
362
334
  /**
363
335
  * This Selector subclass implements the space combinator. It matches if the
364
336
  * right selector matches the current node and the left selector matches some
365
337
  * ancestor of the current node.
366
338
  */
367
-
368
-
369
339
  class AncestorCombinator extends SelectorCombinator {
370
340
  constructor(left, right) {
371
341
  super(left, right);
372
342
  }
373
-
374
343
  match(state) {
375
344
  var rightResult = this.right.match(state);
376
-
377
345
  if (rightResult) {
378
346
  state = state.clone();
379
-
380
347
  while (state.hasParent()) {
381
348
  state.goToParent();
382
349
  var leftResult = this.left.match(state);
383
-
384
350
  if (leftResult) {
385
351
  return leftResult.concat(rightResult);
386
352
  }
387
353
  }
388
354
  }
389
-
390
355
  return null;
391
356
  }
392
-
393
357
  toString() {
394
358
  return this.left.toString() + " " + this.right.toString();
395
359
  }
396
-
397
360
  }
361
+
398
362
  /**
399
363
  * This Selector subclass implements the > combinator. It matches if the
400
364
  * right selector matches the current node and the left selector matches
401
365
  * the parent of the current node.
402
366
  */
403
-
404
-
405
367
  class ParentCombinator extends SelectorCombinator {
406
368
  constructor(left, right) {
407
369
  super(left, right);
408
370
  }
409
-
410
371
  match(state) {
411
372
  var rightResult = this.right.match(state);
412
-
413
373
  if (rightResult) {
414
374
  if (state.hasParent()) {
415
375
  state = state.clone();
416
376
  state.goToParent();
417
377
  var leftResult = this.left.match(state);
418
-
419
378
  if (leftResult) {
420
379
  return leftResult.concat(rightResult);
421
380
  }
422
381
  }
423
382
  }
424
-
425
383
  return null;
426
384
  }
427
-
428
385
  toString() {
429
386
  return this.left.toString() + " > " + this.right.toString();
430
387
  }
431
-
432
388
  }
389
+
433
390
  /**
434
391
  * This Selector subclass implements the + combinator. It matches if the
435
392
  * right selector matches the current node and the left selector matches
436
393
  * the immediate previous sibling of the current node.
437
394
  */
438
-
439
-
440
395
  class PreviousCombinator extends SelectorCombinator {
441
396
  constructor(left, right) {
442
397
  super(left, right);
443
398
  }
444
-
445
399
  match(state) {
446
400
  var rightResult = this.right.match(state);
447
-
448
401
  if (rightResult) {
449
402
  if (state.hasPreviousSibling()) {
450
403
  state = state.clone();
451
404
  state.goToPreviousSibling();
452
405
  var leftResult = this.left.match(state);
453
-
454
406
  if (leftResult) {
455
407
  return leftResult.concat(rightResult);
456
408
  }
457
409
  }
458
410
  }
459
-
460
411
  return null;
461
412
  }
462
-
463
413
  toString() {
464
414
  return this.left.toString() + " + " + this.right.toString();
465
415
  }
466
-
467
416
  }
417
+
468
418
  /**
469
419
  * This Selector subclass implements the ~ combinator. It matches if the
470
420
  * right selector matches the current node and the left selector matches
471
421
  * any previous sibling of the current node.
472
422
  */
473
-
474
-
475
423
  class SiblingCombinator extends SelectorCombinator {
476
424
  constructor(left, right) {
477
425
  super(left, right);
478
426
  }
479
-
480
427
  match(state) {
481
428
  var rightResult = this.right.match(state);
482
-
483
429
  if (rightResult) {
484
430
  state = state.clone();
485
-
486
431
  while (state.hasPreviousSibling()) {
487
432
  state.goToPreviousSibling();
488
433
  var leftResult = this.left.match(state);
489
-
490
434
  if (leftResult) {
491
435
  return leftResult.concat(rightResult);
492
436
  }
493
437
  }
494
438
  }
495
-
496
439
  return null;
497
440
  }
498
-
499
441
  toString() {
500
442
  return this.left.toString() + " ~ " + this.right.toString();
501
443
  }
502
-
503
444
  }
504
445
 
505
446
  /**
@@ -514,25 +455,18 @@ class Rule {
514
455
  // The lint-testing function or a default
515
456
  // Checks to see if we should apply a rule or not
516
457
  // The error message for use with the default function
458
+
517
459
  // The comment at the top of this file has detailed docs for
518
460
  // this constructor and its arguments
519
461
  constructor(name, severity, selector, pattern, lint, applies) {
520
462
  var _this = this;
521
-
522
463
  _defineProperty(this, "name", void 0);
523
-
524
464
  _defineProperty(this, "severity", void 0);
525
-
526
465
  _defineProperty(this, "selector", void 0);
527
-
528
466
  _defineProperty(this, "pattern", void 0);
529
-
530
467
  _defineProperty(this, "lint", void 0);
531
-
532
468
  _defineProperty(this, "applies", void 0);
533
-
534
469
  _defineProperty(this, "message", void 0);
535
-
536
470
  if (!selector && !pattern) {
537
471
  throw new PerseusError("Lint rules must have a selector or pattern", Errors.InvalidInput, {
538
472
  metadata: {
@@ -540,13 +474,13 @@ class Rule {
540
474
  }
541
475
  });
542
476
  }
543
-
544
477
  this.name = name || "unnamed rule";
545
478
  this.severity = severity || Rule.Severity.BULK_WARNING;
546
479
  this.selector = selector || Rule.DEFAULT_SELECTOR;
547
- this.pattern = pattern || null; // If we're called with an error message instead of a function then
548
- // use a default function that will return the message.
480
+ this.pattern = pattern || null;
549
481
 
482
+ // If we're called with an error message instead of a function then
483
+ // use a default function that will return the message.
550
484
  if (typeof lint === "function") {
551
485
  this.lint = lint;
552
486
  this.message = null;
@@ -554,55 +488,51 @@ class Rule {
554
488
  this.lint = function () {
555
489
  return _this._defaultLintFunction(...arguments);
556
490
  };
557
-
558
491
  this.message = lint;
559
492
  }
560
-
561
493
  this.applies = applies || function () {
562
494
  return true;
563
495
  };
564
- } // A factory method for use with rules described in JSON files
565
- // See the documentation at the start of this file for details.
566
-
496
+ }
567
497
 
498
+ // A factory method for use with rules described in JSON files
499
+ // See the documentation at the start of this file for details.
568
500
  static makeRule(options) {
569
501
  return new Rule(options.name, options.severity, options.selector ? Selector.parse(options.selector) : null, Rule.makePattern(options.pattern), options.lint || options.message, options.applies);
570
- } // Check the node n to see if it violates this lint rule. A return value
502
+ }
503
+
504
+ // Check the node n to see if it violates this lint rule. A return value
571
505
  // of false means there is no lint. A returned object indicates a lint
572
506
  // error. See the documentation at the top of this file for details.
573
-
574
-
575
507
  check(node, traversalState, content, context) {
576
508
  // First, see if we match the selector.
577
509
  // If no selector was passed to the constructor, we use a
578
510
  // default selector that matches text nodes.
579
- var selectorMatch = this.selector.match(traversalState); // If the selector did not match, then we're done
511
+ var selectorMatch = this.selector.match(traversalState);
580
512
 
513
+ // If the selector did not match, then we're done
581
514
  if (!selectorMatch) {
582
515
  return null;
583
- } // If the selector matched, then see if the pattern matches
584
-
516
+ }
585
517
 
518
+ // If the selector matched, then see if the pattern matches
586
519
  var patternMatch;
587
-
588
520
  if (this.pattern) {
589
521
  patternMatch = content.match(this.pattern);
590
522
  } else {
591
523
  // If there is no pattern, then just match all of the content.
592
524
  // Use a fake RegExp match object to represent this default match.
593
525
  patternMatch = Rule.FakePatternMatch(content, content, 0);
594
- } // If there was a pattern and it didn't match, then we're done
595
-
526
+ }
596
527
 
528
+ // If there was a pattern and it didn't match, then we're done
597
529
  if (!patternMatch) {
598
530
  return null;
599
531
  }
600
-
601
532
  try {
602
533
  // If we get here, then the selector and pattern have matched
603
534
  // so now we call the lint function to see if there is lint.
604
535
  var error = this.lint(traversalState, content, selectorMatch, patternMatch, context);
605
-
606
536
  if (!error) {
607
537
  return null; // No lint; we're done
608
538
  }
@@ -617,10 +547,9 @@ class Rule {
617
547
  start: 0,
618
548
  end: content.length
619
549
  };
620
- } // If the lint function returned an object, then we just
550
+ }
551
+ // If the lint function returned an object, then we just
621
552
  // add the rule name to the message, start and end.
622
-
623
-
624
553
  return {
625
554
  rule: this.name,
626
555
  severity: this.severity,
@@ -641,22 +570,24 @@ class Rule {
641
570
  end: content.length
642
571
  };
643
572
  }
644
- } // This internal method is the default lint function that we use when a
573
+ }
574
+
575
+ // This internal method is the default lint function that we use when a
645
576
  // rule is defined without a function. This is useful for rules where the
646
577
  // selector and/or pattern match are enough to indicate lint. This
647
578
  // function unconditionally returns the error message that was passed in
648
579
  // place of a function, but also adds start and end properties that
649
580
  // specify which particular portion of the node content matched the
650
581
  // pattern.
651
-
652
-
653
582
  _defaultLintFunction(state, content, selectorMatch, patternMatch, context) {
654
583
  return {
655
584
  message: this.message || "",
656
585
  start: patternMatch.index,
657
586
  end: patternMatch.index + patternMatch[0].length
658
587
  };
659
- } // The makeRule() factory function uses this static method to turn its
588
+ }
589
+
590
+ // The makeRule() factory function uses this static method to turn its
660
591
  // argument into a RegExp. If the argument is already a RegExp, we just
661
592
  // return it. Otherwise, we compile it into a RegExp and return that.
662
593
  // The reason this is necessary is that Rule.makeRule() is designed for
@@ -671,49 +602,41 @@ class Rule {
671
602
  // input "foo" ==> output /foo/
672
603
  // input "/foo/i" ==> output /foo/i
673
604
  //
674
-
675
-
676
605
  static makePattern(pattern) {
677
606
  if (!pattern) {
678
607
  return null;
679
608
  }
680
-
681
609
  if (pattern instanceof RegExp) {
682
610
  return pattern;
683
611
  }
684
-
685
612
  if (pattern[0] === "/") {
686
613
  var lastSlash = pattern.lastIndexOf("/");
687
614
  var expression = pattern.substring(1, lastSlash);
688
615
  var flags = pattern.substring(lastSlash + 1);
616
+ // @ts-expect-error [FEI-5003] - TS2713 - Cannot access 'RegExp.flags' because 'RegExp' is a type, but not a namespace. Did you mean to retrieve the type of the property 'flags' in 'RegExp' with 'RegExp["flags"]'?
689
617
  return new RegExp(expression, flags);
690
618
  }
691
-
692
619
  return new RegExp(pattern);
693
- } // This static method returns an string array with index and input
620
+ }
621
+
622
+ // This static method returns an string array with index and input
694
623
  // properties added, in order to simulate the return value of the
695
624
  // String.match() method. We use it when a Rule has no pattern and we
696
625
  // want to simulate a match on the entire content string.
697
-
698
-
699
626
  static FakePatternMatch(input, match, index) {
700
627
  var result = [match];
701
628
  result.index = index;
702
629
  result.input = input;
703
630
  return result;
704
631
  }
705
-
706
632
  }
707
-
708
633
  _defineProperty(Rule, "DEFAULT_SELECTOR", void 0);
709
-
710
634
  _defineProperty(Rule, "Severity", {
711
635
  ERROR: 1,
712
636
  WARNING: 2,
713
637
  GUIDELINE: 3,
714
638
  BULK_WARNING: 4
715
639
  });
716
-
717
640
  Rule.DEFAULT_SELECTOR = Selector.parse("text");
718
641
 
719
642
  /* eslint-disable no-useless-escape */
@@ -721,17 +644,17 @@ Rule.DEFAULT_SELECTOR = Selector.parse("text");
721
644
  // portion which is usually just the hostname, but may also include
722
645
  // a username, password or port. We don't strip those things out because
723
646
  // we typically want to reject any URL that includes them
724
- var HOSTNAME = /\/\/([^\/]+)/; // Return the hostname of the URL, with any "www." prefix removed.
725
- // If this is a relative URL with no hostname, return an empty string.
647
+ var HOSTNAME = /\/\/([^\/]+)/;
726
648
 
649
+ // Return the hostname of the URL, with any "www." prefix removed.
650
+ // If this is a relative URL with no hostname, return an empty string.
727
651
  function getHostname(url) {
728
652
  if (!url) {
729
653
  return "";
730
654
  }
731
-
732
655
  var match = url.match(HOSTNAME);
733
656
  return match ? match[1] : "";
734
- } // This list of domains that count as internal domains is from
657
+ }
735
658
 
736
659
  var AbsoluteUrl = Rule.makeRule({
737
660
  name: "absolute-url",
@@ -740,7 +663,6 @@ var AbsoluteUrl = Rule.makeRule({
740
663
  lint: function lint(state, content, nodes, match) {
741
664
  var url = nodes[0].target;
742
665
  var hostname = getHostname(url);
743
-
744
666
  if (hostname === "khanacademy.org" || hostname.endsWith(".khanacademy.org")) {
745
667
  return "Don't use absolute URLs:\nWhen linking to KA content or images, omit the\nhttps://www.khanacademy.org URL prefix.\nUse a relative URL beginning with / instead.";
746
668
  }
@@ -797,10 +719,10 @@ var HeadingLevelSkip = Rule.makeRule({
797
719
  selector: "heading ~ heading",
798
720
  lint: function lint(state, content, nodes, match) {
799
721
  var currentHeading = nodes[1];
800
- var previousHeading = nodes[0]; // A heading can have a level less than, the same as
722
+ var previousHeading = nodes[0];
723
+ // A heading can have a level less than, the same as
801
724
  // or one more than the previous heading. But going up
802
725
  // by 2 or more levels is not right
803
-
804
726
  if (currentHeading.level > previousHeading.level + 1) {
805
727
  return "Skipped heading level:\nthis heading is level ".concat(currentHeading.level, " but\nthe previous heading was level ").concat(previousHeading.level);
806
728
  }
@@ -816,9 +738,9 @@ var HeadingSentenceCase = Rule.makeRule({
816
738
  message: "First letter is lowercase:\nthe first letter of a heading should be capitalized."
817
739
  });
818
740
 
741
+ // These are 3-letter and longer words that we would not expect to be
819
742
  // capitalized even in a title-case heading. See
820
743
  // http://blog.apastyle.org/apastyle/2012/03/title-case-and-sentence-case-capitalization-in-apa-style.html
821
-
822
744
  var littleWords = {
823
745
  and: true,
824
746
  nor: true,
@@ -826,12 +748,10 @@ var littleWords = {
826
748
  the: true,
827
749
  for: true
828
750
  };
829
-
830
751
  function isCapitalized(word) {
831
752
  var c = word[0];
832
753
  return c === c.toUpperCase();
833
754
  }
834
-
835
755
  var HeadingTitleCase = Rule.makeRule({
836
756
  name: "heading-title-case",
837
757
  severity: Rule.Severity.GUIDELINE,
@@ -862,14 +782,18 @@ var HeadingTitleCase = Rule.makeRule({
862
782
  // It is marked with a locale property above, but that is NYI
863
783
  //
864
784
  // for APA style rules for title case
785
+
865
786
  var heading = content.trim();
866
- var words = heading.split(/\s+/); // Remove the first word and the little words
787
+ var words = heading.split(/\s+/);
867
788
 
789
+ // Remove the first word and the little words
868
790
  words.shift();
869
- words = words.filter( // eslint-disable-next-line no-prototype-builtins
870
- w => w.length > 2 && !littleWords.hasOwnProperty(w)); // If there are at least 3 remaining words and all
871
- // are capitalized, then the heading is in title case.
791
+ words = words.filter(
792
+ // eslint-disable-next-line no-prototype-builtins
793
+ w => w.length > 2 && !littleWords.hasOwnProperty(w));
872
794
 
795
+ // If there are at least 3 remaining words and all
796
+ // are capitalized, then the heading is in title case.
873
797
  if (words.length >= 3 && words.every(w => isCapitalized(w))) {
874
798
  return "Title-case heading:\nThis heading appears to be in title-case, but should be sentence-case.\nOnly capitalize the first letter and proper nouns.";
875
799
  }
@@ -882,11 +806,9 @@ var ImageAltText = Rule.makeRule({
882
806
  selector: "image",
883
807
  lint: function lint(state, content, nodes, match) {
884
808
  var image = nodes[0];
885
-
886
809
  if (!image.alt || !image.alt.trim()) {
887
810
  return "Images should have alt text:\nfor accessibility, all images should have alt text.\nSpecify alt text inside square brackets after the !.";
888
811
  }
889
-
890
812
  if (image.alt.length < 8) {
891
813
  return "Images should have alt text:\nfor accessibility, all images should have descriptive alt text.\nThis image's alt text is only ".concat(image.alt.length, " characters long.");
892
814
  }
@@ -906,21 +828,20 @@ var ImageSpacesAroundUrls = Rule.makeRule({
906
828
  selector: "image",
907
829
  lint: function lint(state, content, nodes, match, context) {
908
830
  var image = nodes[0];
909
- var url = image.target; // The markdown parser strips leading and trailing spaces for us,
831
+ var url = image.target;
832
+
833
+ // The markdown parser strips leading and trailing spaces for us,
910
834
  // but they're still a problem for our translation process, so
911
835
  // we need to go check for them in the unparsed source string
912
836
  // if we have it.
913
-
914
837
  if (context && context.content) {
915
838
  // Find the url in the original content and make sure that the
916
839
  // character before is '(' and the character after is ')'
917
840
  var index = context.content.indexOf(url);
918
-
919
841
  if (index === -1) {
920
842
  // It is not an error if we didn't find it.
921
843
  return;
922
844
  }
923
-
924
845
  if (context.content[index - 1] !== "(" || context.content[index + url.length] !== ")") {
925
846
  return "Whitespace before or after image url:\nFor images, don't include any space or newlines after '(' or before ')'.\nWhitespace in image URLs causes translation difficulties.";
926
847
  }
@@ -928,12 +849,12 @@ var ImageSpacesAroundUrls = Rule.makeRule({
928
849
  }
929
850
  });
930
851
 
852
+ // Normally we have one rule per file. But since our selector class
931
853
  // can't match specific widget types directly, this rule implements
932
854
  // a number of image widget related rules in one place. This should
933
855
  // slightly increase efficiency, but it means that if there is more
934
856
  // than one problem with an image widget, the user will only see one
935
857
  // problem at a time.
936
-
937
858
  var ImageWidget = Rule.makeRule({
938
859
  name: "image-widget",
939
860
  severity: Rule.Severity.WARNING,
@@ -942,28 +863,26 @@ var ImageWidget = Rule.makeRule({
942
863
  // This rule only looks at image widgets
943
864
  if (state.currentNode().widgetType !== "image") {
944
865
  return;
945
- } // If it can't find a definition for the widget it does nothing
946
-
866
+ }
947
867
 
868
+ // If it can't find a definition for the widget it does nothing
948
869
  var widget = context && context.widgets && context.widgets[state.currentNode().id];
949
-
950
870
  if (!widget) {
951
871
  return;
952
- } // Make sure there is alt text
953
-
872
+ }
954
873
 
874
+ // Make sure there is alt text
955
875
  var alt = widget.options.alt;
956
-
957
876
  if (!alt) {
958
877
  return "Images should have alt text:\nfor accessibility, all images should have a text description.\nAdd a description in the \"Alt Text\" box of the image widget.";
959
- } // Make sure the alt text it is not trivial
960
-
878
+ }
961
879
 
880
+ // Make sure the alt text it is not trivial
962
881
  if (alt.trim().length < 8) {
963
882
  return "Images should have alt text:\nfor accessibility, all images should have descriptive alt text.\nThis image's alt text is only ".concat(alt.trim().length, " characters long.");
964
- } // Make sure there is no math in the caption
965
-
883
+ }
966
884
 
885
+ // Make sure there is no math in the caption
967
886
  if (widget.options.caption && widget.options.caption.match(/[^\\]\$/)) {
968
887
  return "No math in image captions:\nDon't include math expressions in image captions.";
969
888
  }
@@ -1017,29 +936,28 @@ var MathAlignLinebreaks = Rule.makeRule({
1017
936
  // enforces that you don't have the wrong number of pairs of backslashes.
1018
937
  lint: function lint(state, content, nodes, match) {
1019
938
  var text = match[0];
1020
-
1021
939
  while (text.length) {
1022
940
  var index = text.indexOf("\\\\");
1023
-
1024
941
  if (index === -1) {
1025
942
  // No more backslash pairs, so we found no lint
1026
943
  return null;
1027
944
  }
945
+ text = text.substring(index + 2);
1028
946
 
1029
- text = text.substring(index + 2); // Now we expect to find optional spaces, another pair of
947
+ // Now we expect to find optional spaces, another pair of
1030
948
  // backslashes, and more optional spaces not followed immediately
1031
949
  // by another pair of backslashes.
950
+ var nextpair = text.match(/^\s*\\\\\s*(?!\\\\)/);
1032
951
 
1033
- var nextpair = text.match(/^\s*\\\\\s*(?!\\\\)/); // If that does not match then we either have too few or too
952
+ // If that does not match then we either have too few or too
1034
953
  // many pairs of backslashes.
1035
-
1036
954
  if (!nextpair) {
1037
955
  return "Use four backslashes between lines of an align block";
1038
- } // If it did match, then, shorten the string and continue looping
956
+ }
957
+
958
+ // If it did match, then, shorten the string and continue looping
1039
959
  // (because a single align block may have multiple lines that
1040
960
  // all must be separated by two sets of double backslashes).
1041
-
1042
-
1043
961
  text = text.substring(nextpair[0].length);
1044
962
  }
1045
963
  }
@@ -1093,10 +1011,10 @@ var MathTextEmpty = Rule.makeRule({
1093
1011
  message: "Empty \\text{} block in math expression"
1094
1012
  });
1095
1013
 
1014
+ // Because no selector is specified, this rule only applies to text nodes.
1096
1015
  // Math and code hold their content directly and do not have text nodes
1097
1016
  // beneath them (unlike the HTML DOM) so this rule automatically does not
1098
1017
  // apply inside $$ or ``.
1099
-
1100
1018
  var MathWithoutDollars = Rule.makeRule({
1101
1019
  name: "math-without-dollars",
1102
1020
  severity: Rule.Severity.GUIDELINE,
@@ -1127,7 +1045,6 @@ var TableMissingCells = Rule.makeRule({
1127
1045
  var table = nodes[0];
1128
1046
  var headerLength = table.header.length;
1129
1047
  var rowLengths = table.cells.map(r => r.length);
1130
-
1131
1048
  for (var r = 0; r < rowLengths.length; r++) {
1132
1049
  if (rowLengths[r] !== headerLength) {
1133
1050
  return "Table rows don't match header:\nThe table header has ".concat(headerLength, " cells, but\nRow ").concat(r + 1, " has ").concat(rowLengths[r], " cells.");
@@ -1136,10 +1053,10 @@ var TableMissingCells = Rule.makeRule({
1136
1053
  }
1137
1054
  });
1138
1055
 
1056
+ // Because no selector is specified, this rule only applies to text nodes.
1139
1057
  // Math and code hold their content directly and do not have text nodes
1140
1058
  // beneath them (unlike the HTML DOM) so this rule automatically does not
1141
1059
  // apply inside $$ or ``.
1142
-
1143
1060
  var UnbalancedCodeDelimiters = Rule.makeRule({
1144
1061
  name: "unbalanced-code-delimiters",
1145
1062
  severity: Rule.Severity.ERROR,
@@ -1164,6 +1081,7 @@ var WidgetInTable = Rule.makeRule({
1164
1081
  // TODO(davidflanagan):
1165
1082
  var AllRules = [AbsoluteUrl, BlockquotedMath, BlockquotedWidget, DoubleSpacingAfterTerminal, ExtraContentSpacing, HeadingLevel1, HeadingLevelSkip, HeadingSentenceCase, HeadingTitleCase, ImageAltText, ImageInTable, LinkClickHere, LongParagraph, MathAdjacent, MathAlignExtraBreak, MathAlignLinebreaks, MathEmpty, MathFontSize, MathFrac, MathNested, MathStartsWithSpace, MathTextEmpty, NestedLists, TableMissingCells, UnescapedDollar, WidgetInTable, Profanity, MathWithoutDollars, UnbalancedCodeDelimiters, ImageSpacesAroundUrls, ImageWidget];
1166
1083
 
1084
+ // TreeNode is the type of a node in a parse tree. The only real requirement is
1167
1085
  // that every node has a string-valued `type` property
1168
1086
 
1169
1087
  // This is the TreeTransformer class described in detail at the
@@ -1172,29 +1090,30 @@ class TreeTransformer {
1172
1090
  // To create a tree transformer, just pass the root node of the tree
1173
1091
  constructor(root) {
1174
1092
  _defineProperty(this, "root", void 0);
1175
-
1176
1093
  this.root = root;
1177
- } // A utility function for determing whether an arbitrary value is a node
1178
-
1094
+ }
1179
1095
 
1096
+ // A utility function for determing whether an arbitrary value is a node
1180
1097
  static isNode(n) {
1181
1098
  return n && typeof n === "object" && typeof n.type === "string";
1182
- } // Determines whether a value is a node with type "text" and has
1183
- // a text-valued `content` property.
1184
-
1099
+ }
1185
1100
 
1101
+ // Determines whether a value is a node with type "text" and has
1102
+ // a text-valued `content` property.
1186
1103
  static isTextNode(n) {
1187
1104
  return TreeTransformer.isNode(n) && n.type === "text" && typeof n.content === "string";
1188
- } // This is the main entry point for the traverse() method. See the comment
1105
+ }
1106
+
1107
+ // This is the main entry point for the traverse() method. See the comment
1189
1108
  // at the top of this file for a detailed description. Note that this
1190
1109
  // method just creates a new TraversalState object to use for this
1191
1110
  // traversal and then invokes the internal _traverse() method to begin the
1192
1111
  // recursion.
1193
-
1194
-
1195
1112
  traverse(f) {
1196
1113
  this._traverse(this.root, new TraversalState(this.root), f);
1197
- } // Do a post-order traversal of node and its descendants, invoking the
1114
+ }
1115
+
1116
+ // Do a post-order traversal of node and its descendants, invoking the
1198
1117
  // callback function f() once for each node and returning the concatenated
1199
1118
  // text content of the node and its descendants. f() is passed three
1200
1119
  // arguments: the current node, a TraversalState object representing the
@@ -1204,31 +1123,29 @@ class TreeTransformer {
1204
1123
  // This private method holds all the traversal logic and implementation
1205
1124
  // details. Note that this method uses the TraversalState object to store
1206
1125
  // information about the structure of the tree.
1207
-
1208
-
1209
- _traverse( // eslint-disable-next-line ft-flow/no-mutable-array
1210
- n, state, f) {
1126
+ _traverse(n, state, f) {
1211
1127
  var content = "";
1212
-
1213
1128
  if (TreeTransformer.isNode(n)) {
1214
1129
  // If we were called on a node object, then we handle it
1215
1130
  // this way.
1216
1131
  var _node = n; // safe cast; we just tested
1217
- // Put the node on the stack before recursing on its children
1218
1132
 
1133
+ // Put the node on the stack before recursing on its children
1219
1134
  state._containers.push(_node);
1135
+ state._ancestors.push(_node);
1220
1136
 
1221
- state._ancestors.push(_node); // Record the node's text content if it has any.
1137
+ // Record the node's text content if it has any.
1222
1138
  // Usually this is for nodes with a type property of "text",
1223
1139
  // but other nodes types like "math" may also have content.
1224
1140
  // TODO(mdr): We found a new Flow error when upgrading:
1225
1141
  // "node.content (property `content` is missing in `TreeNode` [1].)"
1226
- // $FlowFixMe[prop-missing](0.57.3->0.75.0)
1227
-
1228
-
1142
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'content' does not exist on type 'TreeNode'.
1229
1143
  if (typeof _node.content === "string") {
1144
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'content' does not exist on type 'TreeNode'.
1230
1145
  content = _node.content;
1231
- } // Recurse on the node. If there was content above, then there
1146
+ }
1147
+
1148
+ // Recurse on the node. If there was content above, then there
1232
1149
  // probably won't be any children to recurse on, but we check
1233
1150
  // anyway.
1234
1151
  //
@@ -1237,14 +1154,13 @@ class TreeTransformer {
1237
1154
  // put a switch statement here to dispatch on the node type
1238
1155
  // property with specific recursion steps for each known type of
1239
1156
  // node.
1240
-
1241
-
1242
1157
  var keys = Object.keys(_node);
1243
1158
  keys.forEach(key => {
1244
1159
  // Never recurse on the type property
1245
1160
  if (key === "type") {
1246
1161
  return;
1247
- } // Ignore properties that are null or primitive and only
1162
+ }
1163
+ // Ignore properties that are null or primitive and only
1248
1164
  // recurse on objects and arrays. Note that we don't do a
1249
1165
  // isNode() check here. That is done in the recursive call to
1250
1166
  // _traverse(). Note that the recursive call on each child
@@ -1252,35 +1168,33 @@ class TreeTransformer {
1252
1168
  // content to the content for this node. Also note that we
1253
1169
  // push the name of the property we're recursing over onto a
1254
1170
  // TraversalState stack.
1255
-
1256
-
1257
1171
  var value = _node[key];
1258
-
1259
1172
  if (value && typeof value === "object") {
1260
1173
  state._indexes.push(key);
1261
-
1262
1174
  content += this._traverse(value, state, f);
1263
-
1264
1175
  state._indexes.pop();
1265
1176
  }
1266
- }); // Restore the stacks after recursing on the children
1177
+ });
1267
1178
 
1179
+ // Restore the stacks after recursing on the children
1268
1180
  state._currentNode = state._ancestors.pop();
1181
+ state._containers.pop();
1269
1182
 
1270
- state._containers.pop(); // And finally call the traversal callback for this node. Note
1183
+ // And finally call the traversal callback for this node. Note
1271
1184
  // that this is post-order traversal. We call the callback on the
1272
1185
  // way back up the tree, not on the way down. That way we already
1273
1186
  // know all the content contained within the node.
1274
-
1275
-
1276
1187
  f(_node, state, content);
1277
1188
  } else if (Array.isArray(n)) {
1278
1189
  // If we were called on an array instead of a node, then
1279
1190
  // this is the code we use to recurse.
1280
- var nodes = n; // Push the array onto the stack. This will allow the
1191
+ var nodes = n;
1192
+
1193
+ // Push the array onto the stack. This will allow the
1281
1194
  // TraversalState object to locate siblings of this node.
1195
+ state._containers.push(nodes);
1282
1196
 
1283
- state._containers.push(nodes); // Now loop through this array and recurse on each element in it.
1197
+ // Now loop through this array and recurse on each element in it.
1284
1198
  // Before recursing on an element, we push its array index on a
1285
1199
  // TraversalState stack so that the TraversalState sibling methods
1286
1200
  // can work. Note that TraversalState methods can alter the length
@@ -1288,31 +1202,28 @@ class TreeTransformer {
1288
1202
  // are careful here to test the array length on each iteration and
1289
1203
  // to reset the index when we pop the stack. Also note that we
1290
1204
  // concatentate the text content of the children.
1291
-
1292
-
1293
1205
  var index = 0;
1294
-
1295
1206
  while (index < nodes.length) {
1296
1207
  state._indexes.push(index);
1297
-
1298
- content += this._traverse(nodes[index], state, f); // Casting to convince Flow that this is a number
1299
-
1208
+ content += this._traverse(nodes[index], state, f);
1209
+ // Casting to convince Flow that this is a number
1300
1210
  index = state._indexes.pop() + 1;
1301
- } // Pop the array off the stack. Note, however, that we do not call
1211
+ }
1212
+
1213
+ // Pop the array off the stack. Note, however, that we do not call
1302
1214
  // the traversal callback on the array. That function is only
1303
1215
  // called for nodes, not arrays of nodes.
1304
-
1305
-
1306
1216
  state._containers.pop();
1307
- } // The _traverse() method always returns the text content of
1217
+ }
1218
+
1219
+ // The _traverse() method always returns the text content of
1308
1220
  // this node and its children. This is the one piece of state that
1309
1221
  // is not tracked in the TraversalState object.
1310
-
1311
-
1312
1222
  return content;
1313
1223
  }
1224
+ }
1314
1225
 
1315
- } // An instance of this class is passed to the callback function for
1226
+ // An instance of this class is passed to the callback function for
1316
1227
  // each node traversed. The class itself is not exported, but its
1317
1228
  // methods define the API available to the traversal callback.
1318
1229
 
@@ -1324,67 +1235,65 @@ class TreeTransformer {
1324
1235
  * instantiated directly, but is exported so that its type can be used for
1325
1236
  * Flow annotaions.
1326
1237
  **/
1327
-
1328
1238
  class TraversalState {
1329
1239
  // The root node of the tree being traversed
1240
+
1330
1241
  // These are internal state properties. Use the accessor methods defined
1331
1242
  // below instead of using these properties directly. Note that the
1332
1243
  // _containers and _indexes stacks can have two different types of
1333
1244
  // elements, depending on whether we just recursed on an array or on a
1334
1245
  // node. This is hard for Flow to deal with, so you'll see a number of
1335
1246
  // Flow casts through the any type when working with these two properties.
1336
- // eslint-disable-next-line ft-flow/no-mutable-array
1247
+
1337
1248
  // The constructor just stores the root node and creates empty stacks.
1338
1249
  constructor(root) {
1339
1250
  _defineProperty(this, "root", void 0);
1340
-
1341
1251
  _defineProperty(this, "_currentNode", void 0);
1342
-
1343
1252
  _defineProperty(this, "_containers", void 0);
1344
-
1345
1253
  _defineProperty(this, "_indexes", void 0);
1346
-
1347
1254
  _defineProperty(this, "_ancestors", void 0);
1255
+ this.root = root;
1348
1256
 
1349
- this.root = root; // When the callback is called, this property will hold the
1257
+ // When the callback is called, this property will hold the
1350
1258
  // node that is currently being traversed.
1259
+ this._currentNode = null;
1351
1260
 
1352
- this._currentNode = null; // This is a stack of the objects and arrays that we've
1261
+ // This is a stack of the objects and arrays that we've
1353
1262
  // traversed through before reaching the currentNode.
1354
1263
  // It is different than the ancestors array.
1264
+ this._containers = new Stack();
1355
1265
 
1356
- this._containers = new Stack(); // This stack has the same number of elements as the _containers
1266
+ // This stack has the same number of elements as the _containers
1357
1267
  // stack. The last element of this._indexes[] is the index of
1358
1268
  // the current node in the object or array that is the last element
1359
1269
  // of this._containers[]. If the last element of this._containers[] is
1360
1270
  // an array, then the last element of this stack will be a number.
1361
1271
  // Otherwise if the last container is an object, then the last index
1362
1272
  // will be a string property name.
1273
+ this._indexes = new Stack();
1363
1274
 
1364
- this._indexes = new Stack(); // This is a stack of the ancestor nodes of the current one.
1275
+ // This is a stack of the ancestor nodes of the current one.
1365
1276
  // It is different than the containers[] stack because it only
1366
1277
  // includes nodes, not arrays.
1367
-
1368
1278
  this._ancestors = new Stack();
1369
1279
  }
1280
+
1370
1281
  /**
1371
1282
  * Return the current node in the traversal. Any time the traversal
1372
1283
  * callback is called, this method will return the name value as the
1373
1284
  * first argument to the callback.
1374
1285
  */
1375
-
1376
-
1377
1286
  currentNode() {
1378
1287
  return this._currentNode || this.root;
1379
1288
  }
1289
+
1380
1290
  /**
1381
1291
  * Return the parent of the current node, if there is one, or null.
1382
1292
  */
1383
-
1384
-
1385
1293
  parent() {
1386
1294
  return this._ancestors.top();
1387
1295
  }
1296
+
1388
1297
  /**
1389
1298
  * Return an array of ancestor nodes. The first element of this array is
1390
1299
  * the same as this.parent() and the last element is the root node. If we
@@ -1392,79 +1301,68 @@ class TraversalState {
1392
1301
  * This method makes a copy of the internal state, so modifications to the
1393
1302
  * returned array have no effect on the traversal.
1394
1303
  */
1395
-
1396
-
1397
1304
  ancestors() {
1398
1305
  return this._ancestors.values();
1399
1306
  }
1307
+
1400
1308
  /**
1401
1309
  * Return the next sibling of this node, if it has one, or null otherwise.
1402
1310
  */
1403
-
1404
-
1405
1311
  nextSibling() {
1406
- var siblings = this._containers.top(); // If we're at the root of the tree or if the parent is an
1407
- // object instead of an array, then there are no siblings.
1408
-
1312
+ var siblings = this._containers.top();
1409
1313
 
1314
+ // If we're at the root of the tree or if the parent is an
1315
+ // object instead of an array, then there are no siblings.
1410
1316
  if (!siblings || !Array.isArray(siblings)) {
1411
1317
  return null;
1412
- } // The top index is a number because the top container is an array
1413
-
1318
+ }
1414
1319
 
1320
+ // The top index is a number because the top container is an array
1415
1321
  var index = this._indexes.top();
1416
-
1417
1322
  if (siblings.length > index + 1) {
1418
1323
  return siblings[index + 1];
1419
1324
  }
1420
-
1421
1325
  return null; // There is no next sibling
1422
1326
  }
1327
+
1423
1328
  /**
1424
1329
  * Return the previous sibling of this node, if it has one, or null
1425
1330
  * otherwise.
1426
1331
  */
1427
-
1428
-
1429
1332
  previousSibling() {
1430
- var siblings = this._containers.top(); // If we're at the root of the tree or if the parent is an
1431
- // object instead of an array, then there are no siblings.
1432
-
1333
+ var siblings = this._containers.top();
1433
1334
 
1335
+ // If we're at the root of the tree or if the parent is an
1336
+ // object instead of an array, then there are no siblings.
1434
1337
  if (!siblings || !Array.isArray(siblings)) {
1435
1338
  return null;
1436
- } // The top index is a number because the top container is an array
1437
-
1339
+ }
1438
1340
 
1341
+ // The top index is a number because the top container is an array
1439
1342
  var index = this._indexes.top();
1440
-
1441
1343
  if (index > 0) {
1442
1344
  return siblings[index - 1];
1443
1345
  }
1444
-
1445
1346
  return null; // There is no previous sibling
1446
1347
  }
1348
+
1447
1349
  /**
1448
1350
  * Remove the next sibling node (if there is one) from the tree. Returns
1449
1351
  * the removed sibling or null. This method makes it easy to traverse a
1450
1352
  * tree and concatenate adjacent text nodes into a single node.
1451
1353
  */
1452
-
1453
-
1454
1354
  removeNextSibling() {
1455
1355
  var siblings = this._containers.top();
1456
-
1457
1356
  if (siblings && Array.isArray(siblings)) {
1458
1357
  // top index is a number because top container is an array
1459
1358
  var index = this._indexes.top();
1460
-
1461
1359
  if (siblings.length > index + 1) {
1462
1360
  return siblings.splice(index + 1, 1)[0];
1463
1361
  }
1464
1362
  }
1465
-
1466
1363
  return null;
1467
1364
  }
1365
+
1468
1366
  /**
1469
1367
  * Replace the current node in the tree with the specified nodes. If no
1470
1368
  * nodes are passed, this is a node deletion. If one node (or array) is
@@ -1476,37 +1374,30 @@ class TraversalState {
1476
1374
  * This method throws an error if you attempt to replace the root node of
1477
1375
  * the tree.
1478
1376
  */
1479
-
1480
-
1481
1377
  replace() {
1482
1378
  var parent = this._containers.top();
1483
-
1484
1379
  if (!parent) {
1485
1380
  throw new PerseusError("Can't replace the root of the tree", Errors.Internal);
1486
- } // The top of the container stack is either an array or an object
1381
+ }
1382
+
1383
+ // The top of the container stack is either an array or an object
1487
1384
  // and the top of the indexes stack is a corresponding array index
1488
1385
  // or object property. This is hard for Flow, so we have to do some
1489
1386
  // unsafe casting and be careful when we use which cast version
1490
-
1491
-
1492
1387
  for (var _len = arguments.length, replacements = new Array(_len), _key = 0; _key < _len; _key++) {
1493
1388
  replacements[_key] = arguments[_key];
1494
1389
  }
1495
-
1496
1390
  if (Array.isArray(parent)) {
1497
- var index = this._indexes.top(); // For an array parent we just splice the new nodes in
1498
-
1499
-
1500
- parent.splice(index, 1, ...replacements); // Adjust the index to account for the changed array length.
1391
+ var index = this._indexes.top();
1392
+ // For an array parent we just splice the new nodes in
1393
+ parent.splice(index, 1, ...replacements);
1394
+ // Adjust the index to account for the changed array length.
1501
1395
  // We don't want to traverse any of the newly inserted nodes.
1502
-
1503
1396
  this._indexes.pop();
1504
-
1505
1397
  this._indexes.push(index + replacements.length - 1);
1506
1398
  } else {
1507
- var property = this._indexes.top(); // For an object parent we care how many new nodes there are
1508
-
1509
-
1399
+ var property = this._indexes.top();
1400
+ // For an object parent we care how many new nodes there are
1510
1401
  if (replacements.length === 0) {
1511
1402
  // Deletion
1512
1403
  delete parent[property];
@@ -1519,16 +1410,16 @@ class TraversalState {
1519
1410
  }
1520
1411
  }
1521
1412
  }
1413
+
1522
1414
  /**
1523
1415
  * Returns true if the current node has a previous sibling and false
1524
1416
  * otherwise. If this method returns false, then previousSibling() will
1525
1417
  * return null, and goToPreviousSibling() will throw an error.
1526
1418
  */
1527
-
1528
-
1529
1419
  hasPreviousSibling() {
1530
1420
  return Array.isArray(this._containers.top()) && this._indexes.top() > 0;
1531
1421
  }
1422
+
1532
1423
  /**
1533
1424
  * Modify this traversal state object to have the state it would have had
1534
1425
  * when visiting the previous sibling. Note that you may want to use
@@ -1537,31 +1428,27 @@ class TraversalState {
1537
1428
  * traversals, but is used by the Selector class for matching multi-node
1538
1429
  * selectors.
1539
1430
  */
1540
-
1541
-
1542
1431
  goToPreviousSibling() {
1543
1432
  if (!this.hasPreviousSibling()) {
1544
1433
  throw new PerseusError("goToPreviousSibling(): node has no previous sibling", Errors.Internal);
1545
1434
  }
1546
-
1547
- this._currentNode = this.previousSibling(); // Since we know that we have a previous sibling, we know that
1435
+ this._currentNode = this.previousSibling();
1436
+ // Since we know that we have a previous sibling, we know that
1548
1437
  // the value on top of the stack is a number, but we have to do
1549
1438
  // this unsafe cast because Flow doesn't know that.
1550
-
1551
1439
  var index = this._indexes.pop();
1552
-
1553
1440
  this._indexes.push(index - 1);
1554
1441
  }
1442
+
1555
1443
  /**
1556
1444
  * Returns true if the current node has an ancestor and false otherwise.
1557
1445
  * If this method returns false, then the parent() method will return
1558
1446
  * null and goToParent() will throw an error
1559
1447
  */
1560
-
1561
-
1562
1448
  hasParent() {
1563
1449
  return this._ancestors.size() !== 0;
1564
1450
  }
1451
+
1565
1452
  /**
1566
1453
  * Modify this object to look like it will look when we (later) visit the
1567
1454
  * parent node of this node. You should not modify the instance passed to
@@ -1571,33 +1458,30 @@ class TraversalState {
1571
1458
  * matching multi-node selectors that involve parent and ancestor
1572
1459
  * selectors.
1573
1460
  */
1574
-
1575
-
1576
1461
  goToParent() {
1577
1462
  if (!this.hasParent()) {
1578
1463
  throw new PerseusError("goToParent(): node has no ancestor", Errors.NotAllowed);
1579
1464
  }
1465
+ this._currentNode = this._ancestors.pop();
1580
1466
 
1581
- this._currentNode = this._ancestors.pop(); // We need to pop the containers and indexes stacks at least once
1467
+ // We need to pop the containers and indexes stacks at least once
1582
1468
  // and more as needed until we restore the invariant that
1583
1469
  // this._containers.top()[this.indexes.top()] === this._currentNode
1584
1470
  //
1585
-
1586
- while (this._containers.size() && // This is safe, but easier to just disable flow than do casts
1471
+ while (this._containers.size() &&
1472
+ // This is safe, but easier to just disable flow than do casts
1587
1473
  // $FlowFixMe[incompatible-use]
1588
1474
  this._containers.top()[this._indexes.top()] !== this._currentNode) {
1589
1475
  this._containers.pop();
1590
-
1591
1476
  this._indexes.pop();
1592
1477
  }
1593
1478
  }
1479
+
1594
1480
  /**
1595
1481
  * Return a new TraversalState object that is a copy of this one.
1596
1482
  * This method is useful in conjunction with the mutating methods
1597
1483
  * goToParent() and goToPreviousSibling().
1598
1484
  */
1599
-
1600
-
1601
1485
  clone() {
1602
1486
  var clone = new TraversalState(this.root);
1603
1487
  clone._currentNode = this._currentNode;
@@ -1606,18 +1490,17 @@ class TraversalState {
1606
1490
  clone._ancestors = this._ancestors.clone();
1607
1491
  return clone;
1608
1492
  }
1493
+
1609
1494
  /**
1610
1495
  * Returns true if this TraversalState object is equal to that
1611
1496
  * TraversalState object, or false otherwise. This method exists
1612
1497
  * primarily for use by our unit tests.
1613
1498
  */
1614
-
1615
-
1616
1499
  equals(that) {
1617
1500
  return this.root === that.root && this._currentNode === that._currentNode && this._containers.equals(that._containers) && this._indexes.equals(that._indexes) && this._ancestors.equals(that._ancestors);
1618
1501
  }
1619
-
1620
1502
  }
1503
+
1621
1504
  /**
1622
1505
  * This class is an internal utility that just treats an array as a stack
1623
1506
  * and gives us a top() method so we don't have to write expressions like
@@ -1626,76 +1509,63 @@ class TraversalState {
1626
1509
  * modifying our internal stacks. The use of this Stack abstraction makes
1627
1510
  * the TraversalState class simpler in a number of places.
1628
1511
  */
1629
-
1630
1512
  class Stack {
1631
- // eslint-disable-next-line ft-flow/no-mutable-array
1632
1513
  constructor(array) {
1633
1514
  _defineProperty(this, "stack", void 0);
1634
-
1635
1515
  this.stack = array ? array.slice(0) : [];
1636
1516
  }
1637
- /** Push a value onto the stack. */
1638
-
1639
1517
 
1518
+ /** Push a value onto the stack. */
1640
1519
  push(v) {
1641
1520
  this.stack.push(v);
1642
1521
  }
1643
- /** Pop a value off of the stack. */
1644
-
1645
1522
 
1523
+ /** Pop a value off of the stack. */
1646
1524
  pop() {
1525
+ // @ts-expect-error [FEI-5003] - TS2322 - Type 'T | undefined' is not assignable to type 'T'.
1647
1526
  return this.stack.pop();
1648
1527
  }
1649
- /** Return the top value of the stack without popping it. */
1650
-
1651
1528
 
1529
+ /** Return the top value of the stack without popping it. */
1652
1530
  top() {
1653
1531
  return this.stack[this.stack.length - 1];
1654
1532
  }
1655
- /** Return a copy of the stack as an array */
1656
-
1657
1533
 
1534
+ /** Return a copy of the stack as an array */
1658
1535
  values() {
1659
1536
  return this.stack.slice(0);
1660
1537
  }
1661
- /** Return the number of elements in the stack */
1662
-
1663
1538
 
1539
+ /** Return the number of elements in the stack */
1664
1540
  size() {
1665
1541
  return this.stack.length;
1666
1542
  }
1667
- /** Return a string representation of the stack */
1668
-
1669
1543
 
1544
+ /** Return a string representation of the stack */
1670
1545
  toString() {
1671
1546
  return this.stack.toString();
1672
1547
  }
1673
- /** Return a shallow copy of the stack */
1674
-
1675
1548
 
1549
+ /** Return a shallow copy of the stack */
1676
1550
  clone() {
1677
1551
  return new Stack(this.stack);
1678
1552
  }
1553
+
1679
1554
  /**
1680
1555
  * Compare this stack to another and return true if the contents of
1681
1556
  * the two arrays are the same.
1682
1557
  */
1683
-
1684
-
1685
1558
  equals(that) {
1686
1559
  if (!that || !that.stack || that.stack.length !== this.stack.length) {
1687
1560
  return false;
1688
1561
  }
1689
-
1690
1562
  for (var i = 0; i < this.stack.length; i++) {
1691
1563
  if (this.stack[i] !== that.stack[i]) {
1692
1564
  return false;
1693
1565
  }
1694
1566
  }
1695
-
1696
1567
  return true;
1697
1568
  }
1698
-
1699
1569
  }
1700
1570
 
1701
1571
  // Define the shape of the linter context object that is passed through the
@@ -1713,6 +1583,8 @@ var linterContextDefault = {
1713
1583
  };
1714
1584
 
1715
1585
  var allLintRules = AllRules.filter(r => r.severity < Rule.Severity.BULK_WARNING);
1586
+
1587
+ //
1716
1588
  // Run the Perseus linter over the specified markdown parse tree,
1717
1589
  // with the specified context object, and
1718
1590
  // return a (possibly empty) array of lint warning objects. If the
@@ -1736,26 +1608,26 @@ var allLintRules = AllRules.filter(r => r.severity < Rule.Severity.BULK_WARNING)
1736
1608
  // in that case). This would allow the one function to be used for both
1737
1609
  // online linting and batch linting.
1738
1610
  //
1739
-
1740
1611
  function runLinter(tree, context, highlight) {
1741
1612
  var rules = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : allLintRules;
1742
1613
  var warnings = [];
1743
- var tt = new TreeTransformer(tree); // The markdown parser often outputs adjacent text nodes. We
1744
- // coalesce them before linting for efficiency and accuracy.
1614
+ var tt = new TreeTransformer(tree);
1745
1615
 
1616
+ // The markdown parser often outputs adjacent text nodes. We
1617
+ // coalesce them before linting for efficiency and accuracy.
1746
1618
  tt.traverse((node, state, content) => {
1747
1619
  if (TreeTransformer.isTextNode(node)) {
1748
1620
  var next = state.nextSibling();
1749
-
1750
1621
  while (TreeTransformer.isTextNode(next)) {
1751
- // $FlowFixMe[prop-missing]
1752
- // $FlowFixMe[incompatible-use]
1622
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'content' does not exist on type 'TreeNode'. | TS2533 - Object is possibly 'null' or 'undefined'. | TS2339 - Property 'content' does not exist on type 'TreeNode'.
1753
1623
  node.content += next.content;
1754
1624
  state.removeNextSibling();
1755
1625
  next = state.nextSibling();
1756
1626
  }
1757
1627
  }
1758
- }); // HTML tables are complicated, and the CSS we use in
1628
+ });
1629
+
1630
+ // HTML tables are complicated, and the CSS we use in
1759
1631
  // ../components/lint.jsx to display lint does not work to
1760
1632
  // correctly position the lint indicators in the margin when the
1761
1633
  // lint is inside a table. So as a workaround we keep track of all
@@ -1772,30 +1644,29 @@ function runLinter(tree, context, highlight) {
1772
1644
  // issue too. But using JavaScript has its own downsides: there is
1773
1645
  // risk that the linter JavaScript would interfere with
1774
1646
  // widget-related Javascript.
1775
-
1776
1647
  var tableWarnings = [];
1777
- var insideTable = false; // Traverse through the nodes of the parse tree. At each node, loop
1648
+ var insideTable = false;
1649
+
1650
+ // Traverse through the nodes of the parse tree. At each node, loop
1778
1651
  // through the array of lint rules and check whether there is a
1779
1652
  // lint violation at that node.
1780
-
1781
1653
  tt.traverse((node, state, content) => {
1782
- var nodeWarnings = []; // If our rule is only designed to be tested against a particular
1654
+ var nodeWarnings = [];
1655
+
1656
+ // If our rule is only designed to be tested against a particular
1783
1657
  // content type and we're not in that content type, we don't need to
1784
1658
  // consider that rule.
1659
+ var applicableRules = rules.filter(r => r.applies(context));
1785
1660
 
1786
- var applicableRules = rules.filter(r => r.applies(context)); // Generate a stack so we can identify our position in the tree in
1661
+ // Generate a stack so we can identify our position in the tree in
1787
1662
  // lint rules
1788
-
1789
1663
  var stack = [...context.stack];
1790
1664
  stack.push(node.type);
1791
-
1792
1665
  var nodeContext = _objectSpread2(_objectSpread2({}, context), {}, {
1793
1666
  stack: stack.join(".")
1794
1667
  });
1795
-
1796
1668
  applicableRules.forEach(rule => {
1797
1669
  var warning = rule.check(node, state, content, nodeContext);
1798
-
1799
1670
  if (warning) {
1800
1671
  // The start and end locations are relative to this
1801
1672
  // particular node, and so are not generally very useful.
@@ -1805,32 +1676,34 @@ function runLinter(tree, context, highlight) {
1805
1676
  // character range that will be useful
1806
1677
  if (warning.start || warning.end) {
1807
1678
  warning.target = content.substring(warning.start, warning.end);
1808
- } // Add the warning to the list of all lint we've found
1679
+ }
1809
1680
 
1681
+ // Add the warning to the list of all lint we've found
1682
+ warnings.push(warning);
1810
1683
 
1811
- warnings.push(warning); // If we're going to be highlighting lint, then we also
1684
+ // If we're going to be highlighting lint, then we also
1812
1685
  // need to keep track of warnings specific to this node.
1813
-
1814
1686
  if (highlight) {
1815
1687
  nodeWarnings.push(warning);
1816
1688
  }
1817
1689
  }
1818
- }); // If we're not highlighting lint in the tree, then we're done
1819
- // traversing this node.
1690
+ });
1820
1691
 
1692
+ // If we're not highlighting lint in the tree, then we're done
1693
+ // traversing this node.
1821
1694
  if (!highlight) {
1822
1695
  return;
1823
- } // If the node we are currently at is a table, and there was lint
1824
- // inside the table, then we want to add that lint here
1825
-
1696
+ }
1826
1697
 
1698
+ // If the node we are currently at is a table, and there was lint
1699
+ // inside the table, then we want to add that lint here
1827
1700
  if (node.type === "table") {
1828
1701
  if (tableWarnings.length) {
1829
1702
  nodeWarnings.push(...tableWarnings);
1830
- } // We're not in a table anymore, and don't have to remember
1831
- // the warnings for the table
1832
-
1703
+ }
1833
1704
 
1705
+ // We're not in a table anymore, and don't have to remember
1706
+ // the warnings for the table
1834
1707
  insideTable = false;
1835
1708
  tableWarnings = [];
1836
1709
  } else if (!insideTable) {
@@ -1841,14 +1714,17 @@ function runLinter(tree, context, highlight) {
1841
1714
  // do this check each time... We can just wait until we ascend
1842
1715
  // up to the table, then we'll know we're out of it.
1843
1716
  insideTable = state.ancestors().some(n => n.type === "table");
1844
- } // If we are inside a table and there were any warnings on
1717
+ }
1718
+
1719
+ // If we are inside a table and there were any warnings on
1845
1720
  // this node, then we need to save the warnings for display
1846
1721
  // on the table itself
1847
-
1848
-
1849
1722
  if (insideTable && nodeWarnings.length) {
1723
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
1850
1724
  tableWarnings.push(...nodeWarnings);
1851
- } // If there were any warnings on this node, and if we're highlighting
1725
+ }
1726
+
1727
+ // If there were any warnings on this node, and if we're highlighting
1852
1728
  // lint, then reparent the node so we can highlight it. Note that
1853
1729
  // a single node can have multiple warnings. If this happends we
1854
1730
  // concatenate the warnings and newline separate them. (The lint.jsx
@@ -1861,19 +1737,17 @@ function runLinter(tree, context, highlight) {
1861
1737
  // Note that even if we're inside a table, we still reparent the
1862
1738
  // linty node so that it can be highlighted. We just make a note
1863
1739
  // of whether this lint is inside a table or not.
1864
-
1865
-
1866
1740
  if (nodeWarnings.length) {
1867
1741
  nodeWarnings.sort((a, b) => {
1868
1742
  return a.severity - b.severity;
1869
1743
  });
1870
-
1871
1744
  if (node.type !== "text" || nodeWarnings.length > 1) {
1872
1745
  // If the linty node is not a text node, or if there is more
1873
1746
  // than one warning on a text node, then reparent the entire
1874
1747
  // node under a new lint node and put the warnings there.
1875
1748
  state.replace({
1876
1749
  type: "lint",
1750
+ // @ts-expect-error [FEI-5003] - TS2345 - Argument of type '{ type: string; content: TreeNode; message: string; ruleName: any; blockHighlight: any; insideTable: boolean; severity: any; }' is not assignable to parameter of type 'TreeNode'.
1877
1751
  content: node,
1878
1752
  message: nodeWarnings.map(w => w.message).join("\n\n"),
1879
1753
  ruleName: nodeWarnings[0].rule,
@@ -1907,32 +1781,27 @@ function runLinter(tree, context, highlight) {
1907
1781
  // single line, so keeping them combined in that case might
1908
1782
  // be the best thing, anyway.
1909
1783
  //
1910
- // $FlowFixMe[prop-missing]
1784
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'content' does not exist on type 'TreeNode'.
1911
1785
  var _content = node.content; // Text nodes have content
1912
-
1913
1786
  var warning = nodeWarnings[0]; // There is only one warning.
1914
1787
  // These are the lint boundaries within the content
1915
-
1916
1788
  var start = warning.start || 0;
1917
1789
  var end = warning.end || _content.length;
1918
-
1919
1790
  var prefix = _content.substring(0, start);
1920
-
1921
1791
  var lint = _content.substring(start, end);
1922
-
1923
1792
  var suffix = _content.substring(end);
1924
-
1793
+ // TODO(FEI-5003): Give this a real type.
1925
1794
  var replacements = []; // What we'll replace the node with
1926
- // The prefix text node, if there is one
1927
1795
 
1796
+ // The prefix text node, if there is one
1928
1797
  if (prefix) {
1929
1798
  replacements.push({
1930
1799
  type: "text",
1931
1800
  content: prefix
1932
1801
  });
1933
- } // The lint node wrapped around the linty text
1934
-
1802
+ }
1935
1803
 
1804
+ // The lint node wrapped around the linty text
1936
1805
  replacements.push({
1937
1806
  type: "lint",
1938
1807
  content: {
@@ -1943,17 +1812,18 @@ function runLinter(tree, context, highlight) {
1943
1812
  ruleName: warning.rule,
1944
1813
  insideTable: insideTable,
1945
1814
  severity: warning.severity
1946
- }); // The suffix node, if there is one
1815
+ });
1947
1816
 
1817
+ // The suffix node, if there is one
1948
1818
  if (suffix) {
1949
1819
  replacements.push({
1950
1820
  type: "text",
1951
1821
  content: suffix
1952
1822
  });
1953
- } // Now replace the lint text node with the one to three
1954
- // nodes in the replacement array
1955
-
1823
+ }
1956
1824
 
1825
+ // Now replace the lint text node with the one to three
1826
+ // nodes in the replacement array
1957
1827
  state.replace(...replacements);
1958
1828
  }
1959
1829
  }