esupgrade 2025.1.0 → 2025.2.0
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/AGENTS.md +3 -0
- package/README.md +48 -0
- package/bin/esupgrade.js +47 -30
- package/package.json +1 -1
- package/src/widelyAvailable.js +404 -0
- package/tests/cli.test.js +13 -17
- package/tests/widelyAvailable.test.js +1031 -0
|
@@ -521,6 +521,77 @@ var x = 1;`
|
|
|
521
521
|
})
|
|
522
522
|
})
|
|
523
523
|
|
|
524
|
+
describe("Math.pow() to exponentiation operator", () => {
|
|
525
|
+
test("Math.pow() to **", () => {
|
|
526
|
+
const input = `const result = Math.pow(2, 3);`
|
|
527
|
+
|
|
528
|
+
const result = transform(input)
|
|
529
|
+
|
|
530
|
+
assert.strictEqual(result.modified, true)
|
|
531
|
+
assert.match(result.code, /2 \*\* 3/)
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
test("Math.pow() with variables", () => {
|
|
535
|
+
const input = `const power = Math.pow(base, exponent);`
|
|
536
|
+
|
|
537
|
+
const result = transform(input)
|
|
538
|
+
|
|
539
|
+
assert.strictEqual(result.modified, true)
|
|
540
|
+
assert.match(result.code, /base \*\* exponent/)
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
test("Math.pow() with complex expressions", () => {
|
|
544
|
+
const input = `const result = Math.pow(x + 1, y * 2);`
|
|
545
|
+
|
|
546
|
+
const result = transform(input)
|
|
547
|
+
|
|
548
|
+
assert.strictEqual(result.modified, true)
|
|
549
|
+
assert.match(result.code, /\(x \+ 1\) \*\* \(y \* 2\)/)
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
test("Math.pow() in expressions", () => {
|
|
553
|
+
const input = `const area = Math.PI * Math.pow(radius, 2);`
|
|
554
|
+
|
|
555
|
+
const result = transform(input)
|
|
556
|
+
|
|
557
|
+
assert.strictEqual(result.modified, true)
|
|
558
|
+
assert.match(result.code, /Math\.PI \* radius \*\* 2/)
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
test("Math.pow() should not transform with wrong number of arguments", () => {
|
|
562
|
+
const input = `const result = Math.pow(2);`
|
|
563
|
+
|
|
564
|
+
const result = transform(input)
|
|
565
|
+
|
|
566
|
+
assert.strictEqual(result.modified, false)
|
|
567
|
+
assert.match(result.code, /Math\.pow\(2\)/)
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
test("Math.pow() nested calls", () => {
|
|
571
|
+
const input = `const result = Math.pow(Math.pow(2, 3), 4);`
|
|
572
|
+
|
|
573
|
+
const result = transform(input)
|
|
574
|
+
|
|
575
|
+
assert.strictEqual(result.modified, true)
|
|
576
|
+
// Only the outer Math.pow is transformed in a single pass
|
|
577
|
+
assert.match(result.code, /Math\.pow\(2, 3\) \*\* 4/)
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
test("Math.pow() tracks line numbers", () => {
|
|
581
|
+
const input = `// Line 1
|
|
582
|
+
const result = Math.pow(2, 3);`
|
|
583
|
+
|
|
584
|
+
const result = transform(input)
|
|
585
|
+
|
|
586
|
+
assert.strictEqual(result.modified, true)
|
|
587
|
+
const mathPowChanges = result.changes.filter(
|
|
588
|
+
(c) => c.type === "mathPowToExponentiation",
|
|
589
|
+
)
|
|
590
|
+
assert.strictEqual(mathPowChanges.length, 1)
|
|
591
|
+
assert.strictEqual(mathPowChanges[0].line, 2)
|
|
592
|
+
})
|
|
593
|
+
})
|
|
594
|
+
|
|
524
595
|
test("no changes needed", () => {
|
|
525
596
|
const input = `
|
|
526
597
|
const x = 1;
|
|
@@ -562,4 +633,964 @@ var x = 1;`
|
|
|
562
633
|
assert.strictEqual(result.modified, true)
|
|
563
634
|
assert.match(result.code, /const x = 1/)
|
|
564
635
|
})
|
|
636
|
+
|
|
637
|
+
describe("traditional for loop to for...of", () => {
|
|
638
|
+
test("basic for loop with array indexing", () => {
|
|
639
|
+
const input = `
|
|
640
|
+
for (let i = 0; i < items.length; i++) {
|
|
641
|
+
const item = items[i];
|
|
642
|
+
console.log(item);
|
|
643
|
+
}
|
|
644
|
+
`
|
|
645
|
+
|
|
646
|
+
const result = transform(input)
|
|
647
|
+
|
|
648
|
+
assert.strictEqual(result.modified, true)
|
|
649
|
+
assert.match(result.code, /for \(const item of items\)/)
|
|
650
|
+
assert.match(result.code, /console\.log\(item\)/)
|
|
651
|
+
assert.doesNotMatch(result.code, /items\[i\]/)
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
test("for loop with const variable", () => {
|
|
655
|
+
const input = `
|
|
656
|
+
for (let i = 0; i < arr.length; i++) {
|
|
657
|
+
const element = arr[i];
|
|
658
|
+
process(element);
|
|
659
|
+
}
|
|
660
|
+
`
|
|
661
|
+
|
|
662
|
+
const result = transform(input)
|
|
663
|
+
|
|
664
|
+
assert.strictEqual(result.modified, true)
|
|
665
|
+
assert.match(result.code, /for \(const element of arr\)/)
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
test("for loop with let variable", () => {
|
|
669
|
+
const input = `
|
|
670
|
+
for (let i = 0; i < arr.length; i++) {
|
|
671
|
+
let element = arr[i];
|
|
672
|
+
element = transform(element);
|
|
673
|
+
console.log(element);
|
|
674
|
+
}
|
|
675
|
+
`
|
|
676
|
+
|
|
677
|
+
const result = transform(input)
|
|
678
|
+
|
|
679
|
+
assert.strictEqual(result.modified, true)
|
|
680
|
+
assert.match(result.code, /for \(let element of arr\)/)
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
test("for loop with var variable", () => {
|
|
684
|
+
const input = `
|
|
685
|
+
for (let i = 0; i < arr.length; i++) {
|
|
686
|
+
var element = arr[i];
|
|
687
|
+
console.log(element);
|
|
688
|
+
}
|
|
689
|
+
`
|
|
690
|
+
|
|
691
|
+
const result = transform(input)
|
|
692
|
+
|
|
693
|
+
assert.strictEqual(result.modified, true)
|
|
694
|
+
// Note: var is also converted to const by the varToConst transformer
|
|
695
|
+
assert.match(result.code, /for \(const element of arr\)/)
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
test("should NOT transform when index is used in body", () => {
|
|
699
|
+
const input = `
|
|
700
|
+
for (let i = 0; i < items.length; i++) {
|
|
701
|
+
const item = items[i];
|
|
702
|
+
console.log(item, i);
|
|
703
|
+
}
|
|
704
|
+
`
|
|
705
|
+
|
|
706
|
+
const result = transform(input)
|
|
707
|
+
|
|
708
|
+
assert.strictEqual(result.modified, false)
|
|
709
|
+
assert.match(result.code, /for \(let i = 0; i < items\.length; i\+\+\)/)
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
test("should NOT transform when no array access statement", () => {
|
|
713
|
+
const input = `
|
|
714
|
+
for (let i = 0; i < items.length; i++) {
|
|
715
|
+
console.log(i);
|
|
716
|
+
}
|
|
717
|
+
`
|
|
718
|
+
|
|
719
|
+
const result = transform(input)
|
|
720
|
+
|
|
721
|
+
assert.strictEqual(result.modified, false)
|
|
722
|
+
assert.match(result.code, /for \(let i = 0/)
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
test("should NOT transform when body is empty", () => {
|
|
726
|
+
const input = `
|
|
727
|
+
for (let i = 0; i < items.length; i++) {
|
|
728
|
+
}
|
|
729
|
+
`
|
|
730
|
+
|
|
731
|
+
const result = transform(input)
|
|
732
|
+
|
|
733
|
+
assert.strictEqual(result.modified, false)
|
|
734
|
+
})
|
|
735
|
+
|
|
736
|
+
test("should NOT transform when using different increment", () => {
|
|
737
|
+
const input = `
|
|
738
|
+
for (let i = 0; i < items.length; i += 2) {
|
|
739
|
+
const item = items[i];
|
|
740
|
+
console.log(item);
|
|
741
|
+
}
|
|
742
|
+
`
|
|
743
|
+
|
|
744
|
+
const result = transform(input)
|
|
745
|
+
|
|
746
|
+
assert.strictEqual(result.modified, false)
|
|
747
|
+
assert.match(result.code, /i \+= 2/)
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
test("should NOT transform when starting from non-zero", () => {
|
|
751
|
+
const input = `
|
|
752
|
+
for (let i = 1; i < items.length; i++) {
|
|
753
|
+
const item = items[i];
|
|
754
|
+
console.log(item);
|
|
755
|
+
}
|
|
756
|
+
`
|
|
757
|
+
|
|
758
|
+
const result = transform(input)
|
|
759
|
+
|
|
760
|
+
assert.strictEqual(result.modified, false)
|
|
761
|
+
assert.match(result.code, /let i = 1/)
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
test("should NOT transform when using <= instead of <", () => {
|
|
765
|
+
const input = `
|
|
766
|
+
for (let i = 0; i <= items.length; i++) {
|
|
767
|
+
const item = items[i];
|
|
768
|
+
console.log(item);
|
|
769
|
+
}
|
|
770
|
+
`
|
|
771
|
+
|
|
772
|
+
const result = transform(input)
|
|
773
|
+
|
|
774
|
+
assert.strictEqual(result.modified, false)
|
|
775
|
+
assert.match(result.code, /i <= items\.length/)
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
test("should NOT transform when accessing different array", () => {
|
|
779
|
+
const input = `
|
|
780
|
+
for (let i = 0; i < items.length; i++) {
|
|
781
|
+
const item = otherArray[i];
|
|
782
|
+
console.log(item);
|
|
783
|
+
}
|
|
784
|
+
`
|
|
785
|
+
|
|
786
|
+
const result = transform(input)
|
|
787
|
+
|
|
788
|
+
assert.strictEqual(result.modified, false)
|
|
789
|
+
assert.match(result.code, /otherArray\[i\]/)
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
test("should NOT transform when first statement is not variable declaration", () => {
|
|
793
|
+
const input = `
|
|
794
|
+
for (let i = 0; i < items.length; i++) {
|
|
795
|
+
console.log(items[i]);
|
|
796
|
+
}
|
|
797
|
+
`
|
|
798
|
+
|
|
799
|
+
const result = transform(input)
|
|
800
|
+
|
|
801
|
+
assert.strictEqual(result.modified, false)
|
|
802
|
+
assert.match(result.code, /for \(let i = 0/)
|
|
803
|
+
})
|
|
804
|
+
|
|
805
|
+
test("should NOT transform when using different index variable", () => {
|
|
806
|
+
const input = `
|
|
807
|
+
for (let i = 0; i < items.length; i++) {
|
|
808
|
+
const item = items[j];
|
|
809
|
+
console.log(item);
|
|
810
|
+
}
|
|
811
|
+
`
|
|
812
|
+
|
|
813
|
+
const result = transform(input)
|
|
814
|
+
|
|
815
|
+
assert.strictEqual(result.modified, false)
|
|
816
|
+
})
|
|
817
|
+
|
|
818
|
+
test("should transform with ++i (prefix increment)", () => {
|
|
819
|
+
const input = `
|
|
820
|
+
for (let i = 0; i < items.length; ++i) {
|
|
821
|
+
const item = items[i];
|
|
822
|
+
console.log(item);
|
|
823
|
+
}
|
|
824
|
+
`
|
|
825
|
+
|
|
826
|
+
const result = transform(input)
|
|
827
|
+
|
|
828
|
+
assert.strictEqual(result.modified, true)
|
|
829
|
+
assert.match(result.code, /for \(const item of items\)/)
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
test("multiple statements after array access", () => {
|
|
833
|
+
const input = `
|
|
834
|
+
for (let i = 0; i < items.length; i++) {
|
|
835
|
+
const item = items[i];
|
|
836
|
+
console.log(item);
|
|
837
|
+
process(item);
|
|
838
|
+
cleanup();
|
|
839
|
+
}
|
|
840
|
+
`
|
|
841
|
+
|
|
842
|
+
const result = transform(input)
|
|
843
|
+
|
|
844
|
+
assert.strictEqual(result.modified, true)
|
|
845
|
+
assert.match(result.code, /for \(const item of items\)/)
|
|
846
|
+
assert.match(result.code, /console\.log\(item\)/)
|
|
847
|
+
assert.match(result.code, /process\(item\)/)
|
|
848
|
+
assert.match(result.code, /cleanup\(\)/)
|
|
849
|
+
})
|
|
850
|
+
|
|
851
|
+
test("should NOT transform when init is not a variable declaration", () => {
|
|
852
|
+
const input = `
|
|
853
|
+
for (i = 0; i < items.length; i++) {
|
|
854
|
+
const item = items[i];
|
|
855
|
+
console.log(item);
|
|
856
|
+
}
|
|
857
|
+
`
|
|
858
|
+
|
|
859
|
+
const result = transform(input)
|
|
860
|
+
|
|
861
|
+
assert.strictEqual(result.modified, false)
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
test("should NOT transform when init has multiple declarations", () => {
|
|
865
|
+
const input = `
|
|
866
|
+
for (let i = 0, j = 0; i < items.length; i++) {
|
|
867
|
+
const item = items[i];
|
|
868
|
+
console.log(item);
|
|
869
|
+
}
|
|
870
|
+
`
|
|
871
|
+
|
|
872
|
+
const result = transform(input)
|
|
873
|
+
|
|
874
|
+
assert.strictEqual(result.modified, false)
|
|
875
|
+
})
|
|
876
|
+
|
|
877
|
+
test("should NOT transform when init id is not an identifier", () => {
|
|
878
|
+
const input = `
|
|
879
|
+
for (let [i] = [0]; i < items.length; i++) {
|
|
880
|
+
const item = items[i];
|
|
881
|
+
console.log(item);
|
|
882
|
+
}
|
|
883
|
+
`
|
|
884
|
+
|
|
885
|
+
const result = transform(input)
|
|
886
|
+
|
|
887
|
+
assert.strictEqual(result.modified, false)
|
|
888
|
+
})
|
|
889
|
+
|
|
890
|
+
test("should NOT transform when test is not a binary expression", () => {
|
|
891
|
+
const input = `
|
|
892
|
+
for (let i = 0; items.length; i++) {
|
|
893
|
+
const item = items[i];
|
|
894
|
+
console.log(item);
|
|
895
|
+
}
|
|
896
|
+
`
|
|
897
|
+
|
|
898
|
+
const result = transform(input)
|
|
899
|
+
|
|
900
|
+
assert.strictEqual(result.modified, false)
|
|
901
|
+
})
|
|
902
|
+
|
|
903
|
+
test("should NOT transform when test operator is not <", () => {
|
|
904
|
+
const input = `
|
|
905
|
+
for (let i = 0; i <= items.length; i++) {
|
|
906
|
+
const item = items[i];
|
|
907
|
+
console.log(item);
|
|
908
|
+
}
|
|
909
|
+
`
|
|
910
|
+
|
|
911
|
+
const result = transform(input)
|
|
912
|
+
|
|
913
|
+
assert.strictEqual(result.modified, false)
|
|
914
|
+
})
|
|
915
|
+
|
|
916
|
+
test("should NOT transform when test left is not the index variable", () => {
|
|
917
|
+
const input = `
|
|
918
|
+
for (let i = 0; j < items.length; i++) {
|
|
919
|
+
const item = items[i];
|
|
920
|
+
console.log(item);
|
|
921
|
+
}
|
|
922
|
+
`
|
|
923
|
+
|
|
924
|
+
const result = transform(input)
|
|
925
|
+
|
|
926
|
+
assert.strictEqual(result.modified, false)
|
|
927
|
+
})
|
|
928
|
+
|
|
929
|
+
test("should NOT transform when test right is not a member expression", () => {
|
|
930
|
+
const input = `
|
|
931
|
+
for (let i = 0; i < 10; i++) {
|
|
932
|
+
const item = items[i];
|
|
933
|
+
console.log(item);
|
|
934
|
+
}
|
|
935
|
+
`
|
|
936
|
+
|
|
937
|
+
const result = transform(input)
|
|
938
|
+
|
|
939
|
+
assert.strictEqual(result.modified, false)
|
|
940
|
+
})
|
|
941
|
+
|
|
942
|
+
test("should NOT transform when test right property is not 'length'", () => {
|
|
943
|
+
const input = `
|
|
944
|
+
for (let i = 0; i < items.size; i++) {
|
|
945
|
+
const item = items[i];
|
|
946
|
+
console.log(item);
|
|
947
|
+
}
|
|
948
|
+
`
|
|
949
|
+
|
|
950
|
+
const result = transform(input)
|
|
951
|
+
|
|
952
|
+
assert.strictEqual(result.modified, false)
|
|
953
|
+
})
|
|
954
|
+
|
|
955
|
+
test("should NOT transform when test right object is not an identifier", () => {
|
|
956
|
+
const input = `
|
|
957
|
+
for (let i = 0; i < getItems().length; i++) {
|
|
958
|
+
const item = getItems()[i];
|
|
959
|
+
console.log(item);
|
|
960
|
+
}
|
|
961
|
+
`
|
|
962
|
+
|
|
963
|
+
const result = transform(input)
|
|
964
|
+
|
|
965
|
+
assert.strictEqual(result.modified, false)
|
|
966
|
+
})
|
|
967
|
+
|
|
968
|
+
test("should NOT transform when update is not an update expression", () => {
|
|
969
|
+
const input = `
|
|
970
|
+
for (let i = 0; i < items.length; i = i + 1) {
|
|
971
|
+
const item = items[i];
|
|
972
|
+
console.log(item);
|
|
973
|
+
}
|
|
974
|
+
`
|
|
975
|
+
|
|
976
|
+
const result = transform(input)
|
|
977
|
+
|
|
978
|
+
assert.strictEqual(result.modified, false)
|
|
979
|
+
})
|
|
980
|
+
|
|
981
|
+
test("should NOT transform when update argument is not the index variable", () => {
|
|
982
|
+
const input = `
|
|
983
|
+
for (let i = 0; i < items.length; j++) {
|
|
984
|
+
const item = items[i];
|
|
985
|
+
console.log(item);
|
|
986
|
+
}
|
|
987
|
+
`
|
|
988
|
+
|
|
989
|
+
const result = transform(input)
|
|
990
|
+
|
|
991
|
+
assert.strictEqual(result.modified, false)
|
|
992
|
+
})
|
|
993
|
+
|
|
994
|
+
test("should NOT transform when update operator is not ++", () => {
|
|
995
|
+
const input = `
|
|
996
|
+
for (let i = 0; i < items.length; i--) {
|
|
997
|
+
const item = items[i];
|
|
998
|
+
console.log(item);
|
|
999
|
+
}
|
|
1000
|
+
`
|
|
1001
|
+
|
|
1002
|
+
const result = transform(input)
|
|
1003
|
+
|
|
1004
|
+
assert.strictEqual(result.modified, false)
|
|
1005
|
+
})
|
|
1006
|
+
|
|
1007
|
+
test("should NOT transform when body is not a block statement", () => {
|
|
1008
|
+
const input = `
|
|
1009
|
+
for (let i = 0; i < items.length; i++)
|
|
1010
|
+
console.log(items[i]);
|
|
1011
|
+
`
|
|
1012
|
+
|
|
1013
|
+
const result = transform(input)
|
|
1014
|
+
|
|
1015
|
+
assert.strictEqual(result.modified, false)
|
|
1016
|
+
})
|
|
1017
|
+
|
|
1018
|
+
test("should NOT transform when first statement has multiple declarations", () => {
|
|
1019
|
+
const input = `
|
|
1020
|
+
for (let i = 0; i < items.length; i++) {
|
|
1021
|
+
const item = items[i], other = null;
|
|
1022
|
+
console.log(item);
|
|
1023
|
+
}
|
|
1024
|
+
`
|
|
1025
|
+
|
|
1026
|
+
const result = transform(input)
|
|
1027
|
+
|
|
1028
|
+
assert.strictEqual(result.modified, false)
|
|
1029
|
+
})
|
|
1030
|
+
|
|
1031
|
+
test("should NOT transform when first statement id is not an identifier", () => {
|
|
1032
|
+
const input = `
|
|
1033
|
+
for (let i = 0; i < items.length; i++) {
|
|
1034
|
+
const [item] = items[i];
|
|
1035
|
+
console.log(item);
|
|
1036
|
+
}
|
|
1037
|
+
`
|
|
1038
|
+
|
|
1039
|
+
const result = transform(input)
|
|
1040
|
+
|
|
1041
|
+
assert.strictEqual(result.modified, false)
|
|
1042
|
+
})
|
|
1043
|
+
|
|
1044
|
+
test("should NOT transform when first statement init is not a member expression", () => {
|
|
1045
|
+
const input = `
|
|
1046
|
+
for (let i = 0; i < items.length; i++) {
|
|
1047
|
+
const item = getItem(i);
|
|
1048
|
+
console.log(item);
|
|
1049
|
+
}
|
|
1050
|
+
`
|
|
1051
|
+
|
|
1052
|
+
const result = transform(input)
|
|
1053
|
+
|
|
1054
|
+
assert.strictEqual(result.modified, false)
|
|
1055
|
+
})
|
|
1056
|
+
|
|
1057
|
+
test("should NOT transform when member expression object name doesn't match", () => {
|
|
1058
|
+
const input = `
|
|
1059
|
+
for (let i = 0; i < items.length; i++) {
|
|
1060
|
+
const item = other[i];
|
|
1061
|
+
console.log(item);
|
|
1062
|
+
}
|
|
1063
|
+
`
|
|
1064
|
+
|
|
1065
|
+
const result = transform(input)
|
|
1066
|
+
|
|
1067
|
+
assert.strictEqual(result.modified, false)
|
|
1068
|
+
})
|
|
1069
|
+
|
|
1070
|
+
test("should NOT transform when member expression property doesn't match index", () => {
|
|
1071
|
+
const input = `
|
|
1072
|
+
for (let i = 0; i < items.length; i++) {
|
|
1073
|
+
const item = items[j];
|
|
1074
|
+
console.log(item);
|
|
1075
|
+
}
|
|
1076
|
+
`
|
|
1077
|
+
|
|
1078
|
+
const result = transform(input)
|
|
1079
|
+
|
|
1080
|
+
assert.strictEqual(result.modified, false)
|
|
1081
|
+
})
|
|
1082
|
+
|
|
1083
|
+
test("should NOT transform when member expression is not computed", () => {
|
|
1084
|
+
const input = `
|
|
1085
|
+
for (let i = 0; i < items.length; i++) {
|
|
1086
|
+
const item = items.i;
|
|
1087
|
+
console.log(item);
|
|
1088
|
+
}
|
|
1089
|
+
`
|
|
1090
|
+
|
|
1091
|
+
const result = transform(input)
|
|
1092
|
+
|
|
1093
|
+
assert.strictEqual(result.modified, false)
|
|
1094
|
+
})
|
|
1095
|
+
|
|
1096
|
+
test("tracks line numbers for forLoopToForOf", () => {
|
|
1097
|
+
const input = `// Line 1
|
|
1098
|
+
for (let i = 0; i < items.length; i++) {
|
|
1099
|
+
const item = items[i];
|
|
1100
|
+
console.log(item);
|
|
1101
|
+
}`
|
|
1102
|
+
|
|
1103
|
+
const result = transform(input)
|
|
1104
|
+
|
|
1105
|
+
assert.strictEqual(result.modified, true)
|
|
1106
|
+
const forLoopChanges = result.changes.filter((c) => c.type === "forLoopToForOf")
|
|
1107
|
+
assert.strictEqual(forLoopChanges.length, 1)
|
|
1108
|
+
assert.strictEqual(forLoopChanges[0].line, 2)
|
|
1109
|
+
})
|
|
1110
|
+
})
|
|
1111
|
+
|
|
1112
|
+
describe("iterable forEach to for...of", () => {
|
|
1113
|
+
test("document.querySelectorAll().forEach() to for...of", () => {
|
|
1114
|
+
const input = `
|
|
1115
|
+
document.querySelectorAll('.item').forEach(item => {
|
|
1116
|
+
console.log(item);
|
|
1117
|
+
});
|
|
1118
|
+
`
|
|
1119
|
+
|
|
1120
|
+
const result = transform(input)
|
|
1121
|
+
|
|
1122
|
+
assert.strictEqual(result.modified, true)
|
|
1123
|
+
assert.match(
|
|
1124
|
+
result.code,
|
|
1125
|
+
/for \(const item of document\.querySelectorAll\(['"]\.item['"]\)\)/,
|
|
1126
|
+
)
|
|
1127
|
+
assert.match(result.code, /console\.log\(item\)/)
|
|
1128
|
+
})
|
|
1129
|
+
|
|
1130
|
+
test("document.getElementsByTagName().forEach() to for...of", () => {
|
|
1131
|
+
const input = `
|
|
1132
|
+
document.getElementsByTagName('div').forEach(div => {
|
|
1133
|
+
div.classList.add('active');
|
|
1134
|
+
});
|
|
1135
|
+
`
|
|
1136
|
+
|
|
1137
|
+
const result = transform(input)
|
|
1138
|
+
|
|
1139
|
+
assert.strictEqual(result.modified, true)
|
|
1140
|
+
assert.match(
|
|
1141
|
+
result.code,
|
|
1142
|
+
/for \(const div of document\.getElementsByTagName\(['"]div['"]\)\)/,
|
|
1143
|
+
)
|
|
1144
|
+
})
|
|
1145
|
+
|
|
1146
|
+
test("document.getElementsByClassName().forEach() to for...of", () => {
|
|
1147
|
+
const input = `
|
|
1148
|
+
document.getElementsByClassName('button').forEach(button => {
|
|
1149
|
+
button.disabled = true;
|
|
1150
|
+
});
|
|
1151
|
+
`
|
|
1152
|
+
|
|
1153
|
+
const result = transform(input)
|
|
1154
|
+
|
|
1155
|
+
assert.strictEqual(result.modified, true)
|
|
1156
|
+
assert.match(
|
|
1157
|
+
result.code,
|
|
1158
|
+
/for \(const button of document\.getElementsByClassName\(['"]button['"]\)\)/,
|
|
1159
|
+
)
|
|
1160
|
+
})
|
|
1161
|
+
|
|
1162
|
+
test("document.getElementsByName().forEach() to for...of", () => {
|
|
1163
|
+
const input = `
|
|
1164
|
+
document.getElementsByName('email').forEach(input => {
|
|
1165
|
+
input.required = true;
|
|
1166
|
+
});
|
|
1167
|
+
`
|
|
1168
|
+
|
|
1169
|
+
const result = transform(input)
|
|
1170
|
+
|
|
1171
|
+
assert.strictEqual(result.modified, true)
|
|
1172
|
+
assert.match(
|
|
1173
|
+
result.code,
|
|
1174
|
+
/for \(const input of document\.getElementsByName\(['"]email['"]\)\)/,
|
|
1175
|
+
)
|
|
1176
|
+
})
|
|
1177
|
+
|
|
1178
|
+
test("element variable querySelectorAll should NOT transform", () => {
|
|
1179
|
+
const input = `
|
|
1180
|
+
element.querySelectorAll('span').forEach(span => {
|
|
1181
|
+
span.remove();
|
|
1182
|
+
});
|
|
1183
|
+
`
|
|
1184
|
+
|
|
1185
|
+
const result = transform(input)
|
|
1186
|
+
|
|
1187
|
+
// Should NOT transform because element is not from document chain
|
|
1188
|
+
assert.strictEqual(result.modified, false)
|
|
1189
|
+
assert.match(result.code, /element\.querySelectorAll/)
|
|
1190
|
+
})
|
|
1191
|
+
|
|
1192
|
+
test("chained element method .querySelectorAll().forEach() to for...of", () => {
|
|
1193
|
+
const input = `
|
|
1194
|
+
document.getElementById('container').querySelectorAll('p').forEach(p => {
|
|
1195
|
+
p.textContent = '';
|
|
1196
|
+
});
|
|
1197
|
+
`
|
|
1198
|
+
|
|
1199
|
+
const result = transform(input)
|
|
1200
|
+
|
|
1201
|
+
assert.strictEqual(result.modified, true)
|
|
1202
|
+
assert.match(
|
|
1203
|
+
result.code,
|
|
1204
|
+
/for \(const p of document\.getElementById\(['"]container['"]\)\.querySelectorAll\(['"]p['"]\)\)/,
|
|
1205
|
+
)
|
|
1206
|
+
})
|
|
1207
|
+
|
|
1208
|
+
test("window.frames property forEach() to for...of", () => {
|
|
1209
|
+
const input = `
|
|
1210
|
+
window.frames.forEach(frame => {
|
|
1211
|
+
frame.postMessage('hello', '*');
|
|
1212
|
+
});
|
|
1213
|
+
`
|
|
1214
|
+
|
|
1215
|
+
const result = transform(input)
|
|
1216
|
+
|
|
1217
|
+
assert.strictEqual(result.modified, true)
|
|
1218
|
+
assert.match(result.code, /for \(const frame of window\.frames\)/)
|
|
1219
|
+
})
|
|
1220
|
+
|
|
1221
|
+
test("should NOT transform arrow function without braces (expression body)", () => {
|
|
1222
|
+
const input = `
|
|
1223
|
+
document.querySelectorAll('.item').forEach(item => item.remove());
|
|
1224
|
+
`
|
|
1225
|
+
|
|
1226
|
+
const result = transform(input)
|
|
1227
|
+
|
|
1228
|
+
assert.strictEqual(result.modified, false)
|
|
1229
|
+
assert.match(result.code, /forEach\(item => item\.remove\(\)\)/)
|
|
1230
|
+
})
|
|
1231
|
+
|
|
1232
|
+
test("should NOT transform with index parameter", () => {
|
|
1233
|
+
const input = `
|
|
1234
|
+
document.querySelectorAll('.item').forEach((item, index) => {
|
|
1235
|
+
console.log(item, index);
|
|
1236
|
+
});
|
|
1237
|
+
`
|
|
1238
|
+
|
|
1239
|
+
const result = transform(input)
|
|
1240
|
+
|
|
1241
|
+
assert.strictEqual(result.modified, false)
|
|
1242
|
+
assert.match(result.code, /forEach\(\(item, index\) =>/)
|
|
1243
|
+
})
|
|
1244
|
+
|
|
1245
|
+
test("should NOT transform with array parameter", () => {
|
|
1246
|
+
const input = `
|
|
1247
|
+
document.querySelectorAll('.item').forEach((item, index, array) => {
|
|
1248
|
+
console.log(item, index, array);
|
|
1249
|
+
});
|
|
1250
|
+
`
|
|
1251
|
+
|
|
1252
|
+
const result = transform(input)
|
|
1253
|
+
|
|
1254
|
+
assert.strictEqual(result.modified, false)
|
|
1255
|
+
assert.match(result.code, /forEach\(\(item, index, array\) =>/)
|
|
1256
|
+
})
|
|
1257
|
+
|
|
1258
|
+
test("should NOT transform with non-inline callback (reference)", () => {
|
|
1259
|
+
const input = `
|
|
1260
|
+
document.querySelectorAll('.item').forEach(handleItem);
|
|
1261
|
+
`
|
|
1262
|
+
|
|
1263
|
+
const result = transform(input)
|
|
1264
|
+
|
|
1265
|
+
assert.strictEqual(result.modified, false)
|
|
1266
|
+
assert.match(result.code, /forEach\(handleItem\)/)
|
|
1267
|
+
})
|
|
1268
|
+
|
|
1269
|
+
test("should NOT transform without callback", () => {
|
|
1270
|
+
const input = `
|
|
1271
|
+
document.querySelectorAll('.item').forEach();
|
|
1272
|
+
`
|
|
1273
|
+
|
|
1274
|
+
const result = transform(input)
|
|
1275
|
+
|
|
1276
|
+
assert.strictEqual(result.modified, false)
|
|
1277
|
+
assert.match(result.code, /forEach\(\)/)
|
|
1278
|
+
})
|
|
1279
|
+
|
|
1280
|
+
test("should NOT transform unknown methods", () => {
|
|
1281
|
+
const input = `
|
|
1282
|
+
document.querySomething('.item').forEach(item => {
|
|
1283
|
+
console.log(item);
|
|
1284
|
+
});
|
|
1285
|
+
`
|
|
1286
|
+
|
|
1287
|
+
const result = transform(input)
|
|
1288
|
+
|
|
1289
|
+
assert.strictEqual(result.modified, false)
|
|
1290
|
+
assert.match(result.code, /querySomething/)
|
|
1291
|
+
})
|
|
1292
|
+
|
|
1293
|
+
test("should NOT transform non-document objects with querySelectorAll", () => {
|
|
1294
|
+
const input = `
|
|
1295
|
+
myObject.querySelectorAll('.item').forEach(item => {
|
|
1296
|
+
console.log(item);
|
|
1297
|
+
});
|
|
1298
|
+
`
|
|
1299
|
+
|
|
1300
|
+
const result = transform(input)
|
|
1301
|
+
|
|
1302
|
+
assert.strictEqual(result.modified, false)
|
|
1303
|
+
assert.match(result.code, /myObject\.querySelectorAll/)
|
|
1304
|
+
})
|
|
1305
|
+
|
|
1306
|
+
test("should NOT transform window methods not in allowed list", () => {
|
|
1307
|
+
const input = `
|
|
1308
|
+
window.querySelectorAll('.item').forEach(item => {
|
|
1309
|
+
console.log(item);
|
|
1310
|
+
});
|
|
1311
|
+
`
|
|
1312
|
+
|
|
1313
|
+
const result = transform(input)
|
|
1314
|
+
|
|
1315
|
+
assert.strictEqual(result.modified, false)
|
|
1316
|
+
assert.match(result.code, /window\.querySelectorAll/)
|
|
1317
|
+
})
|
|
1318
|
+
|
|
1319
|
+
test("transforms with function expression", () => {
|
|
1320
|
+
const input = `
|
|
1321
|
+
document.querySelectorAll('button').forEach(function(btn) {
|
|
1322
|
+
btn.disabled = true;
|
|
1323
|
+
});
|
|
1324
|
+
`
|
|
1325
|
+
|
|
1326
|
+
const result = transform(input)
|
|
1327
|
+
|
|
1328
|
+
assert.strictEqual(result.modified, true)
|
|
1329
|
+
assert.match(
|
|
1330
|
+
result.code,
|
|
1331
|
+
/for \(const btn of document\.querySelectorAll\(['"]button['"]\)\)/,
|
|
1332
|
+
)
|
|
1333
|
+
})
|
|
1334
|
+
|
|
1335
|
+
test("tracks line numbers correctly", () => {
|
|
1336
|
+
const input = `// Line 1
|
|
1337
|
+
document.querySelectorAll('.item').forEach(item => {
|
|
1338
|
+
console.log(item);
|
|
1339
|
+
});`
|
|
1340
|
+
|
|
1341
|
+
const result = transform(input)
|
|
1342
|
+
|
|
1343
|
+
assert.strictEqual(result.modified, true)
|
|
1344
|
+
assert.strictEqual(result.changes.length, 1)
|
|
1345
|
+
assert.strictEqual(result.changes[0].type, "iterableForEachToForOf")
|
|
1346
|
+
assert.strictEqual(result.changes[0].line, 2)
|
|
1347
|
+
})
|
|
1348
|
+
|
|
1349
|
+
test("handles complex selector strings", () => {
|
|
1350
|
+
const input = `
|
|
1351
|
+
document.querySelectorAll('[data-toggle="modal"]').forEach(el => {
|
|
1352
|
+
el.addEventListener('click', handleClick);
|
|
1353
|
+
});
|
|
1354
|
+
`
|
|
1355
|
+
|
|
1356
|
+
const result = transform(input)
|
|
1357
|
+
|
|
1358
|
+
assert.strictEqual(result.modified, true)
|
|
1359
|
+
assert.match(
|
|
1360
|
+
result.code,
|
|
1361
|
+
/for \(const el of document\.querySelectorAll\(['"]\[data-toggle="modal"\]['"]\)\)/,
|
|
1362
|
+
)
|
|
1363
|
+
})
|
|
1364
|
+
|
|
1365
|
+
test("preserves multiline function bodies", () => {
|
|
1366
|
+
const input = `
|
|
1367
|
+
document.querySelectorAll('.item').forEach(item => {
|
|
1368
|
+
const value = item.value;
|
|
1369
|
+
console.log(value);
|
|
1370
|
+
item.classList.add('processed');
|
|
1371
|
+
});
|
|
1372
|
+
`
|
|
1373
|
+
|
|
1374
|
+
const result = transform(input)
|
|
1375
|
+
|
|
1376
|
+
assert.strictEqual(result.modified, true)
|
|
1377
|
+
assert.match(result.code, /for \(const item of/)
|
|
1378
|
+
assert.match(result.code, /const value = item\.value/)
|
|
1379
|
+
assert.match(result.code, /console\.log\(value\)/)
|
|
1380
|
+
assert.match(result.code, /item\.classList\.add/)
|
|
1381
|
+
})
|
|
1382
|
+
|
|
1383
|
+
test("should NOT transform element variables with getElementsByTagName", () => {
|
|
1384
|
+
const input = `
|
|
1385
|
+
container.getElementsByTagName('input').forEach(input => {
|
|
1386
|
+
input.value = '';
|
|
1387
|
+
});
|
|
1388
|
+
`
|
|
1389
|
+
|
|
1390
|
+
const result = transform(input)
|
|
1391
|
+
|
|
1392
|
+
// Should not transform because container is not from document chain
|
|
1393
|
+
assert.strictEqual(result.modified, false)
|
|
1394
|
+
assert.match(result.code, /container\.getElementsByTagName/)
|
|
1395
|
+
})
|
|
1396
|
+
|
|
1397
|
+
test("should NOT transform element variables with getElementsByClassName", () => {
|
|
1398
|
+
const input = `
|
|
1399
|
+
section.getElementsByClassName('warning').forEach(warning => {
|
|
1400
|
+
warning.style.display = 'none';
|
|
1401
|
+
});
|
|
1402
|
+
`
|
|
1403
|
+
|
|
1404
|
+
const result = transform(input)
|
|
1405
|
+
|
|
1406
|
+
// Should not transform because section is not from document chain
|
|
1407
|
+
assert.strictEqual(result.modified, false)
|
|
1408
|
+
assert.match(result.code, /section\.getElementsByClassName/)
|
|
1409
|
+
})
|
|
1410
|
+
|
|
1411
|
+
test("should NOT transform window.querySelectorAll (not in allowed methods)", () => {
|
|
1412
|
+
const input = `
|
|
1413
|
+
window.querySelectorAll('.item').forEach(item => {
|
|
1414
|
+
console.log(item);
|
|
1415
|
+
});
|
|
1416
|
+
`
|
|
1417
|
+
|
|
1418
|
+
const result = transform(input)
|
|
1419
|
+
|
|
1420
|
+
// Should not transform because querySelectorAll is not a window method
|
|
1421
|
+
assert.strictEqual(result.modified, false)
|
|
1422
|
+
assert.match(result.code, /window\.querySelectorAll/)
|
|
1423
|
+
})
|
|
1424
|
+
|
|
1425
|
+
test("should NOT transform property access on unknown objects", () => {
|
|
1426
|
+
const input = `
|
|
1427
|
+
customObject.frames.forEach(frame => {
|
|
1428
|
+
frame.postMessage('test', '*');
|
|
1429
|
+
});
|
|
1430
|
+
`
|
|
1431
|
+
|
|
1432
|
+
const result = transform(input)
|
|
1433
|
+
|
|
1434
|
+
// Should not transform because customObject is not window
|
|
1435
|
+
assert.strictEqual(result.modified, false)
|
|
1436
|
+
assert.match(result.code, /customObject\.frames/)
|
|
1437
|
+
})
|
|
1438
|
+
|
|
1439
|
+
test("should NOT transform when method callee is not a member expression", () => {
|
|
1440
|
+
const input = `
|
|
1441
|
+
getSomething().forEach(item => {
|
|
1442
|
+
console.log(item);
|
|
1443
|
+
});
|
|
1444
|
+
`
|
|
1445
|
+
|
|
1446
|
+
const result = transform(input)
|
|
1447
|
+
|
|
1448
|
+
// Should not transform because callee is not a member expression
|
|
1449
|
+
assert.strictEqual(result.modified, false)
|
|
1450
|
+
assert.match(result.code, /getSomething\(\)\.forEach/)
|
|
1451
|
+
})
|
|
1452
|
+
|
|
1453
|
+
test("should NOT transform when method name cannot be extracted (computed property)", () => {
|
|
1454
|
+
const input = `
|
|
1455
|
+
document['querySelectorAll']('.item').forEach(item => {
|
|
1456
|
+
console.log(item);
|
|
1457
|
+
});
|
|
1458
|
+
`
|
|
1459
|
+
|
|
1460
|
+
const result = transform(input)
|
|
1461
|
+
|
|
1462
|
+
// Should not transform because method name is computed (string literal, not identifier)
|
|
1463
|
+
assert.strictEqual(result.modified, false)
|
|
1464
|
+
assert.match(result.code, /document\['querySelectorAll'\]/)
|
|
1465
|
+
})
|
|
1466
|
+
|
|
1467
|
+
test("should NOT transform unknown document methods", () => {
|
|
1468
|
+
const input = `
|
|
1469
|
+
document.customMethod().forEach(item => {
|
|
1470
|
+
console.log(item);
|
|
1471
|
+
});
|
|
1472
|
+
`
|
|
1473
|
+
|
|
1474
|
+
const result = transform(input)
|
|
1475
|
+
|
|
1476
|
+
// Should not transform because customMethod is not in allowed list
|
|
1477
|
+
assert.strictEqual(result.modified, false)
|
|
1478
|
+
assert.match(result.code, /document\.customMethod/)
|
|
1479
|
+
})
|
|
1480
|
+
|
|
1481
|
+
test("should NOT transform chained call from non-document origin", () => {
|
|
1482
|
+
const input = `
|
|
1483
|
+
element.querySelector('div').querySelectorAll('span').forEach(span => {
|
|
1484
|
+
span.remove();
|
|
1485
|
+
});
|
|
1486
|
+
`
|
|
1487
|
+
|
|
1488
|
+
const result = transform(input)
|
|
1489
|
+
|
|
1490
|
+
// Should not transform because chain doesn't start with document
|
|
1491
|
+
assert.strictEqual(result.modified, false)
|
|
1492
|
+
assert.match(result.code, /element\.querySelector/)
|
|
1493
|
+
})
|
|
1494
|
+
|
|
1495
|
+
test("should NOT transform chained call with unknown method", () => {
|
|
1496
|
+
const input = `
|
|
1497
|
+
document.getElementById('x').customMethod().forEach(item => {
|
|
1498
|
+
console.log(item);
|
|
1499
|
+
});
|
|
1500
|
+
`
|
|
1501
|
+
|
|
1502
|
+
const result = transform(input)
|
|
1503
|
+
|
|
1504
|
+
// Should not transform because customMethod is not in document methods
|
|
1505
|
+
assert.strictEqual(result.modified, false)
|
|
1506
|
+
assert.match(result.code, /customMethod/)
|
|
1507
|
+
})
|
|
1508
|
+
|
|
1509
|
+
test("should NOT transform when caller object is neither identifier nor member/call expression", () => {
|
|
1510
|
+
const input = `
|
|
1511
|
+
(function() { return document; })().querySelectorAll('.item').forEach(item => {
|
|
1512
|
+
console.log(item);
|
|
1513
|
+
});
|
|
1514
|
+
`
|
|
1515
|
+
|
|
1516
|
+
const result = transform(input)
|
|
1517
|
+
|
|
1518
|
+
// Should not transform because caller is a function expression
|
|
1519
|
+
assert.strictEqual(result.modified, false)
|
|
1520
|
+
assert.match(result.code, /forEach/)
|
|
1521
|
+
})
|
|
1522
|
+
|
|
1523
|
+
test("should NOT transform when caller object is ThisExpression", () => {
|
|
1524
|
+
const input = `
|
|
1525
|
+
this.querySelectorAll('.item').forEach(item => {
|
|
1526
|
+
console.log(item);
|
|
1527
|
+
});
|
|
1528
|
+
`
|
|
1529
|
+
|
|
1530
|
+
const result = transform(input)
|
|
1531
|
+
|
|
1532
|
+
// Should not transform because caller is this, not document
|
|
1533
|
+
assert.strictEqual(result.modified, false)
|
|
1534
|
+
assert.match(result.code, /this\.querySelectorAll/)
|
|
1535
|
+
})
|
|
1536
|
+
|
|
1537
|
+
test("should NOT transform when forEach object is neither MemberExpression nor CallExpression", () => {
|
|
1538
|
+
const input = `
|
|
1539
|
+
items.forEach(item => {
|
|
1540
|
+
console.log(item);
|
|
1541
|
+
});
|
|
1542
|
+
`
|
|
1543
|
+
|
|
1544
|
+
const result = transform(input)
|
|
1545
|
+
|
|
1546
|
+
// Should not transform because items is just an identifier, not a method call or property
|
|
1547
|
+
assert.strictEqual(result.modified, false)
|
|
1548
|
+
assert.match(result.code, /items\.forEach/)
|
|
1549
|
+
})
|
|
1550
|
+
|
|
1551
|
+
test("deeply nested document chain should transform", () => {
|
|
1552
|
+
const input = `
|
|
1553
|
+
document.getElementById('a').querySelector('b').querySelectorAll('c').forEach(item => {
|
|
1554
|
+
item.remove();
|
|
1555
|
+
});
|
|
1556
|
+
`
|
|
1557
|
+
|
|
1558
|
+
const result = transform(input)
|
|
1559
|
+
|
|
1560
|
+
// Should transform because it chains from document
|
|
1561
|
+
assert.strictEqual(result.modified, true)
|
|
1562
|
+
assert.match(
|
|
1563
|
+
result.code,
|
|
1564
|
+
/for \(const item of document\.getElementById\(['"]a['"]\)\.querySelector\(['"]b['"]\)\.querySelectorAll\(['"]c['"]\)\)/,
|
|
1565
|
+
)
|
|
1566
|
+
})
|
|
1567
|
+
|
|
1568
|
+
test("should NOT transform when callee in chain is not a member expression", () => {
|
|
1569
|
+
const input = `
|
|
1570
|
+
getDocument().querySelectorAll('span').forEach(item => {
|
|
1571
|
+
item.textContent = 'test';
|
|
1572
|
+
});
|
|
1573
|
+
`
|
|
1574
|
+
|
|
1575
|
+
const result = transform(input)
|
|
1576
|
+
|
|
1577
|
+
// Should NOT transform - chain starts with function call, not document
|
|
1578
|
+
assert.strictEqual(result.modified, false)
|
|
1579
|
+
assert.match(result.code, /getDocument\(\)\.querySelectorAll/)
|
|
1580
|
+
})
|
|
1581
|
+
|
|
1582
|
+
test("should transform document property access with querySelectorAll", () => {
|
|
1583
|
+
const input = `
|
|
1584
|
+
document.body.querySelectorAll('div').forEach(div => {
|
|
1585
|
+
div.remove();
|
|
1586
|
+
});
|
|
1587
|
+
`
|
|
1588
|
+
|
|
1589
|
+
const result = transform(input)
|
|
1590
|
+
|
|
1591
|
+
// Should transform - chains from document through property access
|
|
1592
|
+
assert.strictEqual(result.modified, true)
|
|
1593
|
+
assert.match(result.code, /for \(const div of document\.body\.querySelectorAll/)
|
|
1594
|
+
})
|
|
1595
|
+
})
|
|
565
1596
|
})
|