@khanacademy/perseus-linter 2.0.0 → 3.0.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.
package/dist/index.js CHANGED
@@ -1,1151 +1,142 @@
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';
1
+ 'use strict';
5
2
 
6
- class Selector {
7
- static parse(selectorText) {
8
- return new Parser(selectorText).parse();
9
- }
10
- match(state) {
11
- throw new PerseusError("Selector subclasses must implement match()", Errors.NotAllowed);
12
- }
13
- toString() {
14
- return "Unknown selector class";
15
- }
16
- }
17
- class Parser {
18
- constructor(s) {
19
- this.tokens = void 0;
20
- this.tokenIndex = void 0;
21
- s = s.trim().replace(/\s+/g, " ");
22
- this.tokens = s.match(Parser.TOKENS) || [];
23
- this.tokenIndex = 0;
24
- }
25
- nextToken() {
26
- return this.tokens[this.tokenIndex] || "";
27
- }
28
- consume() {
29
- this.tokenIndex++;
30
- }
31
- isIdentifier() {
32
- const c = this.tokens[this.tokenIndex][0];
33
- return c >= "a" && c <= "z" || c >= "A" && c <= "Z";
34
- }
35
- skipSpace() {
36
- while (this.nextToken() === " ") {
37
- this.consume();
38
- }
39
- }
40
- parse() {
41
- const ts = this.parseTreeSelector();
42
- let token = this.nextToken();
43
- if (!token) {
44
- return ts;
45
- }
46
- const treeSelectors = [ts];
47
- while (token) {
48
- if (token === ",") {
49
- this.consume();
50
- } else {
51
- throw new ParseError("Expected comma");
52
- }
53
- treeSelectors.push(this.parseTreeSelector());
54
- token = this.nextToken();
55
- }
56
- return new SelectorList(treeSelectors);
57
- }
58
- parseTreeSelector() {
59
- this.skipSpace();
60
- let ns = this.parseNodeSelector();
61
- for (;;) {
62
- const token = this.nextToken();
63
- if (!token || token === ",") {
64
- break;
65
- } else if (token === " ") {
66
- this.consume();
67
- ns = new AncestorCombinator(ns, this.parseNodeSelector());
68
- } else if (token === ">") {
69
- this.consume();
70
- ns = new ParentCombinator(ns, this.parseNodeSelector());
71
- } else if (token === "+") {
72
- this.consume();
73
- ns = new PreviousCombinator(ns, this.parseNodeSelector());
74
- } else if (token === "~") {
75
- this.consume();
76
- ns = new SiblingCombinator(ns, this.parseNodeSelector());
77
- } else {
78
- throw new ParseError("Unexpected token: " + token);
79
- }
80
- }
81
- return ns;
82
- }
83
- parseNodeSelector() {
84
- this.skipSpace();
85
- const t = this.nextToken();
86
- if (t === "*") {
87
- this.consume();
88
- return new AnyNode();
89
- }
90
- if (this.isIdentifier()) {
91
- this.consume();
92
- return new TypeSelector(t);
93
- }
94
- throw new ParseError("Expected node type");
95
- }
96
- }
97
- Parser.TOKENS = void 0;
98
- Parser.TOKENS = /([a-zA-Z][\w-]*)|(\d+)|[^\s]|(\s(?=[a-zA-Z\*]))/g;
99
- class ParseError extends Error {
100
- constructor(message) {
101
- super(message);
102
- }
103
- }
104
- class SelectorList extends Selector {
105
- constructor(selectors) {
106
- super();
107
- this.selectors = void 0;
108
- this.selectors = selectors;
109
- }
110
- match(state) {
111
- for (let i = 0; i < this.selectors.length; i++) {
112
- const s = this.selectors[i];
113
- const result = s.match(state);
114
- if (result) {
115
- return result;
116
- }
117
- }
118
- return null;
119
- }
120
- toString() {
121
- let result = "";
122
- for (let i = 0; i < this.selectors.length; i++) {
123
- result += i > 0 ? ", " : "";
124
- result += this.selectors[i].toString();
125
- }
126
- return result;
127
- }
128
- }
129
- class AnyNode extends Selector {
130
- match(state) {
131
- return [state.currentNode()];
132
- }
133
- toString() {
134
- return "*";
135
- }
136
- }
137
- class TypeSelector extends Selector {
138
- constructor(type) {
139
- super();
140
- this.type = void 0;
141
- this.type = type;
142
- }
143
- match(state) {
144
- const node = state.currentNode();
145
- if (node.type === this.type) {
146
- return [node];
147
- }
148
- return null;
149
- }
150
- toString() {
151
- return this.type;
152
- }
153
- }
154
- class SelectorCombinator extends Selector {
155
- constructor(left, right) {
156
- super();
157
- this.left = void 0;
158
- this.right = void 0;
159
- this.left = left;
160
- this.right = right;
161
- }
162
- }
163
- class AncestorCombinator extends SelectorCombinator {
164
- constructor(left, right) {
165
- super(left, right);
166
- }
167
- match(state) {
168
- const rightResult = this.right.match(state);
169
- if (rightResult) {
170
- state = state.clone();
171
- while (state.hasParent()) {
172
- state.goToParent();
173
- const leftResult = this.left.match(state);
174
- if (leftResult) {
175
- return leftResult.concat(rightResult);
176
- }
177
- }
178
- }
179
- return null;
180
- }
181
- toString() {
182
- return this.left.toString() + " " + this.right.toString();
183
- }
184
- }
185
- class ParentCombinator extends SelectorCombinator {
186
- constructor(left, right) {
187
- super(left, right);
188
- }
189
- match(state) {
190
- const rightResult = this.right.match(state);
191
- if (rightResult) {
192
- if (state.hasParent()) {
193
- state = state.clone();
194
- state.goToParent();
195
- const leftResult = this.left.match(state);
196
- if (leftResult) {
197
- return leftResult.concat(rightResult);
198
- }
199
- }
200
- }
201
- return null;
202
- }
203
- toString() {
204
- return this.left.toString() + " > " + this.right.toString();
205
- }
206
- }
207
- class PreviousCombinator extends SelectorCombinator {
208
- constructor(left, right) {
209
- super(left, right);
210
- }
211
- match(state) {
212
- const rightResult = this.right.match(state);
213
- if (rightResult) {
214
- if (state.hasPreviousSibling()) {
215
- state = state.clone();
216
- state.goToPreviousSibling();
217
- const leftResult = this.left.match(state);
218
- if (leftResult) {
219
- return leftResult.concat(rightResult);
220
- }
221
- }
222
- }
223
- return null;
224
- }
225
- toString() {
226
- return this.left.toString() + " + " + this.right.toString();
227
- }
228
- }
229
- class SiblingCombinator extends SelectorCombinator {
230
- constructor(left, right) {
231
- super(left, right);
232
- }
233
- match(state) {
234
- const rightResult = this.right.match(state);
235
- if (rightResult) {
236
- state = state.clone();
237
- while (state.hasPreviousSibling()) {
238
- state.goToPreviousSibling();
239
- const leftResult = this.left.match(state);
240
- if (leftResult) {
241
- return leftResult.concat(rightResult);
242
- }
243
- }
244
- }
245
- return null;
246
- }
247
- toString() {
248
- return this.left.toString() + " ~ " + this.right.toString();
249
- }
250
- }
3
+ Object.defineProperty(exports, '__esModule', { value: true });
251
4
 
