@@ -4,7 +4,11 @@ import type { AST } from "jsonc-eslint-parser";
44import { getStaticJSONValue } from "jsonc-eslint-parser" ;
55import type { SourceCode } from "eslint" ;
66import type { AroundTarget } from "../utils/fix-sort-elements" ;
7- import { fixForSorting } from "../utils/fix-sort-elements" ;
7+ import {
8+ fixToMoveDownForSorting ,
9+ fixToMoveUpForSorting ,
10+ } from "../utils/fix-sort-elements" ;
11+ import { calcShortestEditScript } from "../utils/calc-shortest-edit-script" ;
812
913type JSONValue = ReturnType < typeof getStaticJSONValue > ;
1014
@@ -372,8 +376,10 @@ export default createRule<UserOptions>("sort-array-values", {
372376 } ,
373377
374378 messages : {
375- sortValues :
376- "Expected array values to be in {{orderText}} order. '{{thisValue}}' should be before '{{prevValue}}'." ,
379+ shouldBeBefore :
380+ "Expected array values to be in {{orderText}} order. '{{thisValue}}' should be before '{{targetValue}}'." ,
381+ shouldBeAfter :
382+ "Expected array values to be in {{orderText}} order. '{{thisValue}}' should be after '{{targetValue}}'." ,
377383 } ,
378384 type : "suggestion" ,
379385 } ,
@@ -386,48 +392,154 @@ export default createRule<UserOptions>("sort-array-values", {
386392 const parsedOptions = parseOptions ( context . options ) ;
387393
388394 /**
389- * Verify for array element
395+ * Sort elements by bubble sort.
390396 */
391- function verifyArrayElement ( data : JSONElementData , option : ParsedOption ) {
392- if ( option . ignore ( data ) ) {
393- return ;
397+ function bubbleSort ( elements : JSONElementData [ ] , option : ParsedOption ) {
398+ const l = elements . length ;
399+ const result = [ ...elements ] ;
400+ let swapped : boolean ;
401+ do {
402+ swapped = false ;
403+ for ( let nextIndex = 1 ; nextIndex < l ; nextIndex ++ ) {
404+ const prevIndex = nextIndex - 1 ;
405+ if ( option . isValidOrder ( result [ prevIndex ] , result [ nextIndex ] ) )
406+ continue ;
407+ [ result [ prevIndex ] , result [ nextIndex ] ] = [
408+ result [ nextIndex ] ,
409+ result [ prevIndex ] ,
410+ ] ;
411+ swapped = true ;
412+ }
413+ } while ( swapped ) ;
414+ return result ;
415+ }
416+
417+ /**
418+ * Verify for array elements
419+ */
420+ function verifyArrayElements (
421+ elements : JSONElementData [ ] ,
422+ option : ParsedOption ,
423+ ) {
424+ const sorted = bubbleSort ( elements , option ) ;
425+ const editScript = calcShortestEditScript ( elements , sorted ) ;
426+ for ( let index = 0 ; index < editScript . length ; index ++ ) {
427+ const edit = editScript [ index ] ;
428+ if ( edit . type !== "delete" ) continue ;
429+ const insertEditIndex = editScript . findIndex (
430+ ( e ) => e . type === "insert" && e . b === edit . a ,
431+ ) ;
432+ if ( insertEditIndex === - 1 ) {
433+ // should not happen
434+ continue ;
435+ }
436+ if ( index < insertEditIndex ) {
437+ const target = findInsertAfterTarget ( edit . a , insertEditIndex ) ;
438+ if ( ! target ) {
439+ // should not happen
440+ continue ;
441+ }
442+ context . report ( {
443+ loc : edit . a . reportLoc ,
444+ messageId : "shouldBeAfter" ,
445+ data : {
446+ thisValue : toText ( edit . a ) ,
447+ targetValue : toText ( target ) ,
448+ orderText : option . orderText ( edit . a ) ,
449+ } ,
450+ * fix ( fixer ) {
451+ yield * fixToMoveDownForSorting (
452+ fixer ,
453+ sourceCode ,
454+ edit . a . around ,
455+ target . around ,
456+ ) ;
457+ } ,
458+ } ) ;
459+ } else {
460+ const target = findInsertBeforeTarget ( edit . a , insertEditIndex ) ;
461+ if ( ! target ) {
462+ // should not happen
463+ continue ;
464+ }
465+ context . report ( {
466+ loc : edit . a . reportLoc ,
467+ messageId : "shouldBeBefore" ,
468+ data : {
469+ thisValue : toText ( edit . a ) ,
470+ targetValue : toText ( target ) ,
471+ orderText : option . orderText ( edit . a ) ,
472+ } ,
473+ * fix ( fixer ) {
474+ yield * fixToMoveUpForSorting (
475+ fixer ,
476+ sourceCode ,
477+ edit . a . around ,
478+ target . around ,
479+ ) ;
480+ } ,
481+ } ) ;
482+ }
394483 }
395- const prevList = data . array . elements
396- . slice ( 0 , data . index )
397- . reverse ( )
398- . filter ( ( d ) => ! option . ignore ( d ) ) ;
399484
400- if ( prevList . length === 0 ) {
401- return ;
485+ /**
486+ * Find insert after target
487+ */
488+ function findInsertAfterTarget (
489+ element : JSONElementData ,
490+ insertEditIndex : number ,
491+ ) {
492+ for ( let index = insertEditIndex - 1 ; index >= 0 ; index -- ) {
493+ const edit = editScript [ index ] ;
494+ if ( edit . type === "delete" && edit . a === element ) break ;
495+ if ( edit . type !== "common" ) continue ;
496+ return edit . a ;
497+ }
498+
499+ let lastTarget : JSONElementData | null = null ;
500+ for (
501+ let index = elements . indexOf ( element ) + 1 ;
502+ index < elements . length ;
503+ index ++
504+ ) {
505+ const el = elements [ index ] ;
506+ if ( option . isValidOrder ( el , element ) ) {
507+ lastTarget = el ;
508+ continue ;
509+ }
510+ return lastTarget ;
511+ }
512+ return lastTarget ;
402513 }
403- const prev = prevList [ 0 ] ;
404- if ( ! option . isValidOrder ( prev , data ) ) {
405- const reportLoc = data . reportLoc ;
406- context . report ( {
407- loc : reportLoc ,
408- messageId : "sortValues" ,
409- data : {
410- thisValue : toText ( data ) ,
411- prevValue : toText ( prev ) ,
412- orderText : option . orderText ( data ) ,
413- } ,
414- fix ( fixer ) {
415- let moveTarget = prevList [ 0 ] ;
416- for ( const prev of prevList ) {
417- if ( option . isValidOrder ( prev , data ) ) {
418- break ;
419- } else {
420- moveTarget = prev ;
421- }
422- }
423- return fixForSorting (
424- fixer ,
425- sourceCode ,
426- data . around ,
427- moveTarget . around ,
428- ) ;
429- } ,
430- } ) ;
514+
515+ /**
516+ * Find insert before target
517+ */
518+ function findInsertBeforeTarget (
519+ element : JSONElementData ,
520+ insertEditIndex : number ,
521+ ) {
522+ for (
523+ let index = insertEditIndex + 1 ;
524+ index < editScript . length ;
525+ index ++
526+ ) {
527+ const edit = editScript [ index ] ;
528+ if ( edit . type === "delete" && edit . a === element ) break ;
529+ if ( edit . type !== "common" ) continue ;
530+ return edit . a ;
531+ }
532+
533+ let lastTarget : JSONElementData | null = null ;
534+ for ( let index = elements . indexOf ( element ) - 1 ; index >= 0 ; index -- ) {
535+ const el = elements [ index ] ;
536+ if ( option . isValidOrder ( element , el ) ) {
537+ lastTarget = el ;
538+ continue ;
539+ }
540+ return lastTarget ;
541+ }
542+ return lastTarget ;
431543 }
432544 }
433545
@@ -448,9 +560,10 @@ export default createRule<UserOptions>("sort-array-values", {
448560 if ( ! option ) {
449561 return ;
450562 }
451- for ( const element of data . elements ) {
452- verifyArrayElement ( element , option ) ;
453- }
563+ verifyArrayElements (
564+ data . elements . filter ( ( d ) => ! option . ignore ( d ) ) ,
565+ option ,
566+ ) ;
454567 } ,
455568 } ;
456569 } ,
0 commit comments