@khanacademy/perseus-linter 0.2.5 → 0.3.1

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