修 id 解析
This commit is contained in:
+121
-5
@@ -4,7 +4,15 @@ import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
import { ModIndexer } from "../out/indexer/indexer.js";
|
||||
import { CachedDirectoryWalker } from "../out/indexer/fileScanner.js";
|
||||
import { isReferenceAttribute, resolveReferenceTargets } from "../out/indexer/refs.js";
|
||||
import {
|
||||
isLocalReferenceAttribute,
|
||||
isReferenceAttribute,
|
||||
isReferenceAttributeOfType,
|
||||
resolveReferenceTargets,
|
||||
resolveReferenceTargetsForType,
|
||||
} from "../out/indexer/refs.js";
|
||||
import { parseXml } from "../out/language/xmlParser.js";
|
||||
import { resolveElementType } from "../out/language/typeContext.js";
|
||||
import * as model from "../out/model/schemaModel.js";
|
||||
|
||||
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
@@ -63,10 +71,21 @@ test("isReferenceAttribute distinguishes references from enums/paths", () => {
|
||||
assert.equal(isReferenceAttribute("FireWeaponNugget", "WeaponName"), true);
|
||||
});
|
||||
|
||||
test("untyped references (isRef without refType) resolve to any declared id", () => {
|
||||
// LocomotorSet/@Locomotor has type AssetReference: xas:isRef="true" with
|
||||
// no refType. It must be treated as a reference and match the id regardless
|
||||
// of the target asset type.
|
||||
test("attribute-level xas:refType is preserved in the model", () => {
|
||||
// <xs:attribute name="Locomotor" type="AssetReference"
|
||||
// xas:refType="LocomotorTemplate" /> — the refType lives on
|
||||
// the attribute node, not on the AssetReference simple type.
|
||||
assert.equal(
|
||||
model.attributesOfElement("LocomotorSet").find((a) => a.name === "Locomotor")?.refType,
|
||||
"LocomotorTemplate",
|
||||
);
|
||||
assert.equal(
|
||||
model.attributesOfElement("ArmorSet").find((a) => a.name === "Armor")?.refType,
|
||||
"ArmorTemplate",
|
||||
);
|
||||
});
|
||||
|
||||
test("typed Locomotor references resolve only to LocomotorTemplate defs", () => {
|
||||
const idx = {
|
||||
assetsById: new Map([
|
||||
[
|
||||
@@ -79,6 +98,14 @@ test("untyped references (isRef without refType) resolve to any declared id", ()
|
||||
line: 5,
|
||||
origin: "project",
|
||||
},
|
||||
{
|
||||
// Same id under a different type must NOT satisfy the reference.
|
||||
type: "GameObject",
|
||||
id: "AlliedAntiVehicleVehicleTech1Locomotor",
|
||||
file: "GameObject.xml",
|
||||
line: 1,
|
||||
origin: "project",
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
@@ -100,3 +127,92 @@ test("untyped references (isRef without refType) resolve to any declared id", ()
|
||||
// A non-reference attribute still returns nothing.
|
||||
assert.equal(resolveReferenceTargets(idx, "LocomotorSet", "EditorName", "X").length, 0);
|
||||
});
|
||||
|
||||
test("module ids declare the element itself and are never global references", () => {
|
||||
// The exact reported scenario: <TruckDraw id="ModuleTag_Draw"/> inside a
|
||||
// GameObject. ModuleData@id carries xas:refType="ModuleData", which is the
|
||||
// element's own base type -> a definition site, not a reference.
|
||||
const xml = `
|
||||
<AssetDeclaration>
|
||||
<GameObject id="GuardianTank">
|
||||
<Draws>
|
||||
<TruckDraw id="ModuleTag_Draw" />
|
||||
</Draws>
|
||||
</GameObject>
|
||||
</AssetDeclaration>`;
|
||||
const doc = parseXml(xml);
|
||||
const truckDraw = doc.elements.find((e) => e.name === "TruckDraw");
|
||||
assert.ok(truckDraw);
|
||||
const elType = resolveElementType(truckDraw);
|
||||
assert.equal(elType, "W3DTruckDrawModuleData");
|
||||
assert.equal(isLocalReferenceAttribute(elType, "id"), true);
|
||||
assert.equal(isReferenceAttributeOfType(elType, "id"), false);
|
||||
const idx = { assetsById: new Map(), assets: new Map(), defines: new Map() };
|
||||
assert.equal(
|
||||
resolveReferenceTargetsForType(idx, elType, "id", "ModuleTag_Draw").length,
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
test("Poid attributes reference pipeline-local objects, not global assets", () => {
|
||||
// AttachModuleId / ModuleId / AutoResolveBody are Poid-typed references to
|
||||
// modules inside the same GameObject; the global index cannot judge them.
|
||||
assert.equal(isReferenceAttributeOfType("AttachNugget", "AttachModuleId"), false);
|
||||
assert.equal(isLocalReferenceAttribute("AttachNugget", "AttachModuleId"), true);
|
||||
const idx = { assetsById: new Map(), assets: new Map(), defines: new Map() };
|
||||
assert.equal(
|
||||
resolveReferenceTargetsForType(idx, "AttachNugget", "AttachModuleId", "ModuleTag_X").length,
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
test("cross-type id references (RoadObject@id -> Road) stay real references", () => {
|
||||
// RoadObject declares its id with xas:refType="Road" (an AssetReference,
|
||||
// NOT the element's own type), so it must keep reference semantics.
|
||||
assert.equal(
|
||||
model.attributesOfType("RoadObject").find((a) => a.name === "id")?.refType,
|
||||
"Road",
|
||||
);
|
||||
assert.equal(isReferenceAttributeOfType("RoadObject", "id"), true);
|
||||
const idx = {
|
||||
assetsById: new Map([
|
||||
[
|
||||
"road01",
|
||||
[
|
||||
{
|
||||
type: "Road",
|
||||
id: "Road01",
|
||||
file: "Roads.xml",
|
||||
line: 3,
|
||||
origin: "project",
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
assets: new Map(),
|
||||
projectDir: ".",
|
||||
sdkDir: ".",
|
||||
defines: new Map(),
|
||||
files: new Map(),
|
||||
streams: [],
|
||||
manifests: new Map(),
|
||||
sourceCandidates: [],
|
||||
diagnostics: [],
|
||||
stats: {},
|
||||
};
|
||||
const targets = resolveReferenceTargetsForType(idx, "RoadObject", "id", "Road01");
|
||||
assert.equal(targets.length, 1);
|
||||
assert.equal(targets[0].def.type, "Road");
|
||||
});
|
||||
|
||||
test("top-level asset ids with plain pipeline ids are not references", () => {
|
||||
// LocomotorSet@id / ArmorSet@id are Poid pipeline ids without a refType:
|
||||
// they identify the element and never need a global definition.
|
||||
assert.equal(isReferenceAttributeOfType("LocomotorSet", "id"), false);
|
||||
assert.equal(isReferenceAttributeOfType("ArmorTemplateSet", "id"), false);
|
||||
const idx = { assetsById: new Map(), assets: new Map(), defines: new Map() };
|
||||
assert.equal(
|
||||
resolveReferenceTargetsForType(idx, "LocomotorSet", "id", "AnyLocalId").length,
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -17,6 +17,36 @@ test("GameObject has expected attributes", () => {
|
||||
assert.ok(attrs.some((a) => a.name === "inheritFrom"));
|
||||
});
|
||||
|
||||
test("attribute-level xas:refType is captured (module ids, map objects)", () => {
|
||||
// ModuleData@id is declared as <xs:attribute name="id" type="Poid"
|
||||
// xas:refType="ModuleData" />; the refType must reach every module subtype.
|
||||
const moduleId = model.attributesOfType("ModuleData").find((a) => a.name === "id");
|
||||
assert.equal(moduleId?.refType, "ModuleData");
|
||||
assert.equal(
|
||||
model.attributesOfType("W3DTruckDrawModuleData").find((a) => a.name === "id")?.refType,
|
||||
"ModuleData",
|
||||
);
|
||||
// MapObject@id declares itself; ThingTemplate references a GameObject.
|
||||
assert.equal(
|
||||
model.attributesOfType("MapObject").find((a) => a.name === "id")?.refType,
|
||||
"MapObject",
|
||||
);
|
||||
assert.equal(
|
||||
model.attributesOfType("MapObject").find((a) => a.name === "ThingTemplate")?.refType,
|
||||
"GameObject",
|
||||
);
|
||||
// RoadObject@id references a different global asset type (Road).
|
||||
assert.equal(
|
||||
model.attributesOfType("RoadObject").find((a) => a.name === "id")?.refType,
|
||||
"Road",
|
||||
);
|
||||
// Poid pipeline-local references keep their typed targets.
|
||||
assert.equal(
|
||||
model.attributesOfType("AttachNugget").find((a) => a.name === "AttachModuleId")?.refType,
|
||||
"ModuleData",
|
||||
);
|
||||
});
|
||||
|
||||
test("Include has reference/instance/all enum", () => {
|
||||
const attrs = model.attributesOfElement("Include");
|
||||
const type = attrs.find((a) => a.name === "type");
|
||||
|
||||
Reference in New Issue
Block a user