@khanacademy/perseus-linter 1.0.0 → 1.2.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
@@ -791,12 +791,61 @@ var DoubleSpacingAfterTerminal = Rule.makeRule({
791
791
  any other kind of terminal punctuation.`
792
792
  });
793
793
 
794
+ function buttonNotInButtonSet(name, set) {
795
+ return `Answer requires a button not found in the button sets: ${name} (in ${set})`;
796
+ }
797
+ const stringToButtonSet = {
798
+ "\\sqrt": "prealgebra",
799
+ "\\sin": "trig",
800
+ "\\cos": "trig",
801
+ "\\tan": "trig",
802
+ "\\log": "logarithms",
803
+ "\\ln": "logarithms"
804
+ };
805
+
806
+ /**
807
+ * Rule to make sure that Expression questions that require
808
+ * a specific math symbol to answer have that math symbol
809
+ * available in the keypad (desktop learners can use a keyboard,
810
+ * but mobile learners must use the MathInput keypad)
811
+ */
812
+ var ExpressionWidget = Rule.makeRule({
813
+ name: "expression-widget",
814
+ severity: Rule.Severity.WARNING,
815
+ selector: "widget",
816
+ lint: function (state, content, nodes, match, context) {
817
+ // This rule only looks at image widgets
818
+ if (state.currentNode().widgetType !== "expression") {
819
+ return;
820
+ }
821
+ const nodeId = state.currentNode().id;
822
+ if (!nodeId) {
823
+ return;
824
+ }
825
+
826
+ // If it can't find a definition for the widget it does nothing
827
+ const widget = context?.widgets?.[nodeId];
828
+ if (!widget) {
829
+ return;
830
+ }
831
+ const answers = widget.options.answerForms;
832
+ const buttons = widget.options.buttonSets;
833
+ for (const answer of answers) {
834
+ for (const [str, set] of Object.entries(stringToButtonSet)) {
835
+ if (answer.value.includes(str) && !buttons.includes(set)) {
836
+ return buttonNotInButtonSet(str, set);
837
+ }
838
+ }
839
+ }
840
+ }
841
+ });
842
+
794
843
  var ExtraContentSpacing = Rule.makeRule({
795
844
  name: "extra-content-spacing",
796
845
  selector: "paragraph",
797
846
  pattern: /\s+$/,
798
847
  applies: function (context) {
799
- return context.contentType === "article";
848
+ return context?.contentType === "article";
800
849
  },
801
850
  message: `No extra whitespace at the end of content blocks.`
802
851
  });
@@ -976,9 +1025,13 @@ var ImageWidget = Rule.makeRule({
976
1025
  if (state.currentNode().widgetType !== "image") {
977
1026
  return;
978
1027
  }
1028
+ const nodeId = state.currentNode().id;
1029
+ if (!nodeId) {
1030
+ return;
1031
+ }
979
1032
 
980
1033
  // If it can't find a definition for the widget it does nothing
981
- const widget = context && context.widgets && context.widgets[state.currentNode().id];
1034
+ const widget = context && context.widgets && context.widgets[nodeId];
982
1035
  if (!widget) {
983
1036
  return;
984
1037
  }
@@ -1062,7 +1115,7 @@ var MathAlignLinebreaks = Rule.makeRule({
1062
1115
  const index = text.indexOf("\\\\");
1063
1116
  if (index === -1) {
1064
1117
  // No more backslash pairs, so we found no lint
1065
- return null;
1118
+ return;
1066
1119
  }
1067
1120
  text = text.substring(index + 2);
1068
1121
 
@@ -1158,6 +1211,31 @@ nested lists are hard to read on mobile devices;
1158
1211
  do not use additional indentation.`
1159
1212
  });
1160
1213
 
1214
+ var StaticWidgetInQuestionStem = Rule.makeRule({
1215
+ name: "static-widget-in-question-stem",
1216
+ severity: Rule.Severity.WARNING,
1217
+ selector: "widget",
1218
+ lint: (state, content, nodes, match, context) => {
1219
+ if (context?.contentType !== "exercise") {
1220
+ return;
1221
+ }
1222
+ if (context.stack.includes("hint")) {
1223
+ return;
1224
+ }
1225
+ const nodeId = state.currentNode().id;
1226
+ if (!nodeId) {
1227
+ return;
1228
+ }
1229
+ const widget = context?.widgets?.[nodeId];
1230
+ if (!widget) {
1231
+ return;
1232
+ }
1233
+ if (widget.static) {
1234
+ return `Widget in question stem is static (non-interactive).`;
1235
+ }
1236
+ }
1237
+ });
1238
+
1161
1239
  var TableMissingCells = Rule.makeRule({
1162
1240
  name: "table-missing-cells",
1163
1241
  severity: Rule.Severity.WARNING,
@@ -1205,7 +1283,7 @@ do not put widgets inside of tables.`
1205
1283
  });
1206
1284
 
1207
1285
  // TODO(davidflanagan):
1208
- var AllRules = [AbsoluteUrl, BlockquotedMath, BlockquotedWidget, DoubleSpacingAfterTerminal, ExtraContentSpacing, HeadingLevel1, HeadingLevelSkip, HeadingSentenceCase, HeadingTitleCase, ImageAltText, ImageInTable, LinkClickHere, LongParagraph, MathAdjacent, MathAlignExtraBreak, MathAlignLinebreaks, MathEmpty, MathFontSize, MathFrac, MathNested, MathStartsWithSpace, MathTextEmpty, NestedLists, TableMissingCells, UnescapedDollar, WidgetInTable, MathWithoutDollars, UnbalancedCodeDelimiters, ImageSpacesAroundUrls, ImageWidget];
1286
+ var AllRules = [AbsoluteUrl, BlockquotedMath, BlockquotedWidget, DoubleSpacingAfterTerminal, ExpressionWidget, ExtraContentSpacing, HeadingLevel1, HeadingLevelSkip, HeadingSentenceCase, HeadingTitleCase, ImageAltText, ImageInTable, LinkClickHere, LongParagraph, MathAdjacent, MathAlignExtraBreak, MathAlignLinebreaks, MathEmpty, MathFontSize, MathFrac, MathNested, MathStartsWithSpace, MathTextEmpty, NestedLists, StaticWidgetInQuestionStem, TableMissingCells, UnescapedDollar, WidgetInTable, MathWithoutDollars, UnbalancedCodeDelimiters, ImageSpacesAroundUrls, ImageWidget];
1209
1287
 
1210
1288
  /**
1211
1289
  * TreeTransformer is a class for traversing and transforming trees. Create a
@@ -1752,7 +1830,7 @@ class Stack {
1752
1830
 
1753
1831
  // This file is processed by a Rollup plugin (replace) to inject the production
1754
1832
  const libName = "@khanacademy/perseus-linter";
1755
- const libVersion = "1.0.0";
1833
+ const libVersion = "1.2.0";
1756
1834
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
1757
1835
 
1758
1836
  // Define the shape of the linter context object that is passed through the