on master help sign in
detailed snippet tests
commitf9a25a7e0928e1a2f98108cad0f2bea99bedad59 authorPascal Kuthe <pascal.kuthe@semimod.de> dateSun 19 Mar 2023, 22:06 (4 years ago) parent85c9dbf
1 file changed+368−21
helix-lsp/src/snippet.rs+368−21
helix-lsp/src/snippet.rs+368−21
@@ -633,31 +633,378 @@ mod parser {
633633 parse("macro_rules! $1 {\n ($2) => {\n $0\n };\n}")
634634 );
635635 }
636+
637+ fn assert_text(snippet: &str, parsed_text: &str) {
638+ let res = parse(snippet).unwrap();
639+ let text = crate::snippet::render(&res, "\n", true).0;
640+ assert_eq!(text, parsed_text)
641+ }
642+
636643 #[test]
637644 fn robust_parsing() {
638− assert_eq!(
639− Ok(Snippet {
640− elements: vec![
641− Text("$".into()),
642− Text("{}".into()),
643− Text("$".into()),
644− Text("\\a$}\\".into()),
645− ]
646− }),
647− parse("${}$\\a\\$\\}\\\\")
645+ assert_text("$", "$");
646+ assert_text("\\\\$", "\\$");
647+ assert_text("{", "{");
648+ assert_text("\\}", "}");
649+ assert_text("\\abc", "\\abc");
650+ assert_text("foo${f:\\}}bar", "foo}bar");
651+ assert_text("\\{", "\\{");
652+ assert_text("I need \\\\\\$", "I need \\$");
653+ assert_text("\\", "\\");
654+ assert_text("\\{{", "\\{{");
655+ assert_text("{{", "{{");
656+ assert_text("{{dd", "{{dd");
657+ assert_text("}}", "}}");
658+ assert_text("ff}}", "ff}}");
659+ assert_text("farboo", "farboo");
660+ assert_text("far{{}}boo", "far{{}}boo");
661+ assert_text("far{{123}}boo", "far{{123}}boo");
662+ assert_text("far\\{{123}}boo", "far\\{{123}}boo");
663+ assert_text("far{{id:bern}}boo", "far{{id:bern}}boo");
664+ assert_text("far{{id:bern {{basel}}}}boo", "far{{id:bern {{basel}}}}boo");
665+ assert_text(
666+ "far{{id:bern {{id:basel}}}}boo",
667+ "far{{id:bern {{id:basel}}}}boo",
648668 );
649− assert_eq!(
650− Ok(Snippet {
651− elements: vec![
652− Placeholder {
653− tabstop: 1,
654− value: vec![Text("$".into()), Text("{".into())]
655− },
656− Text("}".into())
657− ]
658− }),
659− parse("${1:${}}")
669+ assert_text(
670+ "far{{id:bern {{id2:basel}}}}boo",
671+ "far{{id:bern {{id2:basel}}}}boo",
672+ );
673+ assert_text("${}$\\a\\$\\}\\\\", "${}$\\a$}\\");
674+ assert_text("farboo", "farboo");
675+ assert_text("far{{}}boo", "far{{}}boo");
676+ assert_text("far{{123}}boo", "far{{123}}boo");
677+ assert_text("far\\{{123}}boo", "far\\{{123}}boo");
678+ assert_text("far`123`boo", "far`123`boo");
679+ assert_text("far\\`123\\`boo", "far\\`123\\`boo");
680+ assert_text("\\$far-boo", "$far-boo");
681+ }
682+
683+ fn assert_snippet(snippet: &str, expect: &[SnippetElement]) {
684+ let parsed_snippet = parse(snippet).unwrap();
685+ assert_eq!(parsed_snippet.elements, expect.to_owned())
686+ }
687+
688+ #[test]
689+ fn parse_variable() {
690+ use SnippetElement::*;
691+ assert_snippet(
692+ "$far-boo",
693+ &[
694+ Variable {
695+ name: "far",
696+ default: None,
697+ regex: None,
698+ },
699+ Text("-boo".into()),
700+ ],
701+ );
702+ assert_snippet(
703+ "far$farboo",
704+ &[
705+ Text("far".into()),
706+ Variable {
707+ name: "farboo",
708+ regex: None,
709+ default: None,
710+ },
711+ ],
712+ );
713+ assert_snippet(
714+ "far${farboo}",
715+ &[
716+ Text("far".into()),
717+ Variable {
718+ name: "farboo",
719+ regex: None,
720+ default: None,
721+ },
722+ ],
723+ );
724+ assert_snippet("$123", &[Tabstop { tabstop: 123 }]);
725+ assert_snippet(
726+ "$farboo",
727+ &[Variable {
728+ name: "farboo",
729+ regex: None,
730+ default: None,
731+ }],
732+ );
733+ assert_snippet(
734+ "$far12boo",
735+ &[Variable {
736+ name: "far12boo",
737+ regex: None,
738+ default: None,
739+ }],
740+ );
741+ assert_snippet(
742+ "000_${far}_000",
743+ &[
744+ Text("000_".into()),
745+ Variable {
746+ name: "far",
747+ regex: None,
748+ default: None,
749+ },
750+ Text("_000".into()),
751+ ],
752+ );
753+ }
754+
755+ #[test]
756+ fn parse_variable_transform() {
757+ assert_snippet(
758+ "${foo///}",
759+ &[Variable {
760+ name: "foo",
761+ regex: Some(Regex {
762+ value: Tendril::new(),
763+ replacement: Vec::new(),
764+ options: Tendril::new(),
765+ }),
766+ default: None,
767+ }],
768+ );
769+ assert_snippet(
770+ "${foo/regex/format/gmi}",
771+ &[Variable {
772+ name: "foo",
773+ regex: Some(Regex {
774+ value: "regex".into(),
775+ replacement: vec![FormatItem::Text("format".into())],
776+ options: "gmi".into(),
777+ }),
778+ default: None,
779+ }],
780+ );
781+ assert_snippet(
782+ "${foo/([A-Z][a-z])/format/}",
783+ &[Variable {
784+ name: "foo",
785+ regex: Some(Regex {
786+ value: "([A-Z][a-z])".into(),
787+ replacement: vec![FormatItem::Text("format".into())],
788+ options: Tendril::new(),
789+ }),
790+ default: None,
791+ }],
792+ );
793+
794+ // invalid regex TODO: reneable tests once we actually parse this regex flavour
795+ // assert_text(
796+ // "${foo/([A-Z][a-z])/format/GMI}",
797+ // "${foo/([A-Z][a-z])/format/GMI}",
798+ // );
799+ // assert_text(
800+ // "${foo/([A-Z][a-z])/format/funky}",
801+ // "${foo/([A-Z][a-z])/format/funky}",
802+ // );
803+ // assert_text("${foo/([A-Z][a-z]/format/}", "${foo/([A-Z][a-z]/format/}");
804+ assert_text(
805+ "${foo/regex\\/format/options}",
806+ "${foo/regex\\/format/options}",
807+ );
808+
809+ // tricky regex
810+ assert_snippet(
811+ "${foo/m\\/atch/$1/i}",
812+ &[Variable {
813+ name: "foo",
814+ regex: Some(Regex {
815+ value: "m/atch".into(),
816+ replacement: vec![FormatItem::Capture(1)],
817+ options: "i".into(),
818+ }),
819+ default: None,
820+ }],
821+ );
822+
823+ // incomplete
824+ assert_text("${foo///", "${foo///");
825+ assert_text("${foo/regex/format/options", "${foo/regex/format/options");
826+
827+ // format string
828+ assert_snippet(
829+ "${foo/.*/${0:fooo}/i}",
830+ &[Variable {
831+ name: "foo",
832+ regex: Some(Regex {
833+ value: ".*".into(),
834+ replacement: vec![FormatItem::Conditional(0, None, Some("fooo".into()))],
835+ options: "i".into(),
836+ }),
837+ default: None,
838+ }],
839+ );
840+ assert_snippet(
841+ "${foo/.*/${1}/i}",
842+ &[Variable {
843+ name: "foo",
844+ regex: Some(Regex {
845+ value: ".*".into(),
846+ replacement: vec![FormatItem::Capture(1)],
847+ options: "i".into(),
848+ }),
849+ default: None,
850+ }],
851+ );
852+ assert_snippet(
853+ "${foo/.*/$1/i}",
854+ &[Variable {
855+ name: "foo",
856+ regex: Some(Regex {
857+ value: ".*".into(),
858+ replacement: vec![FormatItem::Capture(1)],
859+ options: "i".into(),
860+ }),
861+ default: None,
862+ }],
863+ );
864+ assert_snippet(
865+ "${foo/.*/This-$1-encloses/i}",
866+ &[Variable {
867+ name: "foo",
868+ regex: Some(Regex {
869+ value: ".*".into(),
870+ replacement: vec![
871+ FormatItem::Text("This-".into()),
872+ FormatItem::Capture(1),
873+ FormatItem::Text("-encloses".into()),
874+ ],
875+ options: "i".into(),
876+ }),
877+ default: None,
878+ }],
879+ );
880+ assert_snippet(
881+ "${foo/.*/complex${1:else}/i}",
882+ &[Variable {
883+ name: "foo",
884+ regex: Some(Regex {
885+ value: ".*".into(),
886+ replacement: vec![
887+ FormatItem::Text("complex".into()),
888+ FormatItem::Conditional(1, None, Some("else".into())),
889+ ],
890+ options: "i".into(),
891+ }),
892+ default: None,
893+ }],
894+ );
895+ assert_snippet(
896+ "${foo/.*/complex${1:-else}/i}",
897+ &[Variable {
898+ name: "foo",
899+ regex: Some(Regex {
900+ value: ".*".into(),
901+ replacement: vec![
902+ FormatItem::Text("complex".into()),
903+ FormatItem::Conditional(1, None, Some("else".into())),
904+ ],
905+ options: "i".into(),
906+ }),
907+ default: None,
908+ }],
909+ );
910+ assert_snippet(
911+ "${foo/.*/complex${1:+if}/i}",
912+ &[Variable {
913+ name: "foo",
914+ regex: Some(Regex {
915+ value: ".*".into(),
916+ replacement: vec![
917+ FormatItem::Text("complex".into()),
918+ FormatItem::Conditional(1, Some("if".into()), None),
919+ ],
920+ options: "i".into(),
921+ }),
922+ default: None,
923+ }],
924+ );
925+ assert_snippet(
926+ "${foo/.*/complex${1:?if:else}/i}",
927+ &[Variable {
928+ name: "foo",
929+ regex: Some(Regex {
930+ value: ".*".into(),
931+ replacement: vec![
932+ FormatItem::Text("complex".into()),
933+ FormatItem::Conditional(1, Some("if".into()), Some("else".into())),
934+ ],
935+ options: "i".into(),
936+ }),
937+ default: None,
938+ }],
939+ );
940+ assert_snippet(
941+ "${foo/.*/complex${1:/upcase}/i}",
942+ &[Variable {
943+ name: "foo",
944+ regex: Some(Regex {
945+ value: ".*".into(),
946+ replacement: vec![
947+ FormatItem::Text("complex".into()),
948+ FormatItem::CaseChange(1, CaseChange::Upcase),
949+ ],
950+ options: "i".into(),
951+ }),
952+ default: None,
953+ }],
954+ );
955+ assert_snippet(
956+ "${TM_DIRECTORY/src\\//$1/}",
957+ &[Variable {
958+ name: "TM_DIRECTORY",
959+ regex: Some(Regex {
960+ value: "src/".into(),
961+ replacement: vec![FormatItem::Capture(1)],
962+ options: Tendril::new(),
963+ }),
964+ default: None,
965+ }],
966+ );
967+ assert_snippet(
968+ "${TM_SELECTED_TEXT/a/\\/$1/g}",
969+ &[Variable {
970+ name: "TM_SELECTED_TEXT",
971+ regex: Some(Regex {
972+ value: "a".into(),
973+ replacement: vec![FormatItem::Text("/".into()), FormatItem::Capture(1)],
974+ options: "g".into(),
975+ }),
976+ default: None,
977+ }],
978+ );
979+ assert_snippet(
980+ "${TM_SELECTED_TEXT/a/in\\/$1ner/g}",
981+ &[Variable {
982+ name: "TM_SELECTED_TEXT",
983+ regex: Some(Regex {
984+ value: "a".into(),
985+ replacement: vec![
986+ FormatItem::Text("in/".into()),
987+ FormatItem::Capture(1),
988+ FormatItem::Text("ner".into()),
989+ ],
990+ options: "g".into(),
991+ }),
992+ default: None,
993+ }],
994+ );
995+ assert_snippet(
996+ "${TM_SELECTED_TEXT/a/end\\//g}",
997+ &[Variable {
998+ name: "TM_SELECTED_TEXT",
999+ regex: Some(Regex {
1000+ value: "a".into(),
1001+ replacement: vec![FormatItem::Text("end/".into())],
1002+ options: "g".into(),
1003+ }),
1004+ default: None,
1005+ }],
6601006 );
6611007 }
1008+ // TODO port more tests from https://github.com/microsoft/vscode/blob/dce493cb6e36346ef2714e82c42ce14fc461b15c/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts
6621009 }
6631010}