@khanacademy/perseus-linter 0.0.0-PR3050-20251119185619 → 0.0.0-PR3050-20251121221126
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 +11 -2
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +31 -2
- package/dist/index.js.map +1 -1
- package/dist/rules/expression-widget-error.d.ts +3 -0
- package/dist/rules/free-response-widget-error.d.ts +3 -0
- package/dist/rules/matcher-widget-error.d.ts +3 -0
- package/dist/rules/radio-widget-error.d.ts +3 -0
- package/package.json +3 -2
package/dist/es/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-utils';
|
|
2
2
|
import { PerseusError, Errors, CoreWidgetRegistry } from '@khanacademy/perseus-core';
|
|
3
|
+
import * as KAS from '@khanacademy/kas';
|
|
3
4
|
import { parse, traverseContent } from '@khanacademy/pure-markdown';
|
|
4
5
|
|
|
5
|
-
const libName="@khanacademy/perseus-linter";const libVersion="4.
|
|
6
|
+
const libName="@khanacademy/perseus-linter";const libVersion="4.5.0";addLibraryVersionToPerseusDebug(libName,libVersion);
|
|
6
7
|
|
|
7
8
|
const linterContextDefault={contentType:"",highlightLint:false,paths:[],stack:[]};
|
|
8
9
|
|
|
@@ -24,8 +25,12 @@ any other kind of terminal punctuation.`});
|
|
|
24
25
|
|
|
25
26
|
function buttonNotInButtonSet(name,set){return `Answer requires a button not found in the button sets: ${name} (in ${set})`}const stringToButtonSet={"\\sqrt":"prealgebra","\\sin":"trig","\\cos":"trig","\\tan":"trig","\\log":"logarithms","\\ln":"logarithms"};var ExpressionWidget = Rule.makeRule({name:"expression-widget",severity:Rule.Severity.WARNING,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="expression"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context?.widgets?.[nodeId];if(!widget){return}const answers=widget.options.answerForms;const buttons=widget.options.buttonSets;for(const answer of answers){for(const[str,set]of Object.entries(stringToButtonSet)){if(answer.value.includes(str)&&!buttons.includes(set)){return buttonNotInButtonSet(str,set)}}}}});
|
|
26
27
|
|
|
28
|
+
var ExpressionWidgetError = Rule.makeRule({name:"expression-widget-error",severity:Rule.Severity.ERROR,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="expression"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context&&context.widgets&&context.widgets[nodeId];if(!widget){return}const issues=[];const{answerForms,functions}=widget.options;if(answerForms.length===0){issues.push("No answers specified.");}else {const hasCorrect=answerForms.some(form=>{return form.considered==="correct"});if(!hasCorrect){issues.push("No correct answer specified.");}answerForms.forEach((form,ix)=>{if(form.value===""){issues.push(`Answer ${ix+1} is empty.`);}else {const expression=KAS.parse(form.value,{functions:functions});if(!expression.parsed){issues.push(`Couldn't parse ${form.value}.`);}else if(form.simplify&&!expression.expr.isSimplified()){issues.push(`${form.value} isn't simplified, but is required to be.`);}}});}const allWarningsString=issues.join("\n\n");return allWarningsString}});
|
|
29
|
+
|
|
27
30
|
var ExtraContentSpacing = Rule.makeRule({name:"extra-content-spacing",selector:"paragraph",pattern:/\s+$/,applies:function(context){return context?.contentType==="article"},message:`No extra whitespace at the end of content blocks.`});
|
|
28
31
|
|
|
32
|
+
const rWidgetRule=/\[\[\u2603 (([a-z-]+) ([0-9]+))\]\]/;var FreeResponseWidgetError = Rule.makeRule({name:"free-response-widget-error",severity:Rule.Severity.ERROR,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="free-response"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context&&context.widgets&&context.widgets[nodeId];if(!widget){return}const question=widget.options.question;if(!question){return "The question is empty"}if(question.match(rWidgetRule)!=null){return "The question contains a widget"}}});
|
|
33
|
+
|
|
29
34
|
var HeadingLevel1 = Rule.makeRule({name:"heading-level-1",severity:Rule.Severity.WARNING,selector:"heading",lint:function(state,content,nodes,match){if(nodes[0].level===1){return `Don't use level-1 headings:
|
|
30
35
|
Begin headings with two or more # characters.`}}});
|
|
31
36
|
|
|
@@ -74,6 +79,8 @@ var LongParagraph = Rule.makeRule({name:"long-paragraph",severity:Rule.Severity.
|
|
|
74
79
|
This paragraph is ${content.length} characters long.
|
|
75
80
|
Shorten it to 500 characters or fewer.`}});
|
|
76
81
|
|
|
82
|
+
var MatcherWidgetError = Rule.makeRule({name:"matcher-widget-error",severity:Rule.Severity.ERROR,selector:"widget",lint:function(state,content,nodes,match,context){if(state.currentNode().widgetType!=="matcher"){return}const nodeId=state.currentNode().id;if(!nodeId){return}const widget=context?.widgets?.[nodeId];if(!widget){return}if(widget.options.left.length!==widget.options.right.length){return "The two halves of the matcher have different numbers of cards."}}});
|
|
83
|
+
|
|
77
84
|
var MathAdjacent = Rule.makeRule({name:"math-adjacent",severity:Rule.Severity.WARNING,selector:"blockMath+blockMath",message:`Adjacent math blocks:
|
|
78
85
|
combine the blocks between \\begin{align} and \\end{align}`});
|
|
79
86
|
|
|
@@ -102,6 +109,8 @@ var NestedLists = Rule.makeRule({name:"nested-lists",severity:Rule.Severity.WARN
|
|
|
102
109
|
nested lists are hard to read on mobile devices;
|
|
103
110
|
do not use additional indentation.`});
|
|
104
111
|
|
|
112
|
+
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."}}});
|
|
113
|
+
|
|
105
114
|
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).`}}});
|
|
106
115
|
|
|
107
116
|
var TableMissingCells = Rule.makeRule({name:"table-missing-cells",severity:Rule.Severity.WARNING,selector:"table",lint:function(state,content,nodes,match){const table=nodes[0];const headerLength=table.header.length;const rowLengths=table.cells.map(r=>r.length);for(let r=0;r<rowLengths.length;r++){if(rowLengths[r]!==headerLength){return `Table rows don't match header:
|
|
@@ -117,7 +126,7 @@ Dollar signs must appear in pairs or be escaped as \\$`});
|
|
|
117
126
|
var WidgetInTable = Rule.makeRule({name:"widget-in-table",severity:Rule.Severity.BULK_WARNING,selector:"table widget",message:`Widget in table:
|
|
118
127
|
do not put widgets inside of tables.`});
|
|
119
128
|
|
|
120
|
-
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];
|
|
129
|
+
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];
|
|
121
130
|
|
|
122
131
|
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):[];}}
|
|
123
132
|
|