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