@@ -1024,6 +1024,151 @@ describe("BindingGenerator", () => {
10241024 } ) ;
10251025 } ) ;
10261026
1027+ describe ( "generate - identifier and string literal sanitization" , ( ) => {
1028+ it ( "escapes double quotes in error enum case names" , ( ) => {
1029+ const errorSpec = xdr . ScSpecEntry . scSpecEntryUdtErrorEnumV0 (
1030+ new xdr . ScSpecUdtErrorEnumV0 ( {
1031+ doc : "" ,
1032+ lib : "" ,
1033+ name : "Errors" ,
1034+ cases : [
1035+ new xdr . ScSpecUdtErrorEnumCaseV0 ( {
1036+ doc : "" ,
1037+ name : 'has "quotes" inside' ,
1038+ value : 0 ,
1039+ } ) ,
1040+ ] ,
1041+ } ) ,
1042+ ) ;
1043+ const spec = new contract . Spec ( [ errorSpec . toXDR ( "base64" ) ] ) ;
1044+ const result = BindingGenerator . fromSpec ( spec ) . generate ( defaultOptions ) ;
1045+
1046+ expect ( result . types ) . toContain ( "export const Errors" ) ;
1047+ // Double quotes should be escaped in the string literal
1048+ expect ( result . types ) . toContain ( 'has \\"quotes\\" inside' ) ;
1049+ } ) ;
1050+
1051+ it ( "strips non-identifier characters from struct field names" , ( ) => {
1052+ const structSpec = createStructSpec ( "MyStruct" , [
1053+ {
1054+ name : "field;name{bad}" ,
1055+ type : xdr . ScSpecTypeDef . scSpecTypeU32 ( ) ,
1056+ } ,
1057+ ] ) ;
1058+ const spec = new contract . Spec ( [ structSpec . toXDR ( "base64" ) ] ) ;
1059+ const result = BindingGenerator . fromSpec ( spec ) . generate ( defaultOptions ) ;
1060+
1061+ // Special characters should be replaced with underscores
1062+ expect ( result . types ) . toContain ( "field_name_bad_: number" ) ;
1063+ expect ( result . types ) . toContain ( "export interface MyStruct" ) ;
1064+ } ) ;
1065+
1066+ it ( "escapes special characters in union case tag strings" , ( ) => {
1067+ const unionSpec = xdr . ScSpecEntry . scSpecEntryUdtUnionV0 (
1068+ new xdr . ScSpecUdtUnionV0 ( {
1069+ doc : "" ,
1070+ lib : "" ,
1071+ name : "MyUnion" ,
1072+ cases : [
1073+ xdr . ScSpecUdtUnionCaseV0 . scSpecUdtUnionCaseVoidV0 (
1074+ new xdr . ScSpecUdtUnionCaseVoidV0 ( {
1075+ doc : "" ,
1076+ name : 'case"with"quotes' ,
1077+ } ) ,
1078+ ) ,
1079+ ] ,
1080+ } ) ,
1081+ ) ;
1082+ const spec = new contract . Spec ( [ unionSpec . toXDR ( "base64" ) ] ) ;
1083+ const result = BindingGenerator . fromSpec ( spec ) . generate ( defaultOptions ) ;
1084+
1085+ expect ( result . types ) . toContain ( 'case\\"with\\"quotes' ) ;
1086+ expect ( result . types ) . toContain ( "export type MyUnion" ) ;
1087+ } ) ;
1088+
1089+ it ( "strips non-identifier characters from enum case names" , ( ) => {
1090+ const enumSpec = xdr . ScSpecEntry . scSpecEntryUdtEnumV0 (
1091+ new xdr . ScSpecUdtEnumV0 ( {
1092+ doc : "" ,
1093+ lib : "" ,
1094+ name : "MyEnum" ,
1095+ cases : [
1096+ new xdr . ScSpecUdtEnumCaseV0 ( {
1097+ doc : "" ,
1098+ name : "Case = 0; extra" ,
1099+ value : 0 ,
1100+ } ) ,
1101+ ] ,
1102+ } ) ,
1103+ ) ;
1104+ const spec = new contract . Spec ( [ enumSpec . toXDR ( "base64" ) ] ) ;
1105+ const result = BindingGenerator . fromSpec ( spec ) . generate ( defaultOptions ) ;
1106+
1107+ expect ( result . types ) . toContain ( "Case___0__extra = 0" ) ;
1108+ expect ( result . types ) . toContain ( "export enum MyEnum" ) ;
1109+ } ) ;
1110+
1111+ it ( "escapes JS line terminators U+2028 and U+2029 in string literals" , ( ) => {
1112+ const errorSpec = xdr . ScSpecEntry . scSpecEntryUdtErrorEnumV0 (
1113+ new xdr . ScSpecUdtErrorEnumV0 ( {
1114+ doc : "" ,
1115+ lib : "" ,
1116+ name : "Errors" ,
1117+ cases : [
1118+ new xdr . ScSpecUdtErrorEnumCaseV0 ( {
1119+ doc : "" ,
1120+ name : "line\u2028sep\u2029end" ,
1121+ value : 0 ,
1122+ } ) ,
1123+ ] ,
1124+ } ) ,
1125+ ) ;
1126+ const spec = new contract . Spec ( [ errorSpec . toXDR ( "base64" ) ] ) ;
1127+ const result = BindingGenerator . fromSpec ( spec ) . generate ( defaultOptions ) ;
1128+
1129+ expect ( result . types ) . toContain ( "\\u2028" ) ;
1130+ expect ( result . types ) . toContain ( "\\u2029" ) ;
1131+ // Raw line terminators should not appear in the output
1132+ expect ( result . types ) . not . toContain ( "\u2028" ) ;
1133+ expect ( result . types ) . not . toContain ( "\u2029" ) ;
1134+ } ) ;
1135+
1136+ it ( "escapes backslashes in string literals" , ( ) => {
1137+ const errorSpec = xdr . ScSpecEntry . scSpecEntryUdtErrorEnumV0 (
1138+ new xdr . ScSpecUdtErrorEnumV0 ( {
1139+ doc : "" ,
1140+ lib : "" ,
1141+ name : "Errors" ,
1142+ cases : [
1143+ new xdr . ScSpecUdtErrorEnumCaseV0 ( {
1144+ doc : "" ,
1145+ name : "path\\to\\file" ,
1146+ value : 0 ,
1147+ } ) ,
1148+ ] ,
1149+ } ) ,
1150+ ) ;
1151+ const spec = new contract . Spec ( [ errorSpec . toXDR ( "base64" ) ] ) ;
1152+ const result = BindingGenerator . fromSpec ( spec ) . generate ( defaultOptions ) ;
1153+
1154+ // Backslashes should be double-escaped
1155+ expect ( result . types ) . toContain ( "path\\\\to\\\\file" ) ;
1156+ } ) ;
1157+
1158+ it ( "falls back to _unnamed for identifiers with only special characters" , ( ) => {
1159+ const structSpec = createStructSpec ( "MyStruct" , [
1160+ {
1161+ name : '";{}' ,
1162+ type : xdr . ScSpecTypeDef . scSpecTypeU32 ( ) ,
1163+ } ,
1164+ ] ) ;
1165+ const spec = new contract . Spec ( [ structSpec . toXDR ( "base64" ) ] ) ;
1166+ const result = BindingGenerator . fromSpec ( spec ) . generate ( defaultOptions ) ;
1167+
1168+ expect ( result . types ) . toContain ( "_unnamed: number" ) ;
1169+ } ) ;
1170+ } ) ;
1171+
10271172 describe ( "generate - full contract scenario" , ( ) => {
10281173 it ( "generates complete bindings for token-like contract" , ( ) => {
10291174 const specs = [
0 commit comments