@khanacademy/perseus-linter 1.1.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
 
@@ -1163,13 +1216,17 @@ var StaticWidgetInQuestionStem = Rule.makeRule({
1163
1216
  severity: Rule.Severity.WARNING,
1164
1217
  selector: "widget",
1165
1218
  lint: (state, content, nodes, match, context) => {
1166
- if (context.contentType !== "exercise") {
1219
+ if (context?.contentType !== "exercise") {
1167
1220
  return;
1168
1221
  }
1169
1222
  if (context.stack.includes("hint")) {
1170
1223
  return;
1171
1224
  }
1172
- const widget = context?.widgets?.[state.currentNode().id];
1225
+ const nodeId = state.currentNode().id;
1226
+ if (!nodeId) {
1227
+ return;
1228
+ }
1229
+ const widget = context?.widgets?.[nodeId];
1173
1230
  if (!widget) {
1174
1231
  return;
1175
1232
  }
@@ -1226,7 +1283,7 @@ do not put widgets inside of tables.`
1226
1283
  });
1227
1284
 
1228
1285
  // TODO(davidflanagan):
1229
- 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, StaticWidgetInQuestionStem, 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];
1230
1287
 
1231
1288
  /**
1232
1289
  * TreeTransformer is a class for traversing and transforming trees. Create a
@@ -1773,7 +1830,7 @@ class Stack {
1773
1830
 
1774
1831
  // This file is processed by a Rollup plugin (replace) to inject the production
1775
1832
  const libName = "@khanacademy/perseus-linter";
1776
- const libVersion = "1.1.0";
1833
+ const libVersion = "1.2.0";
1777
1834
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
1778
1835
 
1779
1836
  // Define the shape of the linter context object that is passed through the