@khanacademy/perseus-linter 0.0.0-PR3050-20251121221126 → 0.0.0-PR3050-20251122003632
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 +13 -2
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/rules/interactive-graph-widget-error.d.ts +3 -0
- package/dist/rules/label-image-widget-error.d.ts +3 -0
- package/dist/rules/numeric-input-widget-error.d.ts +3 -0
- package/dist/rules/phet-simulation-widget-error.d.ts +3 -0
- package/dist/rules/python-program-widget-error.d.ts +3 -0
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ var perseusUtils = require('@khanacademy/perseus-utils');
|
|
|
6
6
|
var perseusCore = require('@khanacademy/perseus-core');
|
|
7
7
|
var KAS = require('@khanacademy/kas');
|
|
8
8
|
var pureMarkdown = require('@khanacademy/pure-markdown');
|
|
9
|
+
var kmath = require('@khanacademy/kmath');
|
|
9
10
|
|
|
10
11
|
function _interopNamespaceCompat(e) {
|
|
11
12
|
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
@@ -96,6 +97,10 @@ This image's alt text is only ${alt.trim().length} characters long.`}}});
|
|
|
96
97
|
|
|
97
98
|
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=perseusCore.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}}}}});
|
|
98
99
|
|
|
100
|
+
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{correct,lockedFigures}=widget.options;for(const figure of lockedFigures??[]){if(figure.type==="line"&&kmath.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=>kmath.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(correct?.type==="polygon"&&correct.numSides==="unlimited"&&correct.coords==null){issues.push("Polygon must be closed.");}const allIssuesString=issues.join("\n\n");return allIssuesString}});
|
|
101
|
+
|
|
102
|
+
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("label-image widget must have at least two answer choices");}if(!imageUrl){warnings.push("No image url provided");}else if(!imageAlt){warnings.push("No image alt text provided");}if(!markers.length){warnings.push("label-image widget requires at least one marker");}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(`label-image widget has ${numNoAnswers} markers with no answers selected`);}if(numNoLabels>0){warnings.push(`label-image widget has ${numNoLabels} markers with no ARIA label`);}}const allWarningsString=warnings.join("\n\n");return allWarningsString}});
|
|
103
|
+
|
|
99
104
|
var LinkClickHere = Rule.makeRule({name:"link-click-here",severity:Rule.Severity.WARNING,selector:"link",pattern:/click here/i,message:`Inappropriate link text:
|
|
100
105
|
Do not use the words "click here" in links.`});
|
|
101
106
|
|
|
@@ -133,6 +138,12 @@ var NestedLists = Rule.makeRule({name:"nested-lists",severity:Rule.Severity.WARN
|
|
|
133
138
|
nested lists are hard to read on mobile devices;
|
|
134
139
|
do not use additional indentation.`});
|
|
135
140
|
|
|
141
|
+
var NumericInputWidgetError = Rule.makeRule({name:"numeric-input-widget-error",severity:Rule.Severity.ERROR,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="numeric-input"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context?.widgets?.[nodeId];if(!widget){return}const issues=[];const answers=widget.options.answers;if(answers.some(answer=>answer.value==null)){issues.push("One or more answers is empty");}answers.forEach((answer,i)=>{const formatError=answer.strict&&(!answer.answerForms||answer.answerForms.length===0);if(formatError){issues.push(`Answer ${i+1} requires a format, but no format was selected`);}});const allWarningsString=issues.join("\n\n");return allWarningsString}});
|
|
142
|
+
|
|
143
|
+
var PhetSimulationWidgetError = Rule.makeRule({name:"phet-simulation-widget-error",severity:Rule.Severity.ERROR,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="phet-simulation"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context?.widgets?.[nodeId];if(!widget){return}if(perseusCore.makeSafeUrl(widget.options.url,"en","https://phet.colorado.edu")===null){return "The URL is not from the PhET domain."}}});
|
|
144
|
+
|
|
145
|
+
var PythonProgramWidgetError = Rule.makeRule({name:"python-program-widget-error",severity:Rule.Severity.ERROR,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="python-program"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context?.widgets?.[nodeId];if(!widget){return}const errors=[];const height=widget.options.height;const programID=widget.options.programID;if(programID===""){errors.push("The program ID is required.");}if(!Number.isInteger(height)||height<1){errors.push("The height must be a positive integer.");}const allErrorsString=errors.join("\n\n");return allErrorsString}});
|
|
146
|
+
|
|
136
147
|
var RadioWidgetError = Rule.makeRule({name:"radio-widget-error",severity:Rule.Severity.ERROR,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="radio"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context?.widgets?.[nodeId];if(!widget){return}const choices=widget.options.choices;if(!choices.some(choice=>choice.correct)){return "No choice is marked as correct."}}});
|
|
137
148
|
|
|
138
149
|
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).`}}});
|
|
@@ -150,7 +161,7 @@ Dollar signs must appear in pairs or be escaped as \\$`});
|
|
|
150
161
|
var WidgetInTable = Rule.makeRule({name:"widget-in-table",severity:Rule.Severity.BULK_WARNING,selector:"table widget",message:`Widget in table:
|
|
151
162
|
do not put widgets inside of tables.`});
|
|
152
163
|
|
|
153
|
-
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];
|
|
164
|
+
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];
|
|
154
165
|
|
|
155
166
|
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):[];}}
|
|
156
167
|
|