252
- class Rule {
253
- constructor(name, severity, selector, pattern, lint, applies) {
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;
261
- if (!selector && !pattern) {
262
- throw new PerseusError("Lint rules must have a selector or pattern", Errors.InvalidInput, {
263
- metadata: {
264
- name
265
- }
266
- });
267
- }
268
- this.name = name || "unnamed rule";
269
- this.severity = severity || Rule.Severity.BULK_WARNING;
270
- this.selector = selector || Rule.DEFAULT_SELECTOR;
271
- this.pattern = pattern || null;
272
- if (typeof lint === "function") {
273
- this.lint = lint;
274
- this.message = null;
275
- } else {
276
- this.lint = (...args) => this._defaultLintFunction(...args);
277
- this.message = lint;
278
- }
279
- this.applies = applies || function () {
280
- return true;
281
- };
282
- }
283
- static makeRule(options) {
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);
285
- }
286
- check(node, traversalState, content, context) {
287
- const selectorMatch = this.selector.match(traversalState);
288
- if (!selectorMatch) {
289
- return null;
290
- }
291
- let patternMatch;
292
- if (this.pattern) {
293
- patternMatch = content.match(this.pattern);
294
- } else {
295
- patternMatch = Rule.FakePatternMatch(content, content, 0);
296
- }
297
- if (!patternMatch) {
298
- return null;
299
- }
300
- try {
301
- const error = this.lint(traversalState, content, selectorMatch, patternMatch, context);
302
- if (!error) {
303
- return null;
304
- }
305
- if (typeof error === "string") {
306
- return {
307
- rule: this.name,
308
- severity: this.severity,
309
- message: error,
310
- start: 0,
311
- end: content.length
312
- };
313
- }
314
- return {
315
- rule: this.name,
316
- severity: this.severity,
317
- message: error.message,
318
- start: error.start,
319
- end: error.end
320
- };
321
- } catch (e) {
322
- return {
323
- rule: "lint-rule-failure",
324
- message: `Exception in rule ${this.name}: ${e.message}
5
+ var perseusCore = require('@khanacademy/perseus-core');
6
+ var perseusUtils = require('@khanacademy/perseus-utils');
7
+ var PropTypes = require('prop-types');
8
+
9
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
10
+
11
+ var PropTypes__default = /*#__PURE__*/_interopDefaultCompat(PropTypes);
12
+
13
+ class Selector{static parse(selectorText){return new Parser(selectorText).parse()}match(state){throw new perseusCore.PerseusError("Selector subclasses must implement match()",perseusCore.Errors.NotAllowed)}toString(){return "Unknown selector class"}}class Parser{nextToken(){return this.tokens[this.tokenIndex]||""}consume(){this.tokenIndex++;}isIdentifier(){const c=this.tokens[this.tokenIndex][0];return c>="a"&&c<="z"||c>="A"&&c<="Z"}skipSpace(){while(this.nextToken()===" "){this.consume();}}parse(){const ts=this.parseTreeSelector();let token=this.nextToken();if(!token){return ts}const treeSelectors=[ts];while(token){if(token===","){this.consume();}else {throw new ParseError("Expected comma")}treeSelectors.push(this.parseTreeSelector());token=this.nextToken();}return new SelectorList(treeSelectors)}parseTreeSelector(){this.skipSpace();let ns=this.parseNodeSelector();for(;;){const token=this.nextToken();if(!token||token===","){break}else if(token===" "){this.consume();ns=new AncestorCombinator(ns,this.parseNodeSelector());}else if(token===">"){this.consume();ns=new ParentCombinator(ns,this.parseNodeSelector());}else if(token==="+"){this.consume();ns=new PreviousCombinator(ns,this.parseNodeSelector());}else if(token==="~"){this.consume();ns=new SiblingCombinator(ns,this.parseNodeSelector());}else {throw new ParseError("Unexpected token: "+token)}}return ns}parseNodeSelector(){this.skipSpace();const t=this.nextToken();if(t==="*"){this.consume();return new AnyNode}if(this.isIdentifier()){this.consume();return new TypeSelector(t)}throw new ParseError("Expected node type")}constructor(s){s=s.trim().replace(/\s+/g," ");this.tokens=s.match(Parser.TOKENS)||[];this.tokenIndex=0;}}Parser.TOKENS=/([a-zA-Z][\w-]*)|(\d+)|[^\s]|(\s(?=[a-zA-Z\*]))/g;class ParseError extends Error{constructor(message){super(message);}}class SelectorList extends Selector{match(state){for(let i=0;i<this.selectors.length;i++){const s=this.selectors[i];const result=s.match(state);if(result){return result}}return null}toString(){let result="";for(let i=0;i<this.selectors.length;i++){result+=i>0?", ":"";result+=this.selectors[i].toString();}return result}constructor(selectors){super();this.selectors=selectors;}}class AnyNode extends Selector{match(state){return [state.currentNode()]}toString(){return "*"}}class TypeSelector extends Selector{match(state){const node=state.currentNode();if(node.type===this.type){return [node]}return null}toString(){return this.type}constructor(type){super();this.type=type;}}class SelectorCombinator extends Selector{constructor(left,right){super();this.left=left;this.right=right;}}class AncestorCombinator extends SelectorCombinator{match(state){const rightResult=this.right.match(state);if(rightResult){state=state.clone();while(state.hasParent()){state.goToParent();const leftResult=this.left.match(state);if(leftResult){return leftResult.concat(rightResult)}}}return null}toString(){return this.left.toString()+" "+this.right.toString()}constructor(left,right){super(left,right);}}class ParentCombinator extends SelectorCombinator{match(state){const rightResult=this.right.match(state);if(rightResult){if(state.hasParent()){state=state.clone();state.goToParent();const leftResult=this.left.match(state);if(leftResult){return leftResult.concat(rightResult)}}}return null}toString(){return this.left.toString()+" > "+this.right.toString()}constructor(left,right){super(left,right);}}class PreviousCombinator extends SelectorCombinator{match(state){const rightResult=this.right.match(state);if(rightResult){if(state.hasPreviousSibling()){state=state.clone();state.goToPreviousSibling();const leftResult=this.left.match(state);if(leftResult){return leftResult.concat(rightResult)}}}return null}toString(){return this.left.toString()+" + "+this.right.toString()}constructor(left,right){super(left,right);}}class SiblingCombinator extends SelectorCombinator{match(state){const rightResult=this.right.match(state);if(rightResult){state=state.clone();while(state.hasPreviousSibling()){state.goToPreviousSibling();const leftResult=this.left.match(state);if(leftResult){return leftResult.concat(rightResult)}}}return null}toString(){return this.left.toString()+" ~ "+this.right.toString()}constructor(left,right){super(left,right);}}
14
+
15
+ class Rule{static makeRule(options){return new Rule(options.name,options.severity,options.selector?Selector.parse(options.selector):null,Rule.makePattern(options.pattern),options.lint||options.message,options.applies)}check(node,traversalState,content,context){const selectorMatch=this.selector.match(traversalState);if(!selectorMatch){return null}let patternMatch;if(this.pattern){patternMatch=content.match(this.pattern);}else {patternMatch=Rule.FakePatternMatch(content,content,0);}if(!patternMatch){return null}try{const error=this.lint(traversalState,content,selectorMatch,patternMatch,context);if(!error){return null}if(typeof error==="string"){return {rule:this.name,severity:this.severity,message:error,start:0,end:content.length}}return {rule:this.name,severity:this.severity,message:error.message,start:error.start,end:error.end}}catch(e){return {rule:"lint-rule-failure",message:`Exception in rule ${this.name}: ${e.message}
325
16
  Stack trace:
326
- ${e.stack}`,
327
- start: 0,
328
- end: content.length
329
- };
330
- }
331
- }
332
- _defaultLintFunction(state, content, selectorMatch, patternMatch, context) {
333
- return {
334
- message: this.message || "",
335
- start: patternMatch.index,
336
- end: patternMatch.index + patternMatch[0].length
337
- };
338
- }
339
- static makePattern(pattern) {
340
- if (!pattern) {
341
- return null;
342
- }
343
- if (pattern instanceof RegExp) {
344
- return pattern;
345
- }
346
- if (pattern[0] === "/") {
347
- const lastSlash = pattern.lastIndexOf("/");
348
- const expression = pattern.substring(1, lastSlash);
349
- const flags = pattern.substring(lastSlash + 1);
350
- return new RegExp(expression, flags);
351
- }
352
- return new RegExp(pattern);
353
- }
354
- static FakePatternMatch(input, match, index) {
355
- const result = [match];
356
- result.index = index;
357
- result.input = input;
358
- return result;
359
- }
360
- }
361
- Rule.DEFAULT_SELECTOR = void 0;
362
- Rule.Severity = {
363
- ERROR: 1,
364
- WARNING: 2,
365
- GUIDELINE: 3,
366
- BULK_WARNING: 4
367
- };
368
- Rule.DEFAULT_SELECTOR = Selector.parse("text");
17
+ ${e.stack}`,start:0,end:content.length}}}_defaultLintFunction(state,content,selectorMatch,patternMatch,context){return {message:this.message||"",start:patternMatch.index,end:patternMatch.index+patternMatch[0].length}}static makePattern(pattern){if(!pattern){return null}if(pattern instanceof RegExp){return pattern}if(pattern[0]==="/"){const lastSlash=pattern.lastIndexOf("/");const expression=pattern.substring(1,lastSlash);const flags=pattern.substring(lastSlash+1);return new RegExp(expression,flags)}return new RegExp(pattern)}static FakePatternMatch(input,match,index){const result=[match];result.index=index;result.input=input;return result}constructor(name,severity,selector,pattern,lint,applies){if(!selector&&!pattern){throw new perseusCore.PerseusError("Lint rules must have a selector or pattern",perseusCore.Errors.InvalidInput,{metadata:{name}})}this.name=name||"unnamed rule";this.severity=severity||Rule.Severity.BULK_WARNING;this.selector=selector||Rule.DEFAULT_SELECTOR;this.pattern=pattern||null;if(typeof lint==="function"){this.lint=lint;this.message=null;}else {this.lint=(...args)=>this._defaultLintFunction(...args);this.message=lint;}this.applies=applies||function(){return true};}}Rule.Severity={ERROR:1,WARNING:2,GUIDELINE:3,BULK_WARNING:4};Rule.DEFAULT_SELECTOR=Selector.parse("text");
369
18
 
