@khanacademy/perseus-linter 0.0.0-PR862-20231207182234 → 0.0.0-PR875-20250221232857
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/LICENSE +18 -0
- package/README.md +7 -0
- package/dist/es/index.js +156 -38
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +181 -38
- package/dist/index.js.map +1 -1
- package/dist/proptypes.d.ts +1 -7
- package/dist/rule.d.ts +24 -8
- package/dist/rules/expression-widget.d.ts +9 -0
- package/dist/rules/lint-utils.d.ts +0 -1
- package/dist/shared-utils/add-library-version-to-perseus-debug.d.ts +9 -0
- package/dist/tree-transformer.d.ts +3 -1
- package/package.json +35 -35
- package/.eslintrc.js +0 -12
- package/CHANGELOG.md +0 -148
- package/src/README.md +0 -41
- package/src/__tests__/matcher.test.ts +0 -498
- package/src/__tests__/rule.test.ts +0 -110
- package/src/__tests__/rules.test.ts +0 -548
- package/src/__tests__/selector-parser.test.ts +0 -51
- package/src/__tests__/tree-transformer.test.ts +0 -444
- package/src/index.ts +0 -281
- package/src/proptypes.ts +0 -19
- package/src/rule.ts +0 -419
- package/src/rules/absolute-url.ts +0 -23
- package/src/rules/all-rules.ts +0 -71
- package/src/rules/blockquoted-math.ts +0 -9
- package/src/rules/blockquoted-widget.ts +0 -9
- package/src/rules/double-spacing-after-terminal.ts +0 -11
- package/src/rules/extra-content-spacing.ts +0 -11
- package/src/rules/heading-level-1.ts +0 -13
- package/src/rules/heading-level-skip.ts +0 -19
- package/src/rules/heading-sentence-case.ts +0 -10
- package/src/rules/heading-title-case.ts +0 -68
- package/src/rules/image-alt-text.ts +0 -20
- package/src/rules/image-in-table.ts +0 -9
- package/src/rules/image-spaces-around-urls.ts +0 -34
- package/src/rules/image-widget.ts +0 -49
- package/src/rules/link-click-here.ts +0 -10
- package/src/rules/lint-utils.ts +0 -47
- package/src/rules/long-paragraph.ts +0 -13
- package/src/rules/math-adjacent.ts +0 -9
- package/src/rules/math-align-extra-break.ts +0 -10
- package/src/rules/math-align-linebreaks.ts +0 -42
- package/src/rules/math-empty.ts +0 -9
- package/src/rules/math-font-size.ts +0 -11
- package/src/rules/math-frac.ts +0 -9
- package/src/rules/math-nested.ts +0 -10
- package/src/rules/math-starts-with-space.ts +0 -11
- package/src/rules/math-text-empty.ts +0 -9
- package/src/rules/math-without-dollars.ts +0 -13
- package/src/rules/nested-lists.ts +0 -10
- package/src/rules/profanity.ts +0 -9
- package/src/rules/table-missing-cells.ts +0 -19
- package/src/rules/unbalanced-code-delimiters.ts +0 -13
- package/src/rules/unescaped-dollar.ts +0 -9
- package/src/rules/widget-in-table.ts +0 -9
- package/src/selector.ts +0 -504
- package/src/tree-transformer.ts +0 -583
- package/src/types.ts +0 -7
- package/src/version.ts +0 -10
- package/tsconfig-build.json +0 -12
- package/tsconfig-build.tsbuildinfo +0 -1
- /package/dist/rules/{math-font-size.d.ts → image-url-empty.d.ts} +0 -0
- /package/dist/rules/{profanity.d.ts → static-widget-in-question-stem.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var perseusError = require('@khanacademy/perseus-error');
|
|
6
5
|
var perseusCore = require('@khanacademy/perseus-core');
|
|
7
6
|
var PropTypes = require('prop-types');
|
|
8
7
|
|
|
@@ -29,7 +28,7 @@ class Selector {
|
|
|
29
28
|
* subclasses must provide an implementation of this method.
|
|
30
29
|
*/
|
|
31
30
|
match(state) {
|
|
32
|
-
throw new
|
|
31
|
+
throw new perseusCore.PerseusError("Selector subclasses must implement match()", perseusCore.Errors.NotAllowed);
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
/**
|
|
@@ -51,9 +50,9 @@ class Selector {
|
|
|
51
50
|
* Instead call the static Selector.parse() method.
|
|
52
51
|
*/
|
|
53
52
|
class Parser {
|
|
54
|
-
// We do lexing with a simple regular expression
|
|
55
|
-
// The array of tokens
|
|
56
|
-
// Which token in the array we're looking at now
|
|
53
|
+
static TOKENS; // We do lexing with a simple regular expression
|
|
54
|
+
tokens; // The array of tokens
|
|
55
|
+
tokenIndex; // Which token in the array we're looking at now
|
|
57
56
|
|
|
58
57
|
constructor(s) {
|
|
59
58
|
// Normalize whitespace:
|
|
@@ -212,6 +211,7 @@ class ParseError extends Error {
|
|
|
212
211
|
* first.
|
|
213
212
|
*/
|
|
214
213
|
class SelectorList extends Selector {
|
|
214
|
+
selectors;
|
|
215
215
|
constructor(selectors) {
|
|
216
216
|
super();
|
|
217
217
|
this.selectors = selectors;
|
|
@@ -254,6 +254,7 @@ class AnyNode extends Selector {
|
|
|
254
254
|
* it matches any node whose `type` property is a specified string
|
|
255
255
|
*/
|
|
256
256
|
class TypeSelector extends Selector {
|
|
257
|
+
type;
|
|
257
258
|
constructor(type) {
|
|
258
259
|
super();
|
|
259
260
|
this.type = type;
|
|
@@ -277,6 +278,8 @@ class TypeSelector extends Selector {
|
|
|
277
278
|
* method.
|
|
278
279
|
*/
|
|
279
280
|
class SelectorCombinator extends Selector {
|
|
281
|
+
left;
|
|
282
|
+
right;
|
|
280
283
|
constructor(left, right) {
|
|
281
284
|
super();
|
|
282
285
|
this.left = left;
|
|
@@ -522,41 +525,47 @@ class SiblingCombinator extends SelectorCombinator {
|
|
|
522
525
|
// This represents the type returned by String.match(). It is an
|
|
523
526
|
// array of strings, but also has index:number and input:string properties.
|
|
524
527
|
// TypeScript doesn't handle it well, so we punt and just use any.
|
|
528
|
+
|
|
525
529
|
// This is the return type of the check() method of a Rule object
|
|
530
|
+
|
|
526
531
|
// This is the return type of the lint detection function passed as the 4th
|
|
527
532
|
// argument to the Rule() constructor. It can return null or a string or an
|
|
528
533
|
// object containing a string and two numbers.
|
|
529
534
|
// prettier-ignore
|
|
530
535
|
// (prettier formats this in a way that ka-lint does not like)
|
|
536
|
+
|
|
531
537
|
// This is the type of the lint detection function that the Rule() constructor
|
|
532
538
|
// expects as its fourth argument. It is passed the TraversalState object and
|
|
533
539
|
// content string that were passed to check(), and is also passed the array of
|
|
534
540
|
// nodes returned by the selector match and the array of strings returned by
|
|
535
541
|
// the pattern match. It should return null if no lint is detected or an
|
|
536
542
|
// error message or an object contining an error message.
|
|
543
|
+
|
|
537
544
|
// An optional check to verify whether or not a particular rule should
|
|
538
545
|
// be checked by context. For example, some rules only apply in exercises,
|
|
539
546
|
// and should never be applied to articles. Defaults to true, so if we
|
|
540
547
|
// omit the applies function in a rule, it'll be tested everywhere.
|
|
548
|
+
|
|
541
549
|
/**
|
|
542
550
|
* A Rule object describes a Perseus lint rule. See the comment at the top of
|
|
543
551
|
* this file for detailed description.
|
|
544
552
|
*/
|
|
545
553
|
class Rule {
|
|
546
|
-
// The name of the rule
|
|
547
|
-
// The severity of the rule
|
|
548
|
-
// The specified selector or the DEFAULT_SELECTOR
|
|
549
|
-
// A regular expression if one was specified
|
|
550
|
-
// The lint-testing function or a default
|
|
551
|
-
// Checks to see if we should apply a rule or not
|
|
552
|
-
// The error message for use with the default function
|
|
554
|
+
name; // The name of the rule
|
|
555
|
+
severity; // The severity of the rule
|
|
556
|
+
selector; // The specified selector or the DEFAULT_SELECTOR
|
|
557
|
+
pattern; // A regular expression if one was specified
|
|
558
|
+
lint; // The lint-testing function or a default
|
|
559
|
+
applies; // Checks to see if we should apply a rule or not
|
|
560
|
+
message; // The error message for use with the default function
|
|
561
|
+
static DEFAULT_SELECTOR;
|
|
553
562
|
|
|
554
563
|
// The comment at the top of this file has detailed docs for
|
|
555
564
|
// this constructor and its arguments
|
|
556
565
|
constructor(name, severity, selector, pattern, lint, applies) {
|
|
557
566
|
var _this = this;
|
|
558
567
|
if (!selector && !pattern) {
|
|
559
|
-
throw new
|
|
568
|
+
throw new perseusCore.PerseusError("Lint rules must have a selector or pattern", perseusCore.Errors.InvalidInput, {
|
|
560
569
|
metadata: {
|
|
561
570
|
name
|
|
562
571
|
}
|
|
@@ -624,7 +633,6 @@ class Rule {
|
|
|
624
633
|
if (!error) {
|
|
625
634
|
return null; // No lint; we're done
|
|
626
635
|
}
|
|
627
|
-
|
|
628
636
|
if (typeof error === "string") {
|
|
629
637
|
// If the lint function returned a string we assume it
|
|
630
638
|
// applies to the entire content of the node and return it.
|
|
@@ -787,12 +795,61 @@ var DoubleSpacingAfterTerminal = Rule.makeRule({
|
|
|
787
795
|
any other kind of terminal punctuation.`
|
|
788
796
|
});
|
|
789
797
|
|
|
798
|
+
function buttonNotInButtonSet(name, set) {
|
|
799
|
+
return `Answer requires a button not found in the button sets: ${name} (in ${set})`;
|
|
800
|
+
}
|
|
801
|
+
const stringToButtonSet = {
|
|
802
|
+
"\\sqrt": "prealgebra",
|
|
803
|
+
"\\sin": "trig",
|
|
804
|
+
"\\cos": "trig",
|
|
805
|
+
"\\tan": "trig",
|
|
806
|
+
"\\log": "logarithms",
|
|
807
|
+
"\\ln": "logarithms"
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Rule to make sure that Expression questions that require
|
|
812
|
+
* a specific math symbol to answer have that math symbol
|
|
813
|
+
* available in the keypad (desktop learners can use a keyboard,
|
|
814
|
+
* but mobile learners must use the MathInput keypad)
|
|
815
|
+
*/
|
|
816
|
+
var ExpressionWidget = Rule.makeRule({
|
|
817
|
+
name: "expression-widget",
|
|
818
|
+
severity: Rule.Severity.WARNING,
|
|
819
|
+
selector: "widget",
|
|
820
|
+
lint: function (state, content, nodes, match, context) {
|
|
821
|
+
// This rule only looks at image widgets
|
|
822
|
+
if (state.currentNode().widgetType !== "expression") {
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const nodeId = state.currentNode().id;
|
|
826
|
+
if (!nodeId) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// If it can't find a definition for the widget it does nothing
|
|
831
|
+
const widget = context?.widgets?.[nodeId];
|
|
832
|
+
if (!widget) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const answers = widget.options.answerForms;
|
|
836
|
+
const buttons = widget.options.buttonSets;
|
|
837
|
+
for (const answer of answers) {
|
|
838
|
+
for (const [str, set] of Object.entries(stringToButtonSet)) {
|
|
839
|
+
if (answer.value.includes(str) && !buttons.includes(set)) {
|
|
840
|
+
return buttonNotInButtonSet(str, set);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
|
|
790
847
|
var ExtraContentSpacing = Rule.makeRule({
|
|
791
848
|
name: "extra-content-spacing",
|
|
792
849
|
selector: "paragraph",
|
|
793
850
|
pattern: /\s+$/,
|
|
794
851
|
applies: function (context) {
|
|
795
|
-
return context
|
|
852
|
+
return context?.contentType === "article";
|
|
796
853
|
},
|
|
797
854
|
message: `No extra whitespace at the end of content blocks.`
|
|
798
855
|
});
|
|
@@ -957,6 +1014,28 @@ Whitespace in image URLs causes translation difficulties.`;
|
|
|
957
1014
|
}
|
|
958
1015
|
});
|
|
959
1016
|
|
|
1017
|
+
var ImageUrlEmpty = Rule.makeRule({
|
|
1018
|
+
name: "image-url-empty",
|
|
1019
|
+
severity: Rule.Severity.ERROR,
|
|
1020
|
+
selector: "image",
|
|
1021
|
+
lint: function (state, content, nodes) {
|
|
1022
|
+
const image = nodes[0];
|
|
1023
|
+
const url = image.target;
|
|
1024
|
+
|
|
1025
|
+
// If no URL is provided, an infinite spinner will be shown in articles
|
|
1026
|
+
// overlaying the page where the image should be. This prevents the page
|
|
1027
|
+
// from fully loading. As a result, we check for URLS with all images.
|
|
1028
|
+
if (!url || !url.trim()) {
|
|
1029
|
+
return "Images should have a URL";
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// NOTE(TB): Ideally there would be a check to confirm the URL works
|
|
1033
|
+
// and leads to a valid resource, but fetching the URL would require
|
|
1034
|
+
// linting to be able to handle async functions, which it currently
|
|
1035
|
+
// cannot do.
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
|
|
960
1039
|
// Normally we have one rule per file. But since our selector class
|
|
961
1040
|
// can't match specific widget types directly, this rule implements
|
|
962
1041
|
// a number of image widget related rules in one place. This should
|
|
@@ -972,9 +1051,13 @@ var ImageWidget = Rule.makeRule({
|
|
|
972
1051
|
if (state.currentNode().widgetType !== "image") {
|
|
973
1052
|
return;
|
|
974
1053
|
}
|
|
1054
|
+
const nodeId = state.currentNode().id;
|
|
1055
|
+
if (!nodeId) {
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
975
1058
|
|
|
976
1059
|
// If it can't find a definition for the widget it does nothing
|
|
977
|
-
const widget = context && context.widgets && context.widgets[
|
|
1060
|
+
const widget = context && context.widgets && context.widgets[nodeId];
|
|
978
1061
|
if (!widget) {
|
|
979
1062
|
return;
|
|
980
1063
|
}
|
|
@@ -1058,7 +1141,7 @@ var MathAlignLinebreaks = Rule.makeRule({
|
|
|
1058
1141
|
const index = text.indexOf("\\\\");
|
|
1059
1142
|
if (index === -1) {
|
|
1060
1143
|
// No more backslash pairs, so we found no lint
|
|
1061
|
-
return
|
|
1144
|
+
return;
|
|
1062
1145
|
}
|
|
1063
1146
|
text = text.substring(index + 2);
|
|
1064
1147
|
|
|
@@ -1089,15 +1172,6 @@ var MathEmpty = Rule.makeRule({
|
|
|
1089
1172
|
message: "Empty math: don't use $$ in your markdown."
|
|
1090
1173
|
});
|
|
1091
1174
|
|
|
1092
|
-
var MathFontSize = Rule.makeRule({
|
|
1093
|
-
name: "math-font-size",
|
|
1094
|
-
severity: Rule.Severity.GUIDELINE,
|
|
1095
|
-
selector: "math, blockMath",
|
|
1096
|
-
pattern: /\\(tiny|Tiny|small|large|Large|LARGE|huge|Huge|scriptsize|normalsize)\s*{/,
|
|
1097
|
-
message: `Math font size:
|
|
1098
|
-
Don't change the default font size with \\Large{} or similar commands`
|
|
1099
|
-
});
|
|
1100
|
-
|
|
1101
1175
|
var MathFrac = Rule.makeRule({
|
|
1102
1176
|
name: "math-frac",
|
|
1103
1177
|
severity: Rule.Severity.GUIDELINE,
|
|
@@ -1154,12 +1228,29 @@ nested lists are hard to read on mobile devices;
|
|
|
1154
1228
|
do not use additional indentation.`
|
|
1155
1229
|
});
|
|
1156
1230
|
|
|
1157
|
-
var
|
|
1158
|
-
name: "
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1231
|
+
var StaticWidgetInQuestionStem = Rule.makeRule({
|
|
1232
|
+
name: "static-widget-in-question-stem",
|
|
1233
|
+
severity: Rule.Severity.WARNING,
|
|
1234
|
+
selector: "widget",
|
|
1235
|
+
lint: (state, content, nodes, match, context) => {
|
|
1236
|
+
if (context?.contentType !== "exercise") {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
if (context.stack.includes("hint")) {
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
const nodeId = state.currentNode().id;
|
|
1243
|
+
if (!nodeId) {
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
const widget = context?.widgets?.[nodeId];
|
|
1247
|
+
if (!widget) {
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
if (widget.static) {
|
|
1251
|
+
return `Widget in question stem is static (non-interactive).`;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1163
1254
|
});
|
|
1164
1255
|
|
|
1165
1256
|
var TableMissingCells = Rule.makeRule({
|
|
@@ -1209,7 +1300,7 @@ do not put widgets inside of tables.`
|
|
|
1209
1300
|
});
|
|
1210
1301
|
|
|
1211
1302
|
// TODO(davidflanagan):
|
|
1212
|
-
var AllRules = [AbsoluteUrl, BlockquotedMath, BlockquotedWidget, DoubleSpacingAfterTerminal, ExtraContentSpacing, HeadingLevel1, HeadingLevelSkip, HeadingSentenceCase, HeadingTitleCase, ImageAltText, ImageInTable, LinkClickHere, LongParagraph, MathAdjacent, MathAlignExtraBreak, MathAlignLinebreaks, MathEmpty,
|
|
1303
|
+
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];
|
|
1213
1304
|
|
|
1214
1305
|
/**
|
|
1215
1306
|
* TreeTransformer is a class for traversing and transforming trees. Create a
|
|
@@ -1271,12 +1362,16 @@ var AllRules = [AbsoluteUrl, BlockquotedMath, BlockquotedWidget, DoubleSpacingAf
|
|
|
1271
1362
|
|
|
1272
1363
|
// TreeNode is the type of a node in a parse tree. The only real requirement is
|
|
1273
1364
|
// that every node has a string-valued `type` property
|
|
1365
|
+
|
|
1274
1366
|
// TraversalCallback is the type of the callback function passed to the
|
|
1275
1367
|
// traverse() method. It is invoked with node, state, and content arguments
|
|
1276
1368
|
// and is expected to return nothing.
|
|
1369
|
+
|
|
1277
1370
|
// This is the TreeTransformer class described in detail at the
|
|
1278
1371
|
// top of this file.
|
|
1279
1372
|
class TreeTransformer {
|
|
1373
|
+
root;
|
|
1374
|
+
|
|
1280
1375
|
// To create a tree transformer, just pass the root node of the tree
|
|
1281
1376
|
constructor(root) {
|
|
1282
1377
|
this.root = root;
|
|
@@ -1424,6 +1519,7 @@ class TreeTransformer {
|
|
|
1424
1519
|
**/
|
|
1425
1520
|
class TraversalState {
|
|
1426
1521
|
// The root node of the tree being traversed
|
|
1522
|
+
root;
|
|
1427
1523
|
|
|
1428
1524
|
// These are internal state properties. Use the accessor methods defined
|
|
1429
1525
|
// below instead of using these properties directly. Note that the
|
|
@@ -1431,6 +1527,11 @@ class TraversalState {
|
|
|
1431
1527
|
// elements, depending on whether we just recursed on an array or on a
|
|
1432
1528
|
// node. This is hard for TypeScript to deal with, so you'll see a number of
|
|
1433
1529
|
// type casts through the any type when working with these two properties.
|
|
1530
|
+
_currentNode;
|
|
1531
|
+
_containers;
|
|
1532
|
+
_indexes;
|
|
1533
|
+
_ancestors;
|
|
1534
|
+
|
|
1434
1535
|
// The constructor just stores the root node and creates empty stacks.
|
|
1435
1536
|
constructor(root) {
|
|
1436
1537
|
this.root = root;
|
|
@@ -1558,7 +1659,7 @@ class TraversalState {
|
|
|
1558
1659
|
replace() {
|
|
1559
1660
|
const parent = this._containers.top();
|
|
1560
1661
|
if (!parent) {
|
|
1561
|
-
throw new
|
|
1662
|
+
throw new perseusCore.PerseusError("Can't replace the root of the tree", perseusCore.Errors.Internal);
|
|
1562
1663
|
}
|
|
1563
1664
|
|
|
1564
1665
|
// The top of the container stack is either an array or an object
|
|
@@ -1611,7 +1712,7 @@ class TraversalState {
|
|
|
1611
1712
|
*/
|
|
1612
1713
|
goToPreviousSibling() {
|
|
1613
1714
|
if (!this.hasPreviousSibling()) {
|
|
1614
|
-
throw new
|
|
1715
|
+
throw new perseusCore.PerseusError("goToPreviousSibling(): node has no previous sibling", perseusCore.Errors.Internal);
|
|
1615
1716
|
}
|
|
1616
1717
|
this._currentNode = this.previousSibling();
|
|
1617
1718
|
// Since we know that we have a previous sibling, we know that
|
|
@@ -1641,7 +1742,7 @@ class TraversalState {
|
|
|
1641
1742
|
*/
|
|
1642
1743
|
goToParent() {
|
|
1643
1744
|
if (!this.hasParent()) {
|
|
1644
|
-
throw new
|
|
1745
|
+
throw new perseusCore.PerseusError("goToParent(): node has no ancestor", perseusCore.Errors.NotAllowed);
|
|
1645
1746
|
}
|
|
1646
1747
|
this._currentNode = this._ancestors.pop();
|
|
1647
1748
|
|
|
@@ -1688,6 +1789,7 @@ class TraversalState {
|
|
|
1688
1789
|
* the TraversalState class simpler in a number of places.
|
|
1689
1790
|
*/
|
|
1690
1791
|
class Stack {
|
|
1792
|
+
stack;
|
|
1691
1793
|
constructor(array) {
|
|
1692
1794
|
this.stack = array ? array.slice(0) : [];
|
|
1693
1795
|
}
|
|
@@ -1745,10 +1847,51 @@ class Stack {
|
|
|
1745
1847
|
}
|
|
1746
1848
|
}
|
|
1747
1849
|
|
|
1850
|
+
/**
|
|
1851
|
+
* Adds the given perseus library version information to the __perseus_debug__
|
|
1852
|
+
* object and ensures that the object is attached to `globalThis` (`window` in
|
|
1853
|
+
* browser environments).
|
|
1854
|
+
*
|
|
1855
|
+
* This allows each library to provide runtime version information to assist in
|
|
1856
|
+
* debugging in production environments.
|
|
1857
|
+
*/
|
|
1858
|
+
const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
|
|
1859
|
+
// If the library version is the default value, then we don't want to
|
|
1860
|
+
// prefix it with a "v" to indicate that it is a version number.
|
|
1861
|
+
let prefix = "v";
|
|
1862
|
+
if (libraryVersion === "__lib_version__") {
|
|
1863
|
+
prefix = "";
|
|
1864
|
+
}
|
|
1865
|
+
const formattedVersion = `${prefix}${libraryVersion}`;
|
|
1866
|
+
if (typeof globalThis !== "undefined") {
|
|
1867
|
+
globalThis.__perseus_debug__ = globalThis.__perseus_debug__ ?? {};
|
|
1868
|
+
const existingVersionEntry = globalThis.__perseus_debug__[libraryName];
|
|
1869
|
+
if (existingVersionEntry) {
|
|
1870
|
+
// If we already have an entry and it doesn't match the registered
|
|
1871
|
+
// version, we morph the entry into an array and log a warning.
|
|
1872
|
+
if (existingVersionEntry !== formattedVersion) {
|
|
1873
|
+
// Existing entry might be an array already (oops, at least 2
|
|
1874
|
+
// versions of the library already loaded!).
|
|
1875
|
+
const allVersions = Array.isArray(existingVersionEntry) ? existingVersionEntry : [existingVersionEntry];
|
|
1876
|
+
allVersions.push(formattedVersion);
|
|
1877
|
+
globalThis.__perseus_debug__[libraryName] = allVersions;
|
|
1878
|
+
|
|
1879
|
+
// eslint-disable-next-line no-console
|
|
1880
|
+
console.warn(`Multiple versions of ${libraryName} loaded on this page: ${allVersions.sort().join(", ")}`);
|
|
1881
|
+
}
|
|
1882
|
+
} else {
|
|
1883
|
+
globalThis.__perseus_debug__[libraryName] = formattedVersion;
|
|
1884
|
+
}
|
|
1885
|
+
} else {
|
|
1886
|
+
// eslint-disable-next-line no-console
|
|
1887
|
+
console.warn(`globalThis not found found (${formattedVersion})`);
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1890
|
+
|
|
1748
1891
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
1749
1892
|
const libName = "@khanacademy/perseus-linter";
|
|
1750
|
-
const libVersion = "
|
|
1751
|
-
|
|
1893
|
+
const libVersion = "1.2.18";
|
|
1894
|
+
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
1752
1895
|
|
|
1753
1896
|
// Define the shape of the linter context object that is passed through the
|
|
1754
1897
|
const linterContextProps = PropTypes__default["default"].shape({
|