@khanacademy/perseus-linter 1.3.7 → 2.0.0

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