370
- const HOSTNAME = /\/\/([^\/]+)/;
371
- function getHostname(url) {
372
- if (!url) {
373
- return "";
374
- }
375
- const match = url.match(HOSTNAME);
376
- return match ? match[1] : "";
377
- }
19
+ const HOSTNAME=/\/\/([^\/]+)/;function getHostname(url){if(!url){return ""}const match=url.match(HOSTNAME);return match?match[1]:""}
378
20
 
379
- var AbsoluteUrl = Rule.makeRule({
380
- name: "absolute-url",
381
- severity: Rule.Severity.GUIDELINE,
382
- selector: "link, image",
383
- lint: function (state, content, nodes, match) {
384
- const url = nodes[0].target;
385
- const hostname = getHostname(url);
386
- if (hostname === "khanacademy.org" || hostname.endsWith(".khanacademy.org")) {
387
- return `Don't use absolute URLs:
21
+ var AbsoluteUrl = Rule.makeRule({name:"absolute-url",severity:Rule.Severity.GUIDELINE,selector:"link, image",lint:function(state,content,nodes,match){const url=nodes[0].target;const hostname=getHostname(url);if(hostname==="khanacademy.org"||hostname.endsWith(".khanacademy.org")){return `Don't use absolute URLs:
388
22
  When linking to KA content or images, omit the
389
23
  https://www.khanacademy.org URL prefix.
390
- Use a relative URL beginning with / instead.`;
391
- }
392
- }
393
- });
24
+ Use a relative URL beginning with / instead.`}}});
394
25
 
395
- var BlockquotedMath = Rule.makeRule({
396
- name: "blockquoted-math",
397
- severity: Rule.Severity.WARNING,
398
- selector: "blockQuote math, blockQuote blockMath",
399
- message: `Blockquoted math:
400
- math should not be indented.`
401
- });
26
+ var BlockquotedMath = Rule.makeRule({name:"blockquoted-math",severity:Rule.Severity.WARNING,selector:"blockQuote math, blockQuote blockMath",message:`Blockquoted math:
27
+ math should not be indented.`});
402
28
 
403
- var BlockquotedWidget = Rule.makeRule({
404
- name: "blockquoted-widget",
405
- severity: Rule.Severity.WARNING,
406
- selector: "blockQuote widget",
407
- message: `Blockquoted widget:
408
- widgets should not be indented.`
409
- });
29
+ var BlockquotedWidget = Rule.makeRule({name:"blockquoted-widget",severity:Rule.Severity.WARNING,selector:"blockQuote widget",message:`Blockquoted widget:
30
+ widgets should not be indented.`});
410
31
 
411
- var DoubleSpacingAfterTerminal = Rule.makeRule({
412
- name: "double-spacing-after-terminal",
413
- severity: Rule.Severity.BULK_WARNING,
414
- selector: "paragraph",
415
- pattern: /[.!\?] {2}/i,
416
- message: `Use a single space after a sentence-ending period, or
417
- any other kind of terminal punctuation.`
418
- });
32
+ var DoubleSpacingAfterTerminal = Rule.makeRule({name:"double-spacing-after-terminal",severity:Rule.Severity.BULK_WARNING,selector:"paragraph",pattern:/[.!\?] {2}/i,message:`Use a single space after a sentence-ending period, or
33
+ any other kind of terminal punctuation.`});
419
34
 
420
- function buttonNotInButtonSet(name, set) {
421
- return `Answer requires a button not found in the button sets: ${name} (in ${set})`;
422
- }
423
- const stringToButtonSet = {
424
- "\\sqrt": "prealgebra",
425
- "\\sin": "trig",
426
- "\\cos": "trig",
427
- "\\tan": "trig",
428
- "\\log": "logarithms",
429
- "\\ln": "logarithms"
430
- };
431
- var ExpressionWidget = Rule.makeRule({
432
- name: "expression-widget",
433
- severity: Rule.Severity.WARNING,
434
- selector: "widget",
435
- lint: function (state, content, nodes, match, context) {
436
- var _context$widgets;
437
- if (state.currentNode().widgetType !== "expression") {
438
- return;
439
- }
440
- const nodeId = state.currentNode().id;
441
- if (!nodeId) {
442
- return;
443
- }
444
- const widget = context == null || (_context$widgets = context.widgets) == null ? void 0 : _context$widgets[nodeId];
445
- if (!widget) {
446
- return;
447
- }
448
- const answers = widget.options.answerForms;
449
- const buttons = widget.options.buttonSets;
450
- for (const answer of answers) {
451
- for (const [str, set] of Object.entries(stringToButtonSet)) {
452
- if (answer.value.includes(str) && !buttons.includes(set)) {
453
- return buttonNotInButtonSet(str, set);
454
- }
455
- }
456
- }
457
- }
458
- });
35
+ function buttonNotInButtonSet(name,set){return `Answer requires a button not found in the button sets: ${name} (in ${set})`}const stringToButtonSet={"\\sqrt":"prealgebra","\\sin":"trig","\\cos":"trig","\\tan":"trig","\\log":"logarithms","\\ln":"logarithms"};var ExpressionWidget = Rule.makeRule({name:"expression-widget",severity:Rule.Severity.WARNING,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="expression"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context?.widgets?.[nodeId];if(!widget){return}const answers=widget.options.answerForms;const buttons=widget.options.buttonSets;for(const answer of answers){for(const[str,set]of Object.entries(stringToButtonSet)){if(answer.value.includes(str)&&!buttons.includes(set)){return buttonNotInButtonSet(str,set)}}}}});
459
36
 
460
- var ExtraContentSpacing = Rule.makeRule({
461
- name: "extra-content-spacing",
462
- selector: "paragraph",
463
- pattern: /\s+$/,
464
- applies: function (context) {
465
- return (context == null ? void 0 : context.contentType) === "article";
466
- },
467
- message: `No extra whitespace at the end of content blocks.`
468
- });
37
+ var ExtraContentSpacing = Rule.makeRule({name:"extra-content-spacing",selector:"paragraph",pattern:/\s+$/,applies:function(context){return context?.contentType==="article"},message:`No extra whitespace at the end of content blocks.`});
469
38
 
