Refactor type analysis utilities and field collection logic using type stack management
This commit is contained in:
@@ -21,6 +21,7 @@ interface TraversalOptions {
|
|||||||
skipProperties: ReadonlySet<string>;
|
skipProperties: ReadonlySet<string>;
|
||||||
includeIdentityProperties: boolean;
|
includeIdentityProperties: boolean;
|
||||||
skipPropertiesForThisObject?: ReadonlySet<string>;
|
skipPropertiesForThisObject?: ReadonlySet<string>;
|
||||||
|
typeStack: ReadonlySet<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AUTHORABLE_ROOTS: readonly FieldRoot[] = [
|
const AUTHORABLE_ROOTS: readonly FieldRoot[] = [
|
||||||
@@ -313,6 +314,29 @@ function isLeafType(type: ts.Type): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTypeKey(type: ts.Type): string {
|
||||||
|
const internalType = type as ts.Type & { id?: number };
|
||||||
|
return String(
|
||||||
|
internalType.id ??
|
||||||
|
type.aliasSymbol?.escapedName ??
|
||||||
|
type.symbol?.escapedName ??
|
||||||
|
type.flags
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeLabel(type: ts.Type): string {
|
||||||
|
try {
|
||||||
|
return checker.typeToString(type);
|
||||||
|
} catch {
|
||||||
|
return getTypeKey(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCallableType(type: ts.Type): boolean {
|
||||||
|
const normalizedType = withoutNullish(type);
|
||||||
|
return normalizedType.getCallSignatures().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function getArrayElementType(type: ts.Type): ts.Type | null {
|
function getArrayElementType(type: ts.Type): ts.Type | null {
|
||||||
const normalizedType = withoutNullish(type);
|
const normalizedType = withoutNullish(type);
|
||||||
|
|
||||||
@@ -325,7 +349,11 @@ function getArrayElementType(type: ts.Type): ts.Type | null {
|
|||||||
|
|
||||||
function isTypedArrayType(type: ts.Type): boolean {
|
function isTypedArrayType(type: ts.Type): boolean {
|
||||||
const normalizedType = withoutNullish(type);
|
const normalizedType = withoutNullish(type);
|
||||||
const typeName = checker.typeToString(normalizedType);
|
const typeName = String(
|
||||||
|
normalizedType.aliasSymbol?.escapedName ??
|
||||||
|
normalizedType.symbol?.escapedName ??
|
||||||
|
""
|
||||||
|
);
|
||||||
return TYPED_ARRAY_TYPE_NAMES.has(typeName);
|
return TYPED_ARRAY_TYPE_NAMES.has(typeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,6 +497,14 @@ function collectFields(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isCallableType(normalizedType)) {
|
||||||
|
entries.push({
|
||||||
|
path: currentPath,
|
||||||
|
condition: options.condition
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const arrayElementType = getArrayElementType(normalizedType);
|
const arrayElementType = getArrayElementType(normalizedType);
|
||||||
|
|
||||||
if (arrayElementType !== null) {
|
if (arrayElementType !== null) {
|
||||||
@@ -492,11 +528,37 @@ function collectFields(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedType.isUnion()) {
|
if (normalizedType.isUnion()) {
|
||||||
collectUnionFields(normalizedType.types, currentPath, entries, options);
|
const typeKey = getTypeKey(normalizedType);
|
||||||
|
|
||||||
|
if (options.typeStack.has(typeKey)) {
|
||||||
|
entries.push({
|
||||||
|
path: currentPath,
|
||||||
|
condition: options.condition
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
collectUnionFields(normalizedType.types, currentPath, entries, {
|
||||||
|
...options,
|
||||||
|
typeStack: new Set([...options.typeStack, typeKey])
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
collectObjectFields(normalizedType, currentPath, entries, options);
|
const typeKey = getTypeKey(normalizedType);
|
||||||
|
|
||||||
|
if (options.typeStack.has(typeKey)) {
|
||||||
|
entries.push({
|
||||||
|
path: currentPath,
|
||||||
|
condition: options.condition
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
collectObjectFields(normalizedType, currentPath, entries, {
|
||||||
|
...options,
|
||||||
|
typeStack: new Set([...options.typeStack, typeKey])
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectUnionFields(
|
function collectUnionFields(
|
||||||
@@ -548,7 +610,7 @@ function collectUnionFields(
|
|||||||
|
|
||||||
const typeLabels = objectTypes.map((objectType) => {
|
const typeLabels = objectTypes.map((objectType) => {
|
||||||
const propertyType = getPropertyType(objectType, name);
|
const propertyType = getPropertyType(objectType, name);
|
||||||
return propertyType === null ? "" : checker.typeToString(propertyType);
|
return propertyType === null ? "" : getTypeLabel(propertyType);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Set(typeLabels).size === 1;
|
return new Set(typeLabels).size === 1;
|
||||||
@@ -573,7 +635,7 @@ function collectUnionFields(
|
|||||||
? ""
|
? ""
|
||||||
: literalUnionLabels(discriminatorType)[0] ?? "";
|
: literalUnionLabels(discriminatorType)[0] ?? "";
|
||||||
const groupKey =
|
const groupKey =
|
||||||
discriminator === null ? checker.typeToString(objectType) : discriminatorValue;
|
discriminator === null ? getTypeLabel(objectType) : discriminatorValue;
|
||||||
groupedTypes.set(groupKey, [...(groupedTypes.get(groupKey) ?? []), objectType]);
|
groupedTypes.set(groupKey, [...(groupedTypes.get(groupKey) ?? []), objectType]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,7 +656,8 @@ function collectUnionFields(
|
|||||||
collectUnionFields(groupTypes, currentPath, entries, {
|
collectUnionFields(groupTypes, currentPath, entries, {
|
||||||
condition,
|
condition,
|
||||||
skipProperties: skippedForGroup,
|
skipProperties: skippedForGroup,
|
||||||
includeIdentityProperties: options.includeIdentityProperties
|
includeIdentityProperties: options.includeIdentityProperties,
|
||||||
|
typeStack: options.typeStack
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -603,7 +666,8 @@ function collectUnionFields(
|
|||||||
condition,
|
condition,
|
||||||
skipProperties: options.skipProperties,
|
skipProperties: options.skipProperties,
|
||||||
includeIdentityProperties: options.includeIdentityProperties,
|
includeIdentityProperties: options.includeIdentityProperties,
|
||||||
skipPropertiesForThisObject: skippedForGroup
|
skipPropertiesForThisObject: skippedForGroup,
|
||||||
|
typeStack: options.typeStack
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -630,7 +694,8 @@ function collectObjectFields(
|
|||||||
collectFields(propertyType, `${currentPath}.${propertyName}`, entries, {
|
collectFields(propertyType, `${currentPath}.${propertyName}`, entries, {
|
||||||
condition: options.condition,
|
condition: options.condition,
|
||||||
skipProperties: options.skipProperties,
|
skipProperties: options.skipProperties,
|
||||||
includeIdentityProperties: options.includeIdentityProperties
|
includeIdentityProperties: options.includeIdentityProperties,
|
||||||
|
typeStack: options.typeStack
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -688,7 +753,8 @@ function collectRootFields(
|
|||||||
collectFields(getRootType(root), root.path, entries, {
|
collectFields(getRootType(root), root.path, entries, {
|
||||||
condition: null,
|
condition: null,
|
||||||
skipProperties: new Set(root.skipProperties ?? []),
|
skipProperties: new Set(root.skipProperties ?? []),
|
||||||
includeIdentityProperties
|
includeIdentityProperties,
|
||||||
|
typeStack: new Set()
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -576,8 +576,8 @@ describe("validateSceneDocument", () => {
|
|||||||
navigationMode: "invalidMode" as unknown as "firstPerson",
|
navigationMode: "invalidMode" as unknown as "firstPerson",
|
||||||
interactionReachMeters: Number.NaN,
|
interactionReachMeters: Number.NaN,
|
||||||
interactionAngleDegrees: Number.NaN,
|
interactionAngleDegrees: Number.NaN,
|
||||||
allowLookInputTargetSwitch: true,
|
allowLookInputTargetSwitch: "yes",
|
||||||
targetButtonCyclesActiveTarget: false,
|
targetButtonCyclesActiveTarget: 1,
|
||||||
movementTemplate: {
|
movementTemplate: {
|
||||||
kind: "invalidTemplate",
|
kind: "invalidTemplate",
|
||||||
moveSpeed: 0,
|
moveSpeed: 0,
|
||||||
@@ -612,6 +612,7 @@ describe("validateSceneDocument", () => {
|
|||||||
sprint: "",
|
sprint: "",
|
||||||
crouch: "",
|
crouch: "",
|
||||||
interact: "",
|
interact: "",
|
||||||
|
clearTarget: "",
|
||||||
pauseTime: ""
|
pauseTime: ""
|
||||||
},
|
},
|
||||||
gamepad: {
|
gamepad: {
|
||||||
@@ -620,6 +621,7 @@ describe("validateSceneDocument", () => {
|
|||||||
sprint: "invalidButton",
|
sprint: "invalidButton",
|
||||||
crouch: "invalidButton",
|
crouch: "invalidButton",
|
||||||
interact: "invalidButton",
|
interact: "invalidButton",
|
||||||
|
clearTarget: "invalidButton",
|
||||||
pauseTime: "invalidButton"
|
pauseTime: "invalidButton"
|
||||||
}
|
}
|
||||||
} as unknown as ReturnType<
|
} as unknown as ReturnType<
|
||||||
@@ -657,6 +659,12 @@ describe("validateSceneDocument", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
code: "invalid-player-start-interaction-angle"
|
code: "invalid-player-start-interaction-angle"
|
||||||
}),
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
code: "invalid-player-start-look-input-target-switch"
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
code: "invalid-player-start-target-button-cycles-active-target"
|
||||||
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
code: "invalid-player-start-movement-template-kind"
|
code: "invalid-player-start-movement-template-kind"
|
||||||
}),
|
}),
|
||||||
@@ -714,6 +722,9 @@ describe("validateSceneDocument", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
code: "invalid-player-start-interact-keyboard-binding"
|
code: "invalid-player-start-interact-keyboard-binding"
|
||||||
}),
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
code: "invalid-player-start-clear-target-keyboard-binding"
|
||||||
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
code: "invalid-player-start-pause-keyboard-binding"
|
code: "invalid-player-start-pause-keyboard-binding"
|
||||||
}),
|
}),
|
||||||
@@ -729,6 +740,9 @@ describe("validateSceneDocument", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
code: "invalid-player-start-interact-gamepad-binding"
|
code: "invalid-player-start-interact-gamepad-binding"
|
||||||
}),
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
code: "invalid-player-start-clear-target-gamepad-binding"
|
||||||
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
code: "invalid-player-start-pause-gamepad-binding"
|
code: "invalid-player-start-pause-gamepad-binding"
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user