@khanacademy/perseus-linter 0.0.0-PR3060-20251121005304 → 0.0.0-PR3061-20251121021959

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/es/index.js CHANGED
@@ -2,6 +2,7 @@ import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-utils';
2
2
  import { PerseusError, Errors, CoreWidgetRegistry, makeSafeUrl } from '@khanacademy/perseus-core';
3
3
  import * as KAS from '@khanacademy/kas';
4
4
  import { parse, traverseContent } from '@khanacademy/pure-markdown';
5
+ import { vector } from '@khanacademy/kmath';
5
6
 
6
7
  const libName="@khanacademy/perseus-linter";const libVersion="4.4.7";addLibraryVersionToPerseusDebug(libName,libVersion);
7
8
 
@@ -72,6 +73,8 @@ This image's alt text is only ${alt.trim().length} characters long.`}}});
72
73
 
73
74
  var InaccessibleWidget = Rule.makeRule({name:"inaccessible-widget",severity:Rule.Severity.WARNING,selector:"widget",lint:function(state,content,nodes,match,context){const node=state.currentNode();const widgetType=node.widgetType;const widgetId=node.id;if(!widgetType||!widgetId){return}const widgetInfo=context?.widgets?.[widgetId];if(!widgetInfo){return}const accessible=CoreWidgetRegistry.isAccessible(widgetType,widgetInfo.options);if(!accessible){return {message:`The "${widgetType}" widget is not accessible.`,start:0,end:content.length,metadata:{widgetType:widgetType,widgetId:widgetId}}}}});
74
75
 
76
+ var InteractiveGraphWidgetError = Rule.makeRule({name:"interactive-graph-widget-error",severity:Rule.Severity.ERROR,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="interactive-graph"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context?.widgets?.[nodeId];if(!widget){return}const issues=[];const{lockedFigures,graph}=widget.options;for(const figure of lockedFigures??[]){if(figure.type==="line"&&vector.equal(figure.points[0].coord,figure.points[1].coord)){issues.push("Locked line cannot have length 0.");}if(figure.type==="polygon"){if(figure.points.every(point=>vector.equal(point,figure.points[0]))){issues.push("Locked polygon cannot have all coordinates be the same.");}}if(figure.type==="ellipse"){if(figure.radius[0]<=0||figure.radius[1]<=0){issues.push("Locked ellipse must have positive radius values.");}}}if(graph?.type==="polygon"&&graph.numSides==="unlimited"&&graph.coords==null){issues.push("Polygon must be closed.");}const allIssuesString=issues.join("\n\n");return allIssuesString}});
77
+
75
78
  var LabelImageWidgetError = Rule.makeRule({name:"label-image-widget-error",severity:Rule.Severity.ERROR,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="label-image"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context?.widgets?.[nodeId];if(!widget){return}const warnings=[];const{choices,imageAlt,imageUrl,markers}=widget.options;if(choices.length<2){warnings.push("Question requires at least two answer choices");}if(!imageUrl){warnings.push("Image is not specified for question");}else if(!imageAlt){warnings.push("Question image has no alt text");}if(!markers.length){warnings.push("Question has no markers, to label answers on image");}else {let numNoAnswers=0;let numNoLabels=0;for(const marker of markers){if(!marker.answers.length){numNoAnswers++;}if(!marker.label){numNoLabels++;}}if(numNoAnswers>0){warnings.push(`Question has ${numNoAnswers} markers with no answers selected`);}if(numNoLabels>0){warnings.push(`Question has ${numNoLabels} markers with no ARIA label`);}}const allWarningsString=warnings.join("\n\n");return allWarningsString}});
76
79
 
77
80
  var LinkClickHere = Rule.makeRule({name:"link-click-here",severity:Rule.Severity.WARNING,selector:"link",pattern:/click here/i,message:`Inappropriate link text:
@@ -134,7 +137,7 @@ Dollar signs must appear in pairs or be escaped as \\$`});
134
137
  var WidgetInTable = Rule.makeRule({name:"widget-in-table",severity:Rule.Severity.BULK_WARNING,selector:"table widget",message:`Widget in table:
135
138
  do not put widgets inside of tables.`});
136
139
 
137
- var AllRules = [AbsoluteUrl,DoubleSpacingAfterTerminal,ImageUrlEmpty,ExpressionWidget,ExtraContentSpacing,HeadingLevel1,HeadingLevelSkip,HeadingSentenceCase,HeadingTitleCase,ImageAltText,ImageMarkdown,ImageInTable,LinkClickHere,LongParagraph,MathAdjacent,MathAlignExtraBreak,MathAlignLinebreaks,MathEmpty,MathFrac,MathNested,MathStartsWithSpace,MathTextEmpty,NestedLists,StaticWidgetInQuestionStem,TableMissingCells,UnescapedDollar,WidgetInTable,MathWithoutDollars,UnbalancedCodeDelimiters,ImageSpacesAroundUrls,ImageWidget,InaccessibleWidget,RadioWidgetError,ExpressionWidgetError,FreeResponseWidgetError,MatcherWidgetError,NumericInputWidgetError,PhetSimulationWidgetError,PythonProgramWidgetError,LabelImageWidgetError];
140
+ var AllRules = [AbsoluteUrl,DoubleSpacingAfterTerminal,ImageUrlEmpty,ExpressionWidget,ExtraContentSpacing,HeadingLevel1,HeadingLevelSkip,HeadingSentenceCase,HeadingTitleCase,ImageAltText,ImageMarkdown,ImageInTable,LinkClickHere,LongParagraph,MathAdjacent,MathAlignExtraBreak,MathAlignLinebreaks,MathEmpty,MathFrac,MathNested,MathStartsWithSpace,MathTextEmpty,NestedLists,StaticWidgetInQuestionStem,TableMissingCells,UnescapedDollar,WidgetInTable,MathWithoutDollars,UnbalancedCodeDelimiters,ImageSpacesAroundUrls,ImageWidget,InaccessibleWidget,RadioWidgetError,ExpressionWidgetError,FreeResponseWidgetError,MatcherWidgetError,NumericInputWidgetError,PhetSimulationWidgetError,PythonProgramWidgetError,LabelImageWidgetError,InteractiveGraphWidgetError];
138
141
 
139
142
  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 PerseusError("Can't replace the root of the tree",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 PerseusError("goToPreviousSibling(): node has no previous sibling",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 PerseusError("goToParent(): node has no ancestor",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):[];}}
140
143