Skip to content

Commit 733dc6a

Browse files
authored
Add stringer-alike String methods to non-string LSP enums (#2148)
1 parent 66ab80d commit 733dc6a

File tree

4 files changed

+526
-62
lines changed

4 files changed

+526
-62
lines changed

internal/fourslash/baselineutil.go

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -904,62 +904,5 @@ func codeFence(lang string, code string) string {
904904
}
905905

906906
func symbolInformationToData(symbol *lsproto.SymbolInformation) string {
907-
var symbolKindToString string
908-
switch symbol.Kind {
909-
case lsproto.SymbolKindFile:
910-
symbolKindToString = "file"
911-
case lsproto.SymbolKindModule:
912-
symbolKindToString = "module"
913-
case lsproto.SymbolKindNamespace:
914-
symbolKindToString = "namespace"
915-
case lsproto.SymbolKindPackage:
916-
symbolKindToString = "package"
917-
case lsproto.SymbolKindClass:
918-
symbolKindToString = "class"
919-
case lsproto.SymbolKindMethod:
920-
symbolKindToString = "method"
921-
case lsproto.SymbolKindProperty:
922-
symbolKindToString = "property"
923-
case lsproto.SymbolKindField:
924-
symbolKindToString = "field"
925-
case lsproto.SymbolKindConstructor:
926-
symbolKindToString = "constructor"
927-
case lsproto.SymbolKindEnum:
928-
symbolKindToString = "enum"
929-
case lsproto.SymbolKindInterface:
930-
symbolKindToString = "interface"
931-
case lsproto.SymbolKindFunction:
932-
symbolKindToString = "function"
933-
case lsproto.SymbolKindVariable:
934-
symbolKindToString = "variable"
935-
case lsproto.SymbolKindConstant:
936-
symbolKindToString = "constant"
937-
case lsproto.SymbolKindString:
938-
symbolKindToString = "string"
939-
case lsproto.SymbolKindNumber:
940-
symbolKindToString = "number"
941-
case lsproto.SymbolKindBoolean:
942-
symbolKindToString = "boolean"
943-
case lsproto.SymbolKindArray:
944-
symbolKindToString = "array"
945-
case lsproto.SymbolKindObject:
946-
symbolKindToString = "object"
947-
case lsproto.SymbolKindKey:
948-
symbolKindToString = "key"
949-
case lsproto.SymbolKindNull:
950-
symbolKindToString = "null"
951-
case lsproto.SymbolKindEnumMember:
952-
symbolKindToString = "enumMember"
953-
case lsproto.SymbolKindStruct:
954-
symbolKindToString = "struct"
955-
case lsproto.SymbolKindEvent:
956-
symbolKindToString = "event"
957-
case lsproto.SymbolKindOperator:
958-
symbolKindToString = "operator"
959-
case lsproto.SymbolKindTypeParameter:
960-
symbolKindToString = "typeParameter"
961-
default:
962-
symbolKindToString = "unknown"
963-
}
964-
return fmt.Sprintf("{| name: %s, kind: %s |}", symbol.Name, symbolKindToString)
907+
return fmt.Sprintf("{| name: %s, kind: %s |}", symbol.Name, symbol.Kind.String())
965908
}

internal/lsp/lsproto/_generate/generate.mts

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,14 @@ function generateCode() {
10131013
}
10141014
}
10151015

1016+
// Helper function to detect if an enum is a bitflag enum
1017+
// Hardcoded list of bitflag enums
1018+
const bitflagEnums = new Set(["WatchKind"]);
1019+
1020+
function isBitflagEnum(enumeration: any): boolean {
1021+
return bitflagEnums.has(enumeration.name);
1022+
}
1023+
10161024
// Generate enumerations
10171025
writeLine("// Enumerations\n");
10181026