470
- var HeadingLevel1 = Rule.makeRule({
471
- name: "heading-level-1",
472
- severity: Rule.Severity.WARNING,
473
- selector: "heading",
474
- lint: function (state, content, nodes, match) {
475
- if (nodes[0].level === 1) {
476
- return `Don't use level-1 headings:
477
- Begin headings with two or more # characters.`;
478
- }
479
- }
480
- });
39
+ var HeadingLevel1 = Rule.makeRule({name:"heading-level-1",severity:Rule.Severity.WARNING,selector:"heading",lint:function(state,content,nodes,match){if(nodes[0].level===1){return `Don't use level-1 headings:
40
+ Begin headings with two or more # characters.`}}});
481
41
 
482
- var HeadingLevelSkip = Rule.makeRule({
483
- name: "heading-level-skip",
484
- severity: Rule.Severity.WARNING,
485
- selector: "heading ~ heading",
486
- lint: function (state, content, nodes, match) {
487
- const currentHeading = nodes[1];
488
- const previousHeading = nodes[0];
489
- if (currentHeading.level > previousHeading.level + 1) {
490
- return `Skipped heading level:
42
+ var HeadingLevelSkip = Rule.makeRule({name:"heading-level-skip",severity:Rule.Severity.WARNING,selector:"heading ~ heading",lint:function(state,content,nodes,match){const currentHeading=nodes[1];const previousHeading=nodes[0];if(currentHeading.level>previousHeading.level+1){return `Skipped heading level:
491
43
  this heading is level ${currentHeading.level} but
492
- the previous heading was level ${previousHeading.level}`;
493
- }
494
- }
495
- });
44
+ the previous heading was level ${previousHeading.level}`}}});
496
45
 
497
- var HeadingSentenceCase = Rule.makeRule({
498
- name: "heading-sentence-case",
499
- severity: Rule.Severity.GUIDELINE,
500
- selector: "heading",
501
- pattern: /^\W*[a-z]/,
502
- message: `First letter is lowercase:
503
- the first letter of a heading should be capitalized.`
504
- });
46
+ var HeadingSentenceCase = Rule.makeRule({name:"heading-sentence-case",severity:Rule.Severity.GUIDELINE,selector:"heading",pattern:/^\W*[a-z]/,message:`First letter is lowercase:
47
+ the first letter of a heading should be capitalized.`});
505
48
 
506
- const littleWords = {
507
- and: true,
508
- nor: true,
509
- but: true,
510
- the: true,
511
- for: true
512
- };
513
- function isCapitalized(word) {
514
- const c = word[0];
515
- return c === c.toUpperCase();
516
- }
517
- var HeadingTitleCase = Rule.makeRule({
518
- name: "heading-title-case",
519
- severity: Rule.Severity.GUIDELINE,
520
- selector: "heading",
521
- pattern: /[^\s:]\s+[A-Z]+[a-z]/,
522
- locale: "en",
523
- lint: function (state, content, nodes, match) {
524
- const heading = content.trim();
525
- let words = heading.split(/\s+/);
526
- words.shift();
527
- words = words.filter(w => w.length > 2 && !littleWords.hasOwnProperty(w));
528
- if (words.length >= 3 && words.every(w => isCapitalized(w))) {
529
- return `Title-case heading:
49
+ const littleWords={and:true,nor:true,but:true,the:true,for:true};function isCapitalized(word){const c=word[0];return c===c.toUpperCase()}var HeadingTitleCase = Rule.makeRule({name:"heading-title-case",severity:Rule.Severity.GUIDELINE,selector:"heading",pattern:/[^\s:]\s+[A-Z]+[a-z]/,locale:"en",lint:function(state,content,nodes,match){const heading=content.trim();let words=heading.split(/\s+/);words.shift();words=words.filter(w=>w.length>2&&!littleWords.hasOwnProperty(w));if(words.length>=3&&words.every(w=>isCapitalized(w))){return `Title-case heading:
530
50
  This heading appears to be in title-case, but should be sentence-case.
531
- Only capitalize the first letter and proper nouns.`;
532
- }
533
- }
534
- });
51
+ Only capitalize the first letter and proper nouns.`}}});
535
52
 
536
- var ImageAltText = Rule.makeRule({
537
- name: "image-alt-text",
538
- severity: Rule.Severity.WARNING,
539
- selector: "image",
540
- lint: function (state, content, nodes, match) {
541
- const image = nodes[0];
542
- if (!image.alt || !image.alt.trim()) {
543
- return `Images should have alt text:
53
+ var ImageAltText = Rule.makeRule({name:"image-alt-text",severity:Rule.Severity.WARNING,selector:"image",lint:function(state,content,nodes,match){const image=nodes[0];if(!image.alt||!image.alt.trim()){return `Images should have alt text:
544
54
  for accessibility, all images should have alt text.
545
- Specify alt text inside square brackets after the !.`;
546
- }
547
- if (image.alt.length < 8) {
548
- return `Images should have alt text:
55
+ Specify alt text inside square brackets after the !.`}if(image.alt.length<8){return `Images should have alt text:
549
56
  for accessibility, all images should have descriptive alt text.
550
- This image's alt text is only ${image.alt.length} characters long.`;
551
- }
552
- }
553
- });
57
+ This image's alt text is only ${image.alt.length} characters long.`}}});
554
58
 
555
- var ImageInTable = Rule.makeRule({
556
- name: "image-in-table",
557
- severity: Rule.Severity.BULK_WARNING,
558
- selector: "table image",
559
- message: `Image in table:
560
- do not put images inside of tables.`
561
- });
59
+ var ImageInTable = Rule.makeRule({name:"image-in-table",severity:Rule.Severity.BULK_WARNING,selector:"table image",message:`Image in table:
60
+ do not put images inside of tables.`});
562
61
 
563
- var ImageSpacesAroundUrls = Rule.makeRule({
564
- name: "image-spaces-around-urls",
565
- severity: Rule.Severity.ERROR,
566
- selector: "image",
567
- lint: function (state, content, nodes, match, context) {
568
- const image = nodes[0];
569
- const url = image.target;
570
- if (context && context.content) {
571
- const index = context.content.indexOf(url);
572
- if (index === -1) {
573
- return;
574
- }
575
- if (context.content[index - 1] !== "(" || context.content[index + url.length] !== ")") {
576
- return `Whitespace before or after image url:
62
+ var ImageSpacesAroundUrls = Rule.makeRule({name:"image-spaces-around-urls",severity:Rule.Severity.ERROR,selector:"image",lint:function(state,content,nodes,match,context){const image=nodes[0];const url=image.target;if(context&&context.content){const index=context.content.indexOf(url);if(index===-1){return}if(context.content[index-1]!=="("||context.content[index+url.length]!==")"){return `Whitespace before or after image url:
577
63
  For images, don't include any space or newlines after '(' or before ')'.
578
- Whitespace in image URLs causes translation difficulties.`;
579
- }
580
- }
581
- }
582
- });
64
+ Whitespace in image URLs causes translation difficulties.`}}}});
583
65
 
584
- var ImageUrlEmpty = Rule.makeRule({
585
- name: "image-url-empty",
586
- severity: Rule.Severity.ERROR,
587
- selector: "image",
588
- lint: function (state, content, nodes) {
589
- const image = nodes[0];
590
- const url = image.target;
591
- if (!url || !url.trim()) {
592
- return "Images should have a URL";
593
- }
594
- }
595
- });
66
+ var ImageUrlEmpty = Rule.makeRule({name:"image-url-empty",severity:Rule.Severity.ERROR,selector:"image",lint:function(state,content,nodes){const image=nodes[0];const url=image.target;if(!url||!url.trim()){return "Images should have a URL"}}});
596
67
 
