fix inherit from

This commit is contained in:
2026-08-11 19:42:23 +02:00
parent b5e055216c
commit 84a44bedfd
26 changed files with 966 additions and 67 deletions
+179
View File
@@ -239,6 +239,88 @@ test("Ctrl+click on simple-content text jumps to the definition", async () => {
);
});
test("hover on simpleContent complex content shows the referenced definition", async () => {
const text =
`<AssetDeclaration>\n` +
` <AudioEvent id="A">\n` +
` <Sound>VoiceFile</Sound>\n` +
` </AudioEvent>\n` +
`</AssetDeclaration>`;
const def = {
type: "AudioFile",
id: "VoiceFile",
file: URI,
line: 2,
origin: "project",
};
const scope = await makeScope(text, makeIdx([def]));
const provider = new Ra3HoverProvider({
isRa3Workspace: () => true,
getScope: async () => scope,
searchPaths: () => null,
});
const line = text.split("\n")[2];
const pos = new Position(2, line.indexOf("VoiceFile") + 3);
const hover = await provider.provideHover(makeDocument(text), pos, {});
assert.ok(hover, "hover is returned for AudioFileRefWithWeight content");
assert.match(hover.contents.value, /1 definition/);
assert.match(hover.contents.value, /AudioFile/);
});
test("Ctrl+click on simpleContent complex content jumps to the definition", async () => {
const text =
`<AssetDeclaration>\n` +
` <AudioEvent id="A">\n` +
` <Sound>VoiceFile</Sound>\n` +
` </AudioEvent>\n` +
`</AssetDeclaration>`;
const def = {
type: "AudioFile",
id: "VoiceFile",
file: URI,
line: 2,
origin: "project",
};
const scope = await makeScope(text, makeIdx([def]));
const provider = new Ra3DefinitionProvider({
isRa3Workspace: () => true,
getScope: async () => scope,
settings: { definitionMode: "all" },
indexer: null,
});
const line = text.split("\n")[2];
const pos = new Position(2, line.indexOf("VoiceFile") + 3);
const locations = await provider.provideDefinition(makeDocument(text), pos, {});
assert.ok(locations && locations.length === 1);
assert.equal(locations[0].uri.fsPath, URI);
});
test("diagnostics report unresolved simpleContent complex content references", async () => {
const text =
`<AssetDeclaration>\n` +
` <Multisound id="M">\n` +
` <Subsound>MissingEvent</Subsound>\n` +
` </Multisound>\n` +
`</AssetDeclaration>`;
const scope = await makeScope(text, makeIdx([]));
const collection = new FakeDiagnosticCollection();
const provider = new Ra3Diagnostics({
isRa3Workspace: () => true,
getScope: async () => scope,
settings: {
diagnoseUnknownElements: false,
reportUnresolvedReferences: "warning",
},
});
provider["collection"] = collection;
await provider.update(makeDocument(text));
const messages = collection.last.diags.map((d) => d.message);
assert.ok(
messages.some((m) => m.includes('Unresolved reference "MissingEvent"')),
"MultisoundSubsoundRef text is diagnosed as a typed content reference",
);
});
test("Ctrl+click on a manifest definition maps to SageXml even when the mod shadows the DATA path", async () => {
const tmp = mkdtempSync(join(tmpdir(), "ra3-nav-manifest-"));
try {
@@ -518,6 +600,103 @@ test("fragment diagnostics ignore unknown wrapper roots and still report missing
);
});
test("diagnostics accept universal inheritFrom on asset types", async () => {
const text =
`<AssetDeclaration>\n` +
` <FXList id="FX_A" inheritFrom="FX_Base">\n` +
` <NuggetList/>\n` +
` </FXList>\n` +
`</AssetDeclaration>`;
const scope = await makeScope(text, makeIdx([]));
const collection = new FakeDiagnosticCollection();
const provider = new Ra3Diagnostics({
isRa3Workspace: () => true,
getScope: async () => scope,
settings: {
diagnoseUnknownElements: true,
reportUnresolvedReferences: "none",
},
});
provider["collection"] = collection;
await provider.update(makeDocument(text));
const codes = collection.last.diags.map((d) => d.code);
assert.ok(
!codes.includes("unknown-attribute"),
"FXList inheritFrom must not be flagged as unknown",
);
const badText =
`<AssetDeclaration>\n` +
` <FXList id="FX_B" Bogus="x">\n` +
` <NuggetList/>\n` +
` </FXList>\n` +
`</AssetDeclaration>`;
const badScope = await makeScope(badText, makeIdx([]));
const badCollection = new FakeDiagnosticCollection();
const badProvider = new Ra3Diagnostics({
isRa3Workspace: () => true,
getScope: async () => badScope,
settings: {
diagnoseUnknownElements: true,
reportUnresolvedReferences: "none",
},
});
badProvider["collection"] = badCollection;
await badProvider.update(makeDocument(badText));
assert.ok(
badCollection.last.diags.map((d) => d.code).includes("unknown-attribute"),
"a real unknown attribute is still reported",
);
});
test("diagnostics keep simpleContent extension attributes known", async () => {
const text =
`<AssetDeclaration>\n` +
` <AudioEvent id="A">\n` +
` <Sound Weight="100">AudioFile</Sound>\n` +
` </AudioEvent>\n` +
`</AssetDeclaration>`;
const scope = await makeScope(text, makeIdx([]));
const collection = new FakeDiagnosticCollection();
const provider = new Ra3Diagnostics({
isRa3Workspace: () => true,
getScope: async () => scope,
settings: {
diagnoseUnknownElements: true,
reportUnresolvedReferences: "none",
},
});
provider["collection"] = collection;
await provider.update(makeDocument(text));
const codes = collection.last.diags.map((d) => d.code);
assert.ok(
!codes.includes("unknown-attribute"),
"Weight on AudioFileRefWithWeight must be known",
);
});
test("diagnostics use the top-level asset type for colliding fragment roots", async () => {
const text =
`<EvaEvent id="IncomingTransmission" Priority="100" TimeBetweenEvents="0ms" ExpirationTime="10000ms"/>`;
const scope = await makeScope(text, makeIdx([]));
const collection = new FakeDiagnosticCollection();
const provider = new Ra3Diagnostics({
isRa3Workspace: () => true,
getScope: async () => scope,
settings: {
diagnoseUnknownElements: true,
reportUnresolvedReferences: "none",
},
});
provider["collection"] = collection;
await provider.update(makeDocument(text));
const codes = collection.last.diags.map((d) => d.code);
assert.ok(
!codes.includes("unknown-attribute"),
"EvaEvent fragment root attributes must resolve against the top-level asset type",
);
});
test("full documents still require ids on top-level assets", async () => {
const text = `<AssetDeclaration>\n <GameObject/>\n</AssetDeclaration>`;
const scope = await makeScope(text, makeIdx([]));