@@ -1041,6 +1049,8 @@ function generateCode() {
10411049

10421050
const enumValues = enumeration.values.map(value => ({
10431051
value: String(value.value),
1052+
numericValue: Number(value.value),
1053+
name: value.name,
10441054
identifier: `${enumeration.name}${value.name}`,
10451055
documentation: value.documentation,
10461056
deprecated: value.deprecated,
@@ -1066,6 +1076,194 @@ function generateCode() {
10661076

10671077
writeLine(")");
10681078
writeLine("");
1079+
1080+
// Generate String() method for non-string enums
1081+
if (enumeration.type.name !== "string") {
1082+
const isBitflag = isBitflagEnum(enumeration);
1083+
1084+
if (isBitflag) {
1085+
// Generate bitflag-aware String() method using stringer-style efficiency
1086+
const sortedValues = [...enumValues].sort((a, b) => a.numericValue - b.numericValue);
1087+
const names = sortedValues.map(v => v.name);
1088+
const values = sortedValues.map(v => v.numericValue);
1089+
1090+
const nameConst = `_${enumeration.name}_name`;
1091+
const indexVar = `_${enumeration.name}_index`;
1092+
const combinedNames = names.join("");
1093+
1094+
writeLine(`const ${nameConst} = "${combinedNames}"`);
1095+
write(`var ${indexVar} = [...]uint16{0`);
1096+
let offset = 0;
1097+
for (const name of names) {
1098+
offset += name.length;
1099+
write(`, ${offset}`);
1100+
}
1101+
writeLine(`}`);
1102+
writeLine("");
1103+
1104+
writeLine(`func (e ${enumeration.name}) String() string {`);
1105+
writeLine(`\tif e == 0 {`);
1106+
writeLine(`\t\treturn "0"`);
1107+
writeLine(`\t}`);
1108+
writeLine(`\tvar parts []string`);
1109+
for (let i = 0; i < values.length; i++) {
1110+
writeLine(`\tif e&${values[i]} != 0 {`);
1111+
writeLine(`\t\tparts = append(parts, ${nameConst}[${indexVar}[${i}]:${indexVar}[${i + 1}]])`);
1112+
writeLine(`\t}`);
1113+
}
1114+
writeLine(`\tif len(parts) == 0 {`);
1115+
writeLine(`\t\treturn fmt.Sprintf("${enumeration.name}(%d)", e)`);
1116+
writeLine(`\t}`);
1117+
writeLine(`\treturn strings.Join(parts, "|")`);
1118+
writeLine(`}`);
1119+
writeLine("");
1120+
}
1121+
else {
1122+
// Generate regular String() method using stringer-style approach
1123+
// Split values into runs of contiguous values
1124+
const sortedValues = [...enumValues].sort((a, b) => a.numericValue - b.numericValue);
1125+
1126+
// Split into runs
1127+
const runs: Array<{ names: string[]; values: number[]; }> = [];
1128+
let currentRun = { names: [sortedValues[0].name], values: [sortedValues[0].numericValue] };
1129+
1130+
for (let i = 1; i < sortedValues.length; i++) {
1131+
if (sortedValues[i].numericValue === sortedValues[i - 1].numericValue + 1) {
1132+
currentRun.names.push(sortedValues[i].name);
1133+
currentRun.values.push(sortedValues[i].numericValue);
1134+
}
1135+
else {
1136+
runs.push(currentRun);
1137+
currentRun = { names: [sortedValues[i].name], values: [sortedValues[i].numericValue] };
1138+
}
1139+
}
1140+
runs.push(currentRun);
1141+
1142+
const nameConst = `_${enumeration.name}_name`;
1143+
const indexVar = `_${enumeration.name}_index`;
1144+
1145+
if (runs.length === 1) {
1146+
// Single contiguous run - simple case
1147+
const combinedNames = runs[0].names.join("");
1148+
writeLine(`const ${nameConst} = "${combinedNames}"`);
1149+
write(`var ${indexVar} = [...]uint16{0`);
1150+
let offset = 0;
1151+
for (const name of runs[0].names) {
1152+
offset += name.length;
1153+
write(`, ${offset}`);
1154+
}
1155+
writeLine(`}`);
1156+
writeLine("");
1157+
1158+
const minVal = runs[0].values[0];
1159+
writeLine(`func (e ${enumeration.name}) String() string {`);
1160+
writeLine(`\ti := int(e) - ${minVal}`);
1161+
// For unsigned types, i can still be negative if e < minVal (due to underflow in conversion)
1162+
// So we always need to check both bounds
1163+
writeLine(`\tif i < 0 || i >= len(${indexVar})-1 {`);
1164+
writeLine(`\t\treturn fmt.Sprintf("${enumeration.name}(%d)", e)`);
1165+
writeLine(`\t}`);
1166+
writeLine(`\treturn ${nameConst}[${indexVar}[i]:${indexVar}[i+1]]`);
1167+
writeLine(`}`);
1168+
writeLine("");
1169+
}
1170+
else if (runs.length <= 10) {
1171+
// Multiple runs - use switch statement
1172+
let allNames = "";
1173+
const runInfo: Array<{ startOffset: number; endOffset: number; minVal: number; maxVal: number; }> = [];
1174+
1175+
for (const run of runs) {
1176+
const startOffset = allNames.length;
1177+
allNames += run.names.join("");
1178+
const endOffset = allNames.length;
1179+
runInfo.push({
1180+
startOffset,
1181+
endOffset,
1182+
minVal: run.values[0],
1183+
maxVal: run.values[run.values.length - 1],
1184+
});
1185+
}
1186+
1187+
writeLine(`const ${nameConst} = "${allNames}"`);
1188+
writeLine("");
1189+
1190+
// Generate index variables for each run
1191+
for (let i = 0; i < runs.length; i++) {
1192+
write(`var ${indexVar}_${i} = [...]uint16{0`);
1193+
let offset = 0;
1194+
for (const name of runs[i].names) {
1195+
offset += name.length;
1196+
write(`, ${offset}`);
1197+
}
1198+
writeLine(`}`);
1199+
}
1200+
writeLine("");
1201+
1202+
writeLine(`func (e ${enumeration.name}) String() string {`);
1203+
writeLine(`\tswitch {`);
1204+
1205+
for (let i = 0; i < runs.length; i++) {
1206+
const run = runs[i];
1207+
const info = runInfo[i];
1208+
1209+
if (run.values.length === 1) {
1210+
writeLine(`\tcase e == ${run.values[0]}:`);
1211+
writeLine(`\t\treturn ${nameConst}[${info.startOffset}:${info.endOffset}]`);
1212+
}
1213+
else {
1214+
if (info.minVal === 0 && baseType.startsWith("uint")) {
1215+
writeLine(`\tcase e <= ${info.maxVal}:`);
1216+
}
1217+
else if (info.minVal === 0) {
1218+
writeLine(`\tcase 0 <= e && e <= ${info.maxVal}:`);
1219+
}
1220+
else {
1221+
writeLine(`\tcase ${info.minVal} <= e && e <= ${info.maxVal}:`);
1222+
}
1223+
writeLine(`\t\ti := int(e) - ${info.minVal}`);
1224+
writeLine(`\t\treturn ${nameConst}[${info.startOffset}+${indexVar}_${i}[i]:${info.startOffset}+${indexVar}_${i}[i+1]]`);
1225+
}
1226+
}
1227+
1228+
writeLine(`\tdefault:`);
1229+
writeLine(`\t\treturn fmt.Sprintf("${enumeration.name}(%d)", e)`);
1230+
writeLine(`\t}`);
1231+
writeLine(`}`);
1232+
writeLine("");
1233+
}
1234+
else {
1235+
// Too many runs - use a map
1236+
let allNames = "";
1237+
const valueMap: Array<{ value: number; startOffset: number; endOffset: number; }> = [];
1238+
1239+
for (const run of runs) {
1240+
for (let i = 0; i < run.names.length; i++) {
1241+
const startOffset = allNames.length;
1242+
allNames += run.names[i];
1243+
const endOffset = allNames.length;
1244+
valueMap.push({ value: run.values[i], startOffset, endOffset });
1245+
}
1246+
}
1247+
1248+
writeLine(`const ${nameConst} = "${allNames}"`);
1249+
writeLine("");
1250+
writeLine(`var ${enumeration.name}_map = map[${enumeration.name}]string{`);
1251+
for (const entry of valueMap) {
1252+
writeLine(`\t${entry.value}: ${nameConst}[${entry.startOffset}:${entry.endOffset}],`);
1253+
}
1254+
writeLine(`}`);
1255+
writeLine("");
1256+
1257+
writeLine(`func (e ${enumeration.name}) String() string {`);
1258+
writeLine(`\tif str, ok := ${enumeration.name}_map[e]; ok {`);
1259+
writeLine(`\t\treturn str`);
1260+
writeLine(`\t}`);
1261+
writeLine(`\treturn fmt.Sprintf("${enumeration.name}(%d)", e)`);
1262+
writeLine(`}`);
1263+
writeLine("");
1264+
}
1265+
}
1266+
}
10691267
}
10701268

10711269
const requestsAndNotifications: (Request | Notification)[] = [...model.requests, ...model.notifications];

0 commit comments

Comments
 (0)