@@ -136,14 +136,41 @@ if (typeof IS_MINIFIED !== 'undefined') {
136136 ) ;
137137 } ;
138138
139+ /**
140+ * Takes a message and a p5 function func, and adds a link pointing to
141+ * the reference documentation of func at the end of the message
142+ *
143+ * @method mapToReference
144+ * @private
145+ * @param {String } message the words to be said
146+ * @param {String } [func] the name of the function to link
147+ *
148+ * @returns {String }
149+ */
150+ const mapToReference = ( message , func ) => {
151+ let msgWithReference = '' ;
152+ if ( func == null || func . substring ( 0 , 4 ) === 'load' ) {
153+ msgWithReference = message ;
154+ } else {
155+ const methodParts = func . split ( '.' ) ;
156+ const referenceSection =
157+ methodParts . length > 1 ? `${ methodParts [ 0 ] } .${ methodParts [ 1 ] } ` : 'p5' ;
158+
159+ const funcName =
160+ methodParts . length === 1 ? func : methodParts . slice ( 2 ) . join ( '/' ) ;
161+ msgWithReference = `${ message } (http://p5js.org/reference/#/${ referenceSection } /${ funcName } )` ;
162+ }
163+ return msgWithReference ;
164+ } ;
165+
139166 /**
140167 * Prints out a fancy, colorful message to the console log
141168 *
142169 * @method report
143170 * @private
144171 * @param {String } message the words to be said
145- * @param {String } func the name of the function to link
146- * @param {Number|String } color CSS color string or error type
172+ * @param {String } [ func] the name of the function to link
173+ * @param {Number|String } [ color] CSS color string or error type
147174 *
148175 * @return console logs
149176 */
@@ -164,22 +191,11 @@ if (typeof IS_MINIFIED !== 'undefined') {
164191 color = typeColors [ color ] ;
165192 }
166193
167- let prefixedMsg ;
194+ // Add a link to the reference docs of func at the end of the message
195+ message = mapToReference ( message , func ) ;
168196 let style = [ `color: ${ color } ` , 'font-family: Arial' , 'font-size: larger' ] ;
169- if ( func == null || func . substring ( 0 , 4 ) === 'load' ) {
170- prefixedMsg = translator ( 'fes.pre' , { message } ) ;
171- } else {
172- const methodParts = func . split ( '.' ) ;
173- const referenceSection =
174- methodParts . length > 1 ? `${ methodParts [ 0 ] } .${ methodParts [ 1 ] } ` : 'p5' ;
175-
176- const funcName =
177- methodParts . length === 1 ? func : methodParts . slice ( 2 ) . join ( '/' ) ;
197+ const prefixedMsg = translator ( 'fes.pre' , { message } ) ;
178198
179- prefixedMsg = translator ( 'fes.pre' , {
180- message : `${ message } (http://p5js.org/reference/#/${ referenceSection } /${ funcName } )`
181- } ) ;
182- }
183199 if ( ENABLE_FES_STYLING ) {
184200 log ( '%c' + prefixedMsg , style . join ( ';' ) ) ;
185201 } else {
@@ -193,7 +209,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
193209 * @method _friendlyError
194210 * @private
195211 * @param {Number } message message to be printed
196- * @param {String } method name of method
212+ * @param {String } [ method] name of method
197213 * @param {Number|String } [color] CSS color string or error type (Optional)
198214 */
199215 p5 . _friendlyError = function ( message , method , color ) {
@@ -304,25 +320,27 @@ if (typeof IS_MINIFIED !== 'undefined') {
304320 defineMisusedAtTopLevelCode ( ) ;
305321 }
306322
307- let min = 999999 ,
308- minIndex = 0 ;
323+ const distanceMap = { } ;
324+ let min = 999999 ;
309325 // compute the levenshtein distance for the symbol against all known
310326 // public p5 properties. Find the property with the minimum distance
311- misusedAtTopLevelCode . forEach ( ( symbol , idx ) => {
327+ misusedAtTopLevelCode . forEach ( symbol => {
312328 let dist = computeEditDistance ( errSym , symbol . name ) ;
313- if ( dist < min ) {
314- min = dist ;
315- minIndex = idx ;
316- }
329+ if ( distanceMap [ dist ] ) distanceMap [ dist ] . push ( symbol ) ;
330+ else distanceMap [ dist ] = [ symbol ] ;
331+
332+ if ( dist < min ) min = dist ;
317333 } ) ;
318334
335+ // if the closest match has more "distance" than the max allowed threshold
319336 if ( min > Math . min ( EDIT_DIST_THRESHOLD , errSym . length ) ) return false ;
320337
321- let symbol = misusedAtTopLevelCode [ minIndex ] ;
322-
323338 // Show a message only if the caught symbol and the matched property name
324339 // differ in their name ( either letter difference or difference of case )
325- if ( errSym !== symbol . name ) {
340+ const matchedSymbols = distanceMap [ min ] . filter (
341+ symbol => symbol . name !== errSym
342+ ) ;
343+ if ( matchedSymbols . length !== 0 ) {
326344 const parsed = p5 . _getErrorStackParser ( ) . parse ( error ) ;
327345 let locationObj ;
328346 if (
@@ -336,18 +354,51 @@ if (typeof IS_MINIFIED !== 'undefined') {
336354 location : `${ parsed [ 0 ] . fileName } :${ parsed [ 0 ] . lineNumber } :${
337355 parsed [ 0 ] . columnNumber
338356 } `,
339- file : parsed [ 0 ] . fileName ,
357+ file : parsed [ 0 ] . fileName . split ( '/' ) . slice ( - 1 ) ,
340358 line : parsed [ 0 ] . lineNumber
341359 } ;
342360 }
343- const msg = translator ( 'fes.misspelling' , {
344- name : errSym ,
345- actualName : symbol . name ,
346- type : symbol . type ,
347- location : locationObj ? translator ( 'fes.location' , locationObj ) : ''
348- } ) ;
349361
350- p5 . _friendlyError ( msg , symbol . name ) ;
362+ let msg ;
363+ if ( matchedSymbols . length === 1 ) {
364+ // To be used when there is only one closest match. The count parameter
365+ // allows i18n to pick between the keys "fes.misspelling" and
366+ // "fes.misspelling__plural"
367+ msg = translator ( 'fes.misspelling' , {
368+ name : errSym ,
369+ actualName : matchedSymbols [ 0 ] . name ,
370+ type : matchedSymbols [ 0 ] . type ,
371+ location : locationObj ? translator ( 'fes.location' , locationObj ) : '' ,
372+ count : matchedSymbols . length
373+ } ) ;
374+ } else {
375+ // To be used when there are multiple closest matches. Gives each
376+ // suggestion on its own line, the function name followed by a link to
377+ // reference documentation
378+ const suggestions = matchedSymbols
379+ . map ( symbol => {
380+ const message =
381+ '▶️ ' + symbol . name + ( symbol . type === 'function' ? '()' : '' ) ;
382+ return mapToReference ( message , symbol . name ) ;
383+ } )
384+ . join ( '\n' ) ;
385+
386+ msg = translator ( 'fes.misspelling' , {
387+ name : errSym ,
388+ suggestions : suggestions ,
389+ location : locationObj ? translator ( 'fes.location' , locationObj ) : '' ,
390+ count : matchedSymbols . length
391+ } ) ;
392+ }
393+
394+ // If there is only one closest match, tell _friendlyError to also add
395+ // a link to the reference documentation. In case of multiple matches,
396+ // this is already done in the suggestions variable, one link for each
397+ // suggestion.
398+ p5 . _friendlyError (
399+ msg ,
400+ matchedSymbols . length === 1 ? matchedSymbols [ 0 ] . name : undefined
401+ ) ;
351402 return true ;
352403 }
353404 return false ;
0 commit comments