Skip to content

Commit ef2f9d9

Browse files
committed
Fix incorrect model files on case-insensitive file systems
This fixes some incorrect model files on case-insensitive file systems when the package names are the same but the capitalization is different. For example, when there are two packages `Volo.Abp.TestApp.MongoDb` and `Volo.Abp.TestApp.MongoDB`, there would be 1 model file for each package. However, on case-insensitive file systems, the second file would overwrite the first file. This results in missing models. This fixes it by canonicalizing the filenames to lowercase and writing all files with the same package name to the same file.
1 parent 60d777a commit ef2f9d9

File tree

2 files changed

+170
-11
lines changed

2 files changed

+170
-11
lines changed

extensions/ql-vscode/src/model-editor/yaml.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -181,25 +181,40 @@ function createDataExtensionYamlsByGrouping(
181181
>,
182182
createFilename: (method: Method) => string,
183183
): Record<string, string> {
184-
const methodsByFilename: Record<string, Record<string, ModeledMethod[]>> = {};
184+
const actualFilenameByCanonicalFilename: Record<string, string> = {};
185+
186+
const methodsByCanonicalFilename: Record<
187+
string,
188+
Record<string, ModeledMethod[]>
189+
> = {};
185190

186191
// We only want to generate a yaml file when it's a known external API usage
187192
// and there are new modeled methods for it. This avoids us overwriting other
188193
// files that may contain data we don't know about.
189194
for (const method of methods) {
190195
if (method.signature in newModeledMethods) {
191-
methodsByFilename[createFilename(method)] = {};
196+
const filename = createFilename(method);
197+
const canonicalFilename = canonicalizeFilename(filename);
198+
199+
methodsByCanonicalFilename[canonicalFilename] = {};
200+
actualFilenameByCanonicalFilename[canonicalFilename] = filename;
192201
}
193202
}
194203

195204
// First populate methodsByFilename with any existing modeled methods.
196205
for (const [filename, methodsBySignature] of Object.entries(
197206
existingModeledMethods,
198207
)) {
199-
if (filename in methodsByFilename) {
208+
const canonicalFilename = canonicalizeFilename(filename);
209+
210+
if (canonicalFilename in methodsByCanonicalFilename) {
200211
for (const [signature, methods] of Object.entries(methodsBySignature)) {
201-
methodsByFilename[filename][signature] = [...methods];
212+
methodsByCanonicalFilename[canonicalFilename][signature] = [...methods];
202213
}
214+
215+
// Ensure that if a file exists on disk, we use the same capitalization
216+
// as the original file.
217+
actualFilenameByCanonicalFilename[canonicalFilename] = filename;
203218
}
204219
}
205220

@@ -209,19 +224,29 @@ function createDataExtensionYamlsByGrouping(
209224
const newMethods = newModeledMethods[method.signature];
210225
if (newMethods) {
211226
const filename = createFilename(method);
227+
const canonicalFilename = canonicalizeFilename(filename);
212228

213229
// Override any existing modeled methods with the new ones.
214-
methodsByFilename[filename][method.signature] = [...newMethods];
230+
methodsByCanonicalFilename[canonicalFilename][method.signature] = [
231+
...newMethods,
232+
];
233+
234+
if (!(canonicalFilename in actualFilenameByCanonicalFilename)) {
235+
actualFilenameByCanonicalFilename[canonicalFilename] = filename;
236+
}
215237
}
216238
}
217239

218240
const result: Record<string, string> = {};
219241

220-
for (const [filename, methods] of Object.entries(methodsByFilename)) {
221-
result[filename] = createDataExtensionYaml(
222-
language,
223-
Object.values(methods).flatMap((methods) => methods),
224-
);
242+
for (const [canonicalFilename, methods] of Object.entries(
243+
methodsByCanonicalFilename,
244+
)) {
245+
result[actualFilenameByCanonicalFilename[canonicalFilename]] =
246+
createDataExtensionYaml(
247+
language,
248+
Object.values(methods).flatMap((methods) => methods),
249+
);
225250
}
226251

227252
return result;
@@ -299,6 +324,13 @@ export function createFilenameForPackage(
299324
return `${prefix}${packageName}${suffix}.yml`;
300325
}
301326

327+
function canonicalizeFilename(filename: string) {
328+
// We want to canonicalize filenames so that they are always in the same format
329+
// for comparison purposes. This is important because we want to avoid overwriting
330+
// data extension YAML files on case-insensitive file systems.
331+
return filename.toLowerCase();
332+
}
333+
302334
function validateModelExtensionFile(data: unknown): data is ModelExtensionFile {
303335
modelExtensionFileSchemaValidate(data);
304336

extensions/ql-vscode/test/unit-tests/model-editor/yaml.test.ts

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import {
66
createFilenameForPackage,
77
loadDataExtensionYaml,
88
} from "../../../src/model-editor/yaml";
9-
import { CallClassification } from "../../../src/model-editor/method";
9+
import { CallClassification, Method } from "../../../src/model-editor/method";
1010
import { QueryLanguage } from "../../../src/common/query-language";
11+
import { ModeledMethod } from "../../../src/model-editor/modeled-method";
1112

1213
describe("createDataExtensionYaml", () => {
1314
it("creates the correct YAML file", () => {
@@ -980,6 +981,132 @@ describe("createDataExtensionYamlsForFrameworkMode", () => {
980981
`,
981982
});
982983
});
984+
985+
describe("with same package names but different capitalizations", () => {
986+
const methods: Method[] = [
987+
{
988+
library: "HostTestAppDbContext",
989+
signature:
990+
"Volo.Abp.TestApp.MongoDb.HostTestAppDbContext#get_FifthDbContextDummyEntity()",
991+
packageName: "Volo.Abp.TestApp.MongoDb",
992+
typeName: "HostTestAppDbContext",
993+
methodName: "get_FifthDbContextDummyEntity",
994+
methodParameters: "()",
995+
supported: false,
996+
supportedType: "none",
997+
usages: [],
998+
},
999+
{
1000+
library: "CityRepository",
1001+
signature:
1002+
"Volo.Abp.TestApp.MongoDB.CityRepository#FindByNameAsync(System.String)",
1003+
packageName: "Volo.Abp.TestApp.MongoDB",
1004+
typeName: "CityRepository",
1005+
methodName: "FindByNameAsync",
1006+
methodParameters: "(System.String)",
1007+
supported: false,
1008+
supportedType: "none",
1009+
usages: [],
1010+
},
1011+
];
1012+
const newModeledMethods: Record<string, ModeledMethod[]> = {
1013+
"Volo.Abp.TestApp.MongoDb.HostTestAppDbContext#get_FifthDbContextDummyEntity()":
1014+
[
1015+
{
1016+
type: "sink",
1017+
input: "Argument[0]",
1018+
kind: "sql",
1019+
provenance: "df-generated",
1020+
signature:
1021+
"Volo.Abp.TestApp.MongoDb.HostTestAppDbContext#get_FifthDbContextDummyEntity()",
1022+
packageName: "Volo.Abp.TestApp.MongoDb",
1023+
typeName: "HostTestAppDbContext",
1024+
methodName: "get_FifthDbContextDummyEntity",
1025+
methodParameters: "()",
1026+
},
1027+
],
1028+
"Volo.Abp.TestApp.MongoDB.CityRepository#FindByNameAsync(System.String)":
1029+
[
1030+
{
1031+
type: "neutral",
1032+
kind: "summary",
1033+
provenance: "df-generated",
1034+
signature:
1035+
"Volo.Abp.TestApp.MongoDB.CityRepository#FindByNameAsync(System.String)",
1036+
packageName: "Volo.Abp.TestApp.MongoDB",
1037+
typeName: "CityRepository",
1038+
methodName: "FindByNameAsync",
1039+
methodParameters: "(System.String)",
1040+
},
1041+
],
1042+
};
1043+
const modelYaml = `extensions:
1044+
- addsTo:
1045+
pack: codeql/csharp-all
1046+
extensible: sourceModel
1047+
data: []
1048+
1049+
- addsTo:
1050+
pack: codeql/csharp-all
1051+
extensible: sinkModel
1052+
data:
1053+
- ["Volo.Abp.TestApp.MongoDb","HostTestAppDbContext",true,"get_FifthDbContextDummyEntity","()","","Argument[0]","sql","df-generated"]
1054+
1055+
- addsTo:
1056+
pack: codeql/csharp-all
1057+
extensible: summaryModel
1058+
data: []
1059+
1060+
- addsTo:
1061+
pack: codeql/csharp-all
1062+
extensible: neutralModel
1063+
data:
1064+
- ["Volo.Abp.TestApp.MongoDB","CityRepository","FindByNameAsync","(System.String)","summary","df-generated"]
1065+
`;
1066+
1067+
it("creates the correct YAML files when there are existing modeled methods", () => {
1068+
const yaml = createDataExtensionYamlsForFrameworkMode(
1069+
QueryLanguage.CSharp,
1070+
methods,
1071+
newModeledMethods,
1072+
{},
1073+
);
1074+
1075+
expect(yaml).toEqual({
1076+
"models/Volo.Abp.TestApp.MongoDB.model.yml": modelYaml,
1077+
});
1078+
});
1079+
1080+
it("creates the correct YAML files when there are existing modeled methods", () => {
1081+
const yaml = createDataExtensionYamlsForFrameworkMode(
1082+
QueryLanguage.CSharp,
1083+
methods,
1084+
newModeledMethods,
1085+
{
1086+
"models/Volo.Abp.TestApp.mongodb.model.yml": {
1087+
"Volo.Abp.TestApp.MongoDB.CityRepository#FindByNameAsync(System.String)":
1088+
[
1089+
{
1090+
type: "neutral",
1091+
kind: "summary",
1092+
provenance: "manual",
1093+
signature:
1094+
"Volo.Abp.TestApp.MongoDB.CityRepository#FindByNameAsync(System.String)",
1095+
packageName: "Volo.Abp.TestApp.MongoDB",
1096+
typeName: "CityRepository",
1097+
methodName: "FindByNameAsync",
1098+
methodParameters: "(System.String)",
1099+
},
1100+
],
1101+
},
1102+
},
1103+
);
1104+
1105+
expect(yaml).toEqual({
1106+
"models/Volo.Abp.TestApp.mongodb.model.yml": modelYaml,
1107+
});
1108+
});
1109+
});
9831110
});
9841111

9851112
describe("loadDataExtensionYaml", () => {

0 commit comments

Comments
 (0)