597
- var ImageWidget = Rule.makeRule({
598
- name: "image-widget",
599
- severity: Rule.Severity.WARNING,
600
- selector: "widget",
601
- lint: function (state, content, nodes, match, context) {
602
- if (state.currentNode().widgetType !== "image") {
603
- return;
604
- }
605
- const nodeId = state.currentNode().id;
606
- if (!nodeId) {
607
- return;
608
- }
609
- const widget = context && context.widgets && context.widgets[nodeId];
610
- if (!widget) {
611
- return;
612
- }
613
- const alt = widget.options.alt;
614
- if (!alt) {
615
- return `Images should have alt text:
68
+ var ImageWidget = Rule.makeRule({name:"image-widget",severity:Rule.Severity.WARNING,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="image"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context&&context.widgets&&context.widgets[nodeId];if(!widget){return}const alt=widget.options.alt;if(!alt){return `Images should have alt text:
616
69
  for accessibility, all images should have a text description.
617
- Add a description in the "Alt Text" box of the image widget.`;
618
- }
619
- if (alt.trim().length < 8) {
620
- return `Images should have alt text:
70
+ Add a description in the "Alt Text" box of the image widget.`}if(alt.trim().length<8){return `Images should have alt text:
621
71
  for accessibility, all images should have descriptive alt text.
622
- This image's alt text is only ${alt.trim().length} characters long.`;
623
- }
624
- if (widget.options.caption && widget.options.caption.match(/[^\\]\$/)) {
625
- return `No math in image captions:
626
- Don't include math expressions in image captions.`;
627
- }
628
- }
629
- });
72
+ This image's alt text is only ${alt.trim().length} characters long.`}if(widget.options.caption&&widget.options.caption.match(/[^\\]\$/)){return `No math in image captions:
73
+ Don't include math expressions in image captions.`}}});
630
74
 
631
- var LinkClickHere = Rule.makeRule({
632
- name: "link-click-here",
633
- severity: Rule.Severity.WARNING,
634
- selector: "link",
635
- pattern: /click here/i,
636
- message: `Inappropriate link text:
637
- Do not use the words "click here" in links.`
638
- });
75
+ var LinkClickHere = Rule.makeRule({name:"link-click-here",severity:Rule.Severity.WARNING,selector:"link",pattern:/click here/i,message:`Inappropriate link text:
76
+ Do not use the words "click here" in links.`});
639
77
 
640
- var LongParagraph = Rule.makeRule({
641
- name: "long-paragraph",
642
- severity: Rule.Severity.GUIDELINE,
643
- selector: "paragraph",
644
- pattern: /^.{501,}/,
645
- lint: function (state, content, nodes, match) {
646
- return `Paragraph too long:
78
+ var LongParagraph = Rule.makeRule({name:"long-paragraph",severity:Rule.Severity.GUIDELINE,selector:"paragraph",pattern:/^.{501,}/,lint:function(state,content,nodes,match){return `Paragraph too long:
647
79
  This paragraph is ${content.length} characters long.
648
- Shorten it to 500 characters or fewer.`;
649
- }
650
- });
80
+ Shorten it to 500 characters or fewer.`}});
651
81
 
652
- var MathAdjacent = Rule.makeRule({
653
- name: "math-adjacent",
654
- severity: Rule.Severity.WARNING,
655
- selector: "blockMath+blockMath",
656
- message: `Adjacent math blocks:
657
- combine the blocks between \\begin{align} and \\end{align}`
658
- });
82
+ var MathAdjacent = Rule.makeRule({name:"math-adjacent",severity:Rule.Severity.WARNING,selector:"blockMath+blockMath",message:`Adjacent math blocks:
83
+ combine the blocks between \\begin{align} and \\end{align}`});
659
84
 
660
- var MathAlignExtraBreak = Rule.makeRule({
661
- name: "math-align-extra-break",
662
- severity: Rule.Severity.WARNING,
663
- selector: "blockMath",
664
- pattern: /(\\{2,})\s*\\end{align}/,
665
- message: `Extra space at end of block:
666
- Don't end an align block with backslashes`
667
- });
85
+ var MathAlignExtraBreak = Rule.makeRule({name:"math-align-extra-break",severity:Rule.Severity.WARNING,selector:"blockMath",pattern:/(\\{2,})\s*\\end{align}/,message:`Extra space at end of block:
86
+ Don't end an align block with backslashes`});
668
87
 
669
- var MathAlignLinebreaks = Rule.makeRule({
670
- name: "math-align-linebreaks",
671
- severity: Rule.Severity.WARNING,
672
- selector: "blockMath",
673
- pattern: /\\begin{align}[\s\S]*\\\\[\s\S]+\\end{align}/,
674
- lint: function (state, content, nodes, match) {
675
- let text = match[0];
676
- while (text.length) {
677
- const index = text.indexOf("\\\\");
678
- if (index === -1) {
679
- return;
680
- }
681
- text = text.substring(index + 2);
682
- const nextpair = text.match(/^\s*\\\\\s*(?!\\\\)/);
683
- if (!nextpair) {
684
- return "Use four backslashes between lines of an align block";
685
- }
686
- text = text.substring(nextpair[0].length);
687
- }
688
- }
689
- });
88
+ var MathAlignLinebreaks = Rule.makeRule({name:"math-align-linebreaks",severity:Rule.Severity.WARNING,selector:"blockMath",pattern:/\\begin{align}[\s\S]*\\\\[\s\S]+\\end{align}/,lint:function(state,content,nodes,match){let text=match[0];while(text.length){const index=text.indexOf("\\\\");if(index===-1){return}text=text.substring(index+2);const nextpair=text.match(/^\s*\\\\\s*(?!\\\\)/);if(!nextpair){return "Use four backslashes between lines of an align block"}text=text.substring(nextpair[0].length);}}});
690
89
 
691
- var MathEmpty = Rule.makeRule({
692
- name: "math-empty",
693
- severity: Rule.Severity.WARNING,
694
- selector: "math, blockMath",
695
- pattern: /^$/,
696
- message: "Empty math: don't use $$ in your markdown."
697
- });
90
+ var MathEmpty = Rule.makeRule({name:"math-empty",severity:Rule.Severity.WARNING,selector:"math, blockMath",pattern:/^$/,message:"Empty math: don't use $$ in your markdown."});
698
91
 
699
- var MathFrac = Rule.makeRule({
700
- name: "math-frac",
701
- severity: Rule.Severity.GUIDELINE,
702
- selector: "math, blockMath",
703
- pattern: /\\frac[ {]/,
704
- message: "Use \\dfrac instead of \\frac in your math expressions."
705
- });
92
+ var MathFrac = Rule.makeRule({name:"math-frac",severity:Rule.Severity.GUIDELINE,selector:"math, blockMath",pattern:/\\frac[ {]/,message:"Use \\dfrac instead of \\frac in your math expressions."});
706
93
 
707
- var MathNested = Rule.makeRule({
708
- name: "math-nested",
709
- severity: Rule.Severity.ERROR,
710
- selector: "math, blockMath",
711
- pattern: /\\text{[^$}]*\$[^$}]*\$[^}]*}/,
712
- message: `Nested math:
713
- Don't nest math expressions inside \\text{} blocks`
714
- });
94
+ var MathNested = Rule.makeRule({name:"math-nested",severity:Rule.Severity.ERROR,selector:"math, blockMath",pattern:/\\text{[^$}]*\$[^$}]*\$[^}]*}/,message:`Nested math:
95
+ Don't nest math expressions inside \\text{} blocks`});
715
96
 
716
- var MathStartsWithSpace = Rule.makeRule({
717
- name: "math-starts-with-space",
718
- severity: Rule.Severity.GUIDELINE,
719
- selector: "math, blockMath",
720
- pattern: /^\s*(~|\\qquad|\\quad|\\,|\\;|\\:|\\ |\\!|\\enspace|\\phantom)/,
721
- message: `Math starts with space:
97
+ var MathStartsWithSpace = Rule.makeRule({name:"math-starts-with-space",severity:Rule.Severity.GUIDELINE,selector:"math, blockMath",pattern:/^\s*(~|\\qquad|\\quad|\\,|\\;|\\:|\\ |\\!|\\enspace|\\phantom)/,message:`Math starts with space:
722
98
  math should not be indented. Do not begin math expressions with
723
- LaTeX space commands like ~, \\;, \\quad, or \\phantom`
724
- });
99
+ LaTeX space commands like ~, \\;, \\quad, or \\phantom`});
725
100
 
726
- var MathTextEmpty = Rule.makeRule({
727
- name: "math-text-empty",
728
- severity: Rule.Severity.WARNING,
729
- selector: "math, blockMath",
730
- pattern: /\\text{\s*}/,
731
- message: "Empty \\text{} block in math expression"
732
- });
101
+ var MathTextEmpty = Rule.makeRule({name:"math-text-empty",severity:Rule.Severity.WARNING,selector:"math, blockMath",pattern:/\\text{\s*}/,message:"Empty \\text{} block in math expression"});
733
102
 
734
- var MathWithoutDollars = Rule.makeRule({
735
- name: "math-without-dollars",
736
- severity: Rule.Severity.GUIDELINE,
737
- pattern: /\\\w+{[^}]*}|{|}/,
738
- message: `This looks like LaTeX:
739
- did you mean to put it inside dollar signs?`
740
- });
103
+ var MathWithoutDollars = Rule.makeRule({name:"math-without-dollars",severity:Rule.Severity.GUIDELINE,pattern:/\\\w+{[^}]*}|{|}/,message:`This looks like LaTeX:
104
+ did you mean to put it inside dollar signs?`});
741
105
 
742
- var NestedLists = Rule.makeRule({
743
- name: "nested-lists",
744
- severity: Rule.Severity.WARNING,
745
- selector: "list list",
746
- message: `Nested lists:
106
+ var NestedLists = Rule.makeRule({name:"nested-lists",severity:Rule.Severity.WARNING,selector:"list list",message:`Nested lists:
747
107
  nested lists are hard to read on mobile devices;
748
- do not use additional indentation.`
749
- });
108
+ do not use additional indentation.`});
750
109
 
751
- var StaticWidgetInQuestionStem = Rule.makeRule({
752
- name: "static-widget-in-question-stem",
753
- severity: Rule.Severity.WARNING,
754
- selector: "widget",
755
- lint: (state, content, nodes, match, context) => {
756
- var _context$widgets;
757
- if ((context == null ? void 0 : context.contentType) !== "exercise") {
758
- return;
759
- }
760
- if (context.stack.includes("hint")) {
761
- return;
762
- }
763
- const nodeId = state.currentNode().id;
764
- if (!nodeId) {
765
- return;
766
- }
767
- const widget = context == null || (_context$widgets = context.widgets) == null ? void 0 : _context$widgets[nodeId];
768
- if (!widget) {
769
- return;
770
- }
771
- if (widget.static) {
772
- return `Widget in question stem is static (non-interactive).`;
773
- }
774
- }
775
- });
110
+ var StaticWidgetInQuestionStem = Rule.makeRule({name:"static-widget-in-question-stem",severity:Rule.Severity.WARNING,selector:"widget",lint:(state,content,nodes,match,context)=>{if(context?.contentType!=="exercise"){return}if(context.stack.includes("hint")){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context?.widgets?.[nodeId];if(!widget){return}if(widget.static){return `Widget in question stem is static (non-interactive).`}}});
776
111
 
777
- var TableMissingCells = Rule.makeRule({
778
- name: "table-missing-cells",
779
- severity: Rule.Severity.WARNING,
780
- selector: "table",
781
- lint: function (state, content, nodes, match) {
782
- const table = nodes[0];
783
- const headerLength = table.header.length;
784
- const rowLengths = table.cells.map(r => r.length);
785
- for (let r = 0; r < rowLengths.length; r++) {
786
- if (rowLengths[r] !== headerLength) {
787
- return `Table rows don't match header:
112
+ var TableMissingCells = Rule.makeRule({name:"table-missing-cells",severity:Rule.Severity.WARNING,selector:"table",lint:function(state,content,nodes,match){const table=nodes[0];const headerLength=table.header.length;const rowLengths=table.cells.map(r=>r.length);for(let r=0;r<rowLengths.length;r++){if(rowLengths[r]!==headerLength){return `Table rows don't match header:
788
113
  The table header has ${headerLength} cells, but
789
- Row ${r + 1} has ${rowLengths[r]} cells.`;
790
- }
791
- }
792
- }
793
- });
114
+ Row ${r+1} has ${rowLengths[r]} cells.`}}}});
794
115
 
795
- var UnbalancedCodeDelimiters = Rule.makeRule({
796
- name: "unbalanced-code-delimiters",
797
- severity: Rule.Severity.ERROR,
798
- pattern: /[`~]+/,
799
- message: `Unbalanced code delimiters:
800
- code blocks should begin and end with the same type and number of delimiters`
801
- });
116
+ var UnbalancedCodeDelimiters = Rule.makeRule({name:"unbalanced-code-delimiters",severity:Rule.Severity.ERROR,pattern:/[`~]+/,message:`Unbalanced code delimiters:
117
+ code blocks should begin and end with the same type and number of delimiters`});
802
118
 
803
- var UnescapedDollar = Rule.makeRule({
804
- name: "unescaped-dollar",
805
- severity: Rule.Severity.ERROR,
806
- selector: "unescapedDollar",
807
- message: `Unescaped dollar sign:
808
- Dollar signs must appear in pairs or be escaped as \\$`
809
- });
119
+ var UnescapedDollar = Rule.makeRule({name:"unescaped-dollar",severity:Rule.Severity.ERROR,selector:"unescapedDollar",message:`Unescaped dollar sign:
120
+ Dollar signs must appear in pairs or be escaped as \\$`});
810
121
 
811
- var WidgetInTable = Rule.makeRule({
812
- name: "widget-in-table",
813
- severity: Rule.Severity.BULK_WARNING,
814
- selector: "table widget",
815
- message: `Widget in table:
816
- do not put widgets inside of tables.`
817
- });
122
+ var WidgetInTable = Rule.makeRule({name:"widget-in-table",severity:Rule.Severity.BULK_WARNING,selector:"table widget",message:`Widget in table:
123
+ do not put widgets inside of tables.`});
818
124
 
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];
125
+ 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];
820
126
 
821
- class TreeTransformer {
822
- constructor(root) {
823
- this.root = void 0;
824
- this.root = root;
825
- }
826
- static isNode(n) {
827
- return n && typeof n === "object" && typeof n.type === "string";
828
- }
829
- static isTextNode(n) {
830
- return TreeTransformer.isNode(n) && n.type === "text" && typeof n.content === "string";
831
- }
832
- traverse(f) {
833
- this._traverse(this.root, new TraversalState(this.root), f);
834
- }
835
- _traverse(n, state, f) {
836
- let content = "";
837
- if (TreeTransformer.isNode(n)) {
838
- const node = n;
839
- state._containers.push(node);
840
- state._ancestors.push(node);
841
- if (typeof node.content === "string") {
842
- content = node.content;
843
- }
844
- const keys = Object.keys(node);
845
- keys.forEach(key => {
846
- if (key === "type") {
847
- return;
848
- }
849
- const value = node[key];
850
- if (value && typeof value === "object") {
851
- state._indexes.push(key);
852
- content += this._traverse(value, state, f);
853
- state._indexes.pop();
854
- }
855
- });
856
- state._currentNode = state._ancestors.pop();
857
- state._containers.pop();
858
- f(node, state, content);
859
- } else if (Array.isArray(n)) {
860
- const nodes = n;
861
- state._containers.push(nodes);
862
- let index = 0;
863
- while (index < nodes.length) {
864
- state._indexes.push(index);
865
- content += this._traverse(nodes[index], state, f);
866
- index = state._indexes.pop() + 1;
867
- }
868
- state._containers.pop();
869
- }
870
- return content;
871
- }
872
- }
873
- class TraversalState {
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;
880
- this.root = root;
881
- this._currentNode = null;
882
- this._containers = new Stack();
883
- this._indexes = new Stack();
884
- this._ancestors = new Stack();
885
- }
886
- currentNode() {
887
- return this._currentNode || this.root;
888
- }
889
- parent() {
890
- return this._ancestors.top();
891
- }
892
- ancestors() {
893
- return this._ancestors.values();
894
- }
895
- nextSibling() {
896
- const siblings = this._containers.top();
897
- if (!siblings || !Array.isArray(siblings)) {
898
- return null;
899
- }
900
- const index = this._indexes.top();
901
- if (siblings.length > index + 1) {
902
- return siblings[index + 1];
903
- }
904
- return null;
905
- }
906
- previousSibling() {
907
- const siblings = this._containers.top();
908
- if (!siblings || !Array.isArray(siblings)) {
909
- return null;
910
- }
911
- const index = this._indexes.top();
912
- if (index > 0) {
913
- return siblings[index - 1];
914
- }
915
- return null;
916
- }
917
- removeNextSibling() {
918
- const siblings = this._containers.top();
919
- if (siblings && Array.isArray(siblings)) {
920
- const index = this._indexes.top();
921
- if (siblings.length > index + 1) {
922
- return siblings.splice(index + 1, 1)[0];
923
- }
924
- }
925
- return null;
926
- }
927
- replace(...replacements) {
928
- const parent = this._containers.top();
929
- if (!parent) {
930
- throw new PerseusError("Can't replace the root of the tree", Errors.Internal);
931
- }
932
- if (Array.isArray(parent)) {
933
- const index = this._indexes.top();
934
- parent.splice(index, 1, ...replacements);
935
- this._indexes.pop();
936
- this._indexes.push(index + replacements.length - 1);
937
- } else {
938
- const property = this._indexes.top();
939
- if (replacements.length === 0) {
940
- delete parent[property];
941
- } else if (replacements.length === 1) {
942
- parent[property] = replacements[0];
943
- } else {
944
- parent[property] = replacements;
945
- }
946
- }
947
- }
948
- hasPreviousSibling() {
949
- return Array.isArray(this._containers.top()) && this._indexes.top() > 0;
950
- }
951
- goToPreviousSibling() {
952
- if (!this.hasPreviousSibling()) {
953
- throw new PerseusError("goToPreviousSibling(): node has no previous sibling", Errors.Internal);
954
- }
955
- this._currentNode = this.previousSibling();
956
- const index = this._indexes.pop();
957
- this._indexes.push(index - 1);
958
- }
959
- hasParent() {
960
- return this._ancestors.size() !== 0;
961
- }
962
- goToParent() {
963
- if (!this.hasParent()) {
964
- throw new PerseusError("goToParent(): node has no ancestor", Errors.NotAllowed);
965
- }
966
- this._currentNode = this._ancestors.pop();
967
- while (this._containers.size() && this._containers.top()[this._indexes.top()] !== this._currentNode) {
968
- this._containers.pop();
969
- this._indexes.pop();
970
- }
971
- }
972
- clone() {
973
- const clone = new TraversalState(this.root);
974
- clone._currentNode = this._currentNode;
975
- clone._containers = this._containers.clone();
976
- clone._indexes = this._indexes.clone();
977
- clone._ancestors = this._ancestors.clone();
978
- return clone;
979
- }
980
- equals(that) {
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);
982
- }
983
- }
984
- class Stack {
985
- constructor(array) {
986
- this.stack = void 0;
987
- this.stack = array ? array.slice(0) : [];
988
- }
989
- push(v) {
990
- this.stack.push(v);
991
- }
992
- pop() {
993
- return this.stack.pop();
994
- }
995
- top() {
996
- return this.stack[this.stack.length - 1];
997
- }
998
- values() {
999
- return this.stack.slice(0);
1000
- }
1001
- size() {
1002
- return this.stack.length;
1003
- }
1004
- toString() {
1005
- return this.stack.toString();
1006
- }
1007
- clone() {
1008
- return new Stack(this.stack);
1009
- }
1010
- equals(that) {
1011
- if (!that || !that.stack || that.stack.length !== this.stack.length) {
1012
- return false;
1013
- }
1014
- for (let i = 0; i < this.stack.length; i++) {
1015
- if (this.stack[i] !== that.stack[i]) {
1016
- return false;
1017
- }
1018
- }
1019
- return true;
1020
- }
1021
- }
127
+ class TreeTransformer{static isNode(n){return n&&typeof n==="object"&&typeof n.type==="string"}static isTextNode(n){return TreeTransformer.isNode(n)&&n.type==="text"&&typeof n.content==="string"}traverse(f){this._traverse(this.root,new TraversalState(this.root),f);}_traverse(n,state,f){let content="";if(TreeTransformer.isNode(n)){const node=n;state._containers.push(node);state._ancestors.push(node);if(typeof node.content==="string"){content=node.content;}const keys=Object.keys(node);keys.forEach(key=>{if(key==="type"){return}const value=node[key];if(value&&typeof value==="object"){state._indexes.push(key);content+=this._traverse(value,state,f);state._indexes.pop();}});state._currentNode=state._ancestors.pop();state._containers.pop();f(node,state,content);}else if(Array.isArray(n)){const nodes=n;state._containers.push(nodes);let index=0;while(index<nodes.length){state._indexes.push(index);content+=this._traverse(nodes[index],state,f);index=state._indexes.pop()+1;}state._containers.pop();}return content}constructor(root){this.root=root;}}class TraversalState{currentNode(){return this._currentNode||this.root}parent(){return this._ancestors.top()}ancestors(){return this._ancestors.values()}nextSibling(){const siblings=this._containers.top();if(!siblings||!Array.isArray(siblings)){return null}const index=this._indexes.top();if(siblings.length>index+1){return siblings[index+1]}return null}previousSibling(){const siblings=this._containers.top();if(!siblings||!Array.isArray(siblings)){return null}const index=this._indexes.top();if(index>0){return siblings[index-1]}return null}removeNextSibling(){const siblings=this._containers.top();if(siblings&&Array.isArray(siblings)){const index=this._indexes.top();if(siblings.length>index+1){return siblings.splice(index+1,1)[0]}}return null}replace(...replacements){const parent=this._containers.top();if(!parent){throw new perseusCore.PerseusError("Can't replace the root of the tree",perseusCore.Errors.Internal)}if(Array.isArray(parent)){const index=this._indexes.top();parent.splice(index,1,...replacements);this._indexes.pop();this._indexes.push(index+replacements.length-1);}else {const property=this._indexes.top();if(replacements.length===0){delete parent[property];}else if(replacements.length===1){parent[property]=replacements[0];}else {parent[property]=replacements;}}}hasPreviousSibling(){return Array.isArray(this._containers.top())&&this._indexes.top()>0}goToPreviousSibling(){if(!this.hasPreviousSibling()){throw new perseusCore.PerseusError("goToPreviousSibling(): node has no previous sibling",perseusCore.Errors.Internal)}this._currentNode=this.previousSibling();const index=this._indexes.pop();this._indexes.push(index-1);}hasParent(){return this._ancestors.size()!==0}goToParent(){if(!this.hasParent()){throw new perseusCore.PerseusError("goToParent(): node has no ancestor",perseusCore.Errors.NotAllowed)}this._currentNode=this._ancestors.pop();while(this._containers.size()&&this._containers.top()[this._indexes.top()]!==this._currentNode){this._containers.pop();this._indexes.pop();}}clone(){const clone=new TraversalState(this.root);clone._currentNode=this._currentNode;clone._containers=this._containers.clone();clone._indexes=this._indexes.clone();clone._ancestors=this._ancestors.clone();return clone}equals(that){return this.root===that.root&&this._currentNode===that._currentNode&&this._containers.equals(that._containers)&&this._indexes.equals(that._indexes)&&this._ancestors.equals(that._ancestors)}constructor(root){this.root=root;this._currentNode=null;this._containers=new Stack;this._indexes=new Stack;this._ancestors=new Stack;}}class Stack{push(v){this.stack.push(v);}pop(){return this.stack.pop()}top(){return this.stack[this.stack.length-1]}values(){return this.stack.slice(0)}size(){return this.stack.length}toString(){return this.stack.toString()}clone(){return new Stack(this.stack)}equals(that){if(!that||!that.stack||that.stack.length!==this.stack.length){return false}for(let i=0;i<this.stack.length;i++){if(this.stack[i]!==that.stack[i]){return false}}return true}constructor(array){this.stack=array?array.slice(0):[];}}
1022
128
 
1023
- const libName = "@khanacademy/perseus-linter";
1024
- const libVersion = "2.0.0";
1025
- addLibraryVersionToPerseusDebug(libName, libVersion);
129
+ const libName="@khanacademy/perseus-linter";const libVersion="3.0.1";perseusUtils.addLibraryVersionToPerseusDebug(libName,libVersion);
1026
130
 
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)
1032
- });
1033
- const linterContextDefault = {
1034
- contentType: "",
1035
- highlightLint: false,
1036
- paths: [],
1037
- stack: []
1038
- };
131
+ const linterContextProps=PropTypes__default.default.shape({contentType:PropTypes__default.default.string,highlightLint:PropTypes__default.default.bool,paths:PropTypes__default.default.arrayOf(PropTypes__default.default.string),stack:PropTypes__default.default.arrayOf(PropTypes__default.default.string)});const linterContextDefault={contentType:"",highlightLint:false,paths:[],stack:[]};
1039
132
 
1040
- const allLintRules = AllRules.filter(r => r.severity < Rule.Severity.BULK_WARNING);
1041
- function runLinter(tree, context, highlight, rules = allLintRules) {
1042
- const warnings = [];
1043
- const tt = new TreeTransformer(tree);
1044
- tt.traverse((node, state, content) => {
1045
- if (TreeTransformer.isTextNode(node)) {
1046
- let next = state.nextSibling();
1047
- while (TreeTransformer.isTextNode(next)) {
1048
- node.content += next.content;
1049
- state.removeNextSibling();
1050
- next = state.nextSibling();
1051
- }
1052
- }
1053
- });
1054
- let tableWarnings = [];
1055
- let insideTable = false;
1056
- tt.traverse((node, state, content) => {
1057
- const nodeWarnings = [];
1058
- const applicableRules = rules.filter(r => r.applies(context));
1059
- const stack = [...context.stack];
1060
- stack.push(node.type);
1061
- const nodeContext = _extends({}, context, {
1062
- stack: stack.join(".")
1063
- });
1064
- applicableRules.forEach(rule => {
1065
- const warning = rule.check(node, state, content, nodeContext);
1066
- if (warning) {
1067
- if (warning.start || warning.end) {
1068
- warning.target = content.substring(warning.start, warning.end);
1069
- }
1070
- warnings.push(warning);
1071
- if (highlight) {
1072
- nodeWarnings.push(warning);
1073
- }
1074
- }
1075
- });
1076
- if (!highlight) {
1077
- return;
1078
- }
1079
- if (node.type === "table") {
1080
- if (tableWarnings.length) {
1081
- nodeWarnings.push(...tableWarnings);
1082
- }
1083
- insideTable = false;
1084
- tableWarnings = [];
1085
- } else if (!insideTable) {
1086
- insideTable = state.ancestors().some(n => n.type === "table");
1087
- }
1088
- if (insideTable && nodeWarnings.length) {
1089
- tableWarnings.push(...nodeWarnings);
1090
- }
1091
- if (nodeWarnings.length) {
1092
- nodeWarnings.sort((a, b) => {
1093
- return a.severity - b.severity;
1094
- });
1095
- if (node.type !== "text" || nodeWarnings.length > 1) {
1096
- state.replace({
1097
- type: "lint",
1098
- content: node,
1099
- message: nodeWarnings.map(w => w.message).join("\n\n"),
1100
- ruleName: nodeWarnings[0].rule,
1101
- blockHighlight: nodeContext.blockHighlight,
1102
- insideTable: insideTable,
1103
- severity: nodeWarnings[0].severity
1104
- });
1105
- } else {
1106
- const _content = node.content;
1107
- const warning = nodeWarnings[0];
1108
- const start = warning.start || 0;
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 = [];
1114
- if (prefix) {
1115
- replacements.push({
1116
- type: "text",
1117
- content: prefix
1118
- });
1119
- }
1120
- replacements.push({
1121
- type: "lint",
1122
- content: {
1123
- type: "text",
1124
- content: lint
1125
- },
1126
- message: warning.message,
1127
- ruleName: warning.rule,
1128
- insideTable: insideTable,
1129
- severity: warning.severity
1130
- });
1131
- if (suffix) {
1132
- replacements.push({
1133
- type: "text",
1134
- content: suffix
1135
- });
1136
- }
1137
- state.replace(...replacements);
1138
- }
1139
- }
1140
- });
1141
- return warnings;
1142
- }
1143
- function pushContextStack(context, name) {
1144
- const stack = context.stack || [];
1145
- return _extends({}, context, {
1146
- stack: stack.concat(name)
1147
- });
1148
- }
133
+ const allLintRules=AllRules.filter(r=>r.severity<Rule.Severity.BULK_WARNING);function runLinter(tree,context,highlight,rules=allLintRules){const warnings=[];const tt=new TreeTransformer(tree);tt.traverse((node,state,content)=>{if(TreeTransformer.isTextNode(node)){let next=state.nextSibling();while(TreeTransformer.isTextNode(next)){node.content+=next.content;state.removeNextSibling();next=state.nextSibling();}}});let tableWarnings=[];let insideTable=false;tt.traverse((node,state,content)=>{const nodeWarnings=[];const applicableRules=rules.filter(r=>r.applies(context));const stack=[...context.stack];stack.push(node.type);const nodeContext={...context,stack:stack.join(".")};applicableRules.forEach(rule=>{const warning=rule.check(node,state,content,nodeContext);if(warning){if(warning.start||warning.end){warning.target=content.substring(warning.start,warning.end);}warnings.push(warning);if(highlight){nodeWarnings.push(warning);}}});if(!highlight){return}if(node.type==="table"){if(tableWarnings.length){nodeWarnings.push(...tableWarnings);}insideTable=false;tableWarnings=[];}else if(!insideTable){insideTable=state.ancestors().some(n=>n.type==="table");}if(insideTable&&nodeWarnings.length){tableWarnings.push(...nodeWarnings);}if(nodeWarnings.length){nodeWarnings.sort((a,b)=>{return a.severity-b.severity});if(node.type!=="text"||nodeWarnings.length>1){state.replace({type:"lint",content:node,message:nodeWarnings.map(w=>w.message).join("\n\n"),ruleName:nodeWarnings[0].rule,blockHighlight:nodeContext.blockHighlight,insideTable:insideTable,severity:nodeWarnings[0].severity});}else {const content=node.content;const warning=nodeWarnings[0];const start=warning.start||0;const end=warning.end||content.length;const prefix=content.substring(0,start);const lint=content.substring(start,end);const suffix=content.substring(end);const replacements=[];if(prefix){replacements.push({type:"text",content:prefix});}replacements.push({type:"lint",content:{type:"text",content:lint},message:warning.message,ruleName:warning.rule,insideTable:insideTable,severity:warning.severity});if(suffix){replacements.push({type:"text",content:suffix});}state.replace(...replacements);}}});return warnings}function pushContextStack(context,name){const stack=context.stack||[];return {...context,stack:stack.concat(name)}}
1149
134
 
1150
- export { Rule, libVersion, linterContextDefault, linterContextProps, pushContextStack, allLintRules as rules, runLinter };
135
+ exports.Rule = Rule;
136
+ exports.libVersion = libVersion;
137
+ exports.linterContextDefault = linterContextDefault;
138
+ exports.linterContextProps = linterContextProps;
139
+ exports.pushContextStack = pushContextStack;
140
+ exports.rules = allLintRules;
141
+ exports.runLinter = runLinter;
1151
142
  //# sourceMappingURL=index.js.map