import * as core from '@spyglassmc/core' import type { JsonNode } from '@spyglassmc/json' import * as json from '@spyglassmc/json' import { JsonArrayNode, JsonBooleanNode, JsonNumberNode, JsonObjectNode, JsonPairNode, JsonStringNode } from '@spyglassmc/json' import { localeQuote } from '@spyglassmc/locales' import type { ListType, LiteralType, McdocType, NumericType, PrimitiveArrayType, StringType, TupleType, UnionType } from '@spyglassmc/mcdoc' import type { McdocCheckerContext, SimplifiedEnum, SimplifiedMcdocType, SimplifiedMcdocTypeNoUnion, SimplifiedStructType, SimplifiedStructTypePairField, SimplifyValueNode } from '@spyglassmc/mcdoc/lib/runtime/checker/index.js' import { simplify } from '@spyglassmc/mcdoc/lib/runtime/checker/index.js' import { getValues } from '@spyglassmc/mcdoc/lib/runtime/completer/index.js' import { Identifier, ItemStack } from 'deepslate' import { useCallback, useMemo } from 'preact/hooks' import config from '../../Config.js' import { useLocale } from '../../contexts/Locale.jsx' import { useFocus } from '../../hooks/useFocus.js' import { generateColor, hexId, randomInt, randomSeed } from '../../Utils.js' import { ItemDisplay } from '../ItemDisplay.jsx' import { Octicon } from '../Octicon.jsx' const SPECIAL_UNSET = '__unset__' export interface McdocContext extends core.CheckerContext {} type MakeEdit = (edit: (range: core.Range) => JsonNode | undefined) => void interface Props { type: Type optional?: boolean node: JsonNode | undefined makeEdit: MakeEdit ctx: McdocContext } export function McdocRoot({ type, node, makeEdit, ctx } : Props) { const { locale } = useLocale() if (type.kind === 'struct' && type.fields.length > 0 && JsonObjectNode.is(node)) { return } return <>
} function Head({ type, optional, node, makeEdit, ctx }: Props) { if (type.kind === 'string') { return } if (type.kind === 'enum') { return } if (type.kind === 'byte' || type.kind === 'short' || type.kind === 'int' || type.kind === 'long' || type.kind === 'float' || type.kind === 'double') { return } if (type.kind === 'boolean') { return } if (type.kind === 'union') { return } if (type.kind === 'struct') { return } if (type.kind === 'list' || type.kind === 'byte_array' || type.kind === 'int_array' || type.kind === 'long_array') { if (type.lengthRange?.min !== undefined && type.lengthRange.min === type.lengthRange.max) { return getItemType(type)), attributes: type.attributes }} optional={optional} node={node} makeEdit={makeEdit} ctx={ctx} /> } return } if (type.kind === 'tuple') { return } if (type.kind === 'literal') { return } return <> } function StringHead({ type, optional, node, makeEdit, ctx }: Props) { const { locale } = useLocale() const value = JsonStringNode.is(node) ? node.value : undefined const idAttribute = type.attributes?.find(a => a.name === 'id')?.value const idRegistry = idAttribute?.kind === 'literal' && idAttribute.value.kind === 'string' ? idAttribute.value.value : idAttribute?.kind === 'tree' && idAttribute.values.registry?.kind === 'literal' && idAttribute.values.registry?.value.kind === 'string' ? idAttribute.values.registry?.value.value : undefined const isSelect = idRegistry && selectRegistries.has(idRegistry) const onChangeValue = useCallback((newValue: string) => { if (value === newValue) { return } makeEdit((range) => { if ((newValue.length === 0 && optional) || (isSelect && newValue === SPECIAL_UNSET)) { return undefined } return { type: 'json:string', range, options: json.parser.JsonStringOptions, value: newValue, valueMap: [{ inner: core.Range.create(0), outer: core.Range.create(range.start) }], } }) }, [optional, node, makeEdit, isSelect]) const completions = useMemo(() => { return getValues(type, { ...ctx, offset: node?.range.start ?? 0 }) .filter(c => c.kind === 'string' && c.value !== 'THIS') }, [type, node, ctx]) const datalistId = `mcdoc_completions_${hexId()}` const gen = idRegistry ? config.generators.find(gen => gen.id === idRegistry) : undefined const color = type.attributes?.find(a => a.name === 'color')?.value const colorKind = color?.kind === 'literal' && color.value.kind === 'string' ? color.value.value : undefined const onRandomColor = useCallback(() => { const color = generateColor() onChangeValue('#' + (color.toString(16).padStart(6, '0') ?? '000000')) }, [onChangeValue]) return <> {((idRegistry === 'item' || idRegistry === 'block') && value && !value.startsWith('#')) && } {isSelect ? <> : <> {completions.length > 0 && {completions.map(c => )} } onChangeValue((e.target as HTMLInputElement).value)} list={completions.length > 0 ? datalistId : undefined} /> {value && gen && {Octicon.link_external} } } {colorKind === 'hex_rgb' && <> onChangeValue((e.target as HTMLInputElement).value)} /> } } function EnumHead({ type, optional, node, makeEdit }: Props) { const { locale } = useLocale() const value = JsonStringNode.is(node) ? node.value : (node && JsonNumberNode.is(node)) ? Number(node.value.value) : undefined const onChangeValue = useCallback((newValue: string) => { if (value === newValue) { return } makeEdit((range) => { if (newValue === SPECIAL_UNSET) { return undefined } if (type.enumKind === 'string') { return { type: 'json:string', range, options: json.parser.JsonStringOptions, value: newValue, valueMap: [{ inner: core.Range.create(0), outer: core.Range.create(range.start) }], } } const number: core.FloatNode = { type: 'float', range, value: parseFloat(newValue), } const result: JsonNumberNode = { type: 'json:number', range, children: [number], value: number, } number.parent = result return result }) }, [type.enumKind, value, makeEdit]) return } function NumericHead({ type, node, makeEdit }: Props) { const { locale } = useLocale() const value = node && JsonNumberNode.is(node) ? Number(node.value.value) : undefined const isFloat = type.kind === 'float' || type.kind === 'double' const onChangeValue = useCallback((value: string | bigint | number) => { const number = typeof value === 'string' ? (value.length === 0 ? undefined : Number(value)) : value if (number !== undefined && Number.isNaN(number)) { return } makeEdit((range) => { if (number === undefined) { return undefined } const newValue: core.FloatNode | core.LongNode = isFloat ? { type: 'float', range, value: Number(number) } : { type: 'long', range, value: BigInt(number) } const newNode: JsonNumberNode = { type: 'json:number', range, value: newValue, children: [newValue], } newValue.parent = newNode return newNode }) }, [isFloat, node, makeEdit]) const color = type.attributes?.find(a => a.name === 'color')?.value const colorKind = color?.kind === 'literal' && color.value.kind === 'string' ? color.value.value : undefined const onChangeColor = useCallback((value: string) => { onChangeValue(parseInt(value.slice(1), 16).toString()) }, [onChangeValue]) const onRandomColor = useCallback(() => { onChangeValue(generateColor().toString()) }, [onChangeValue]) const random = type.attributes?.find(a => a.name === 'random') const onRandom = useCallback(() => { onChangeValue(type.kind === 'long' ? randomSeed() : randomInt()) }, [type, onChangeValue]) return <> onChangeValue((e.target as HTMLInputElement).value)} /> {colorKind && <> onChangeColor((e.target as HTMLInputElement).value)} /> } {random && <> } } function BooleanHead({ node, makeEdit }: Props) { const value = node && JsonBooleanNode.is(node) ? node.value : undefined const onSelect = useCallback((newValue: boolean) => { makeEdit((range) => { if (value === newValue) { return undefined } return { type: 'json:boolean', range, value: newValue, } }) }, [node, makeEdit, value]) return <> } function UnionHead({ type, optional, node, makeEdit, ctx }: Props>) { const { locale } = useLocale() const selectedType = findSelectedMember(type, node) const onSelect = useCallback((newValue: string) => { makeEdit((range) => { if (newValue === SPECIAL_UNSET) { return undefined } const newSelected = type.members[parseInt(newValue)] return getDefault(newSelected, range, ctx) }) }, [type, makeEdit, ctx]) const memberIndex = selectedType ? type.members.findIndex(m => quickEqualTypes(m, selectedType)) : -1 return <> {selectedType && } } function StructHead({ type: outerType, optional, node, makeEdit, ctx }: Props) { const { locale } = useLocale() const type = node?.typeDef?.kind === 'struct' ? node.typeDef : outerType const onRemove = useCallback(() => { makeEdit(() => { return undefined }) }, [makeEdit]) const onSetDefault = useCallback(() => { makeEdit((range) => { return getDefault(type, range, ctx) }) }, [type, ctx]) return <> {optional ? (JsonObjectNode.is(node) ? : ) : (!JsonObjectNode.is(node) ? : <> )} } function ListHead({ type, node, makeEdit, ctx }: Props) { const { locale } = useLocale() const canAdd = (type.lengthRange?.max ?? Infinity) > (node?.children?.length ?? 0) const onAddTop = useCallback(() => { if (canAdd) { makeEdit((range) => { const itemType = simplifyType(getItemType(type), ctx) const newValue = getDefault(itemType, range, ctx) const newItem: core.ItemNode = { type: 'item', range, children: [newValue], value: newValue, } newValue.parent = newItem if (JsonArrayNode.is(node)) { node.children.unshift(newItem) newItem.parent = node return node } const newArray: JsonArrayNode = { type: 'json:array', range, children: [newItem], } newItem.parent = newArray return newArray }) } }, [type, node, makeEdit, ctx, canAdd]) return } function TupleHead({ type, optional, node, makeEdit, ctx }: Props) { const { locale } = useLocale() const onRemove = useCallback(() => { makeEdit(() => { return undefined }) }, [makeEdit]) const onSetDefault = useCallback(() => { makeEdit((range) => { return getDefault(type, range, ctx) }) }, [type, ctx]) if (optional) { if (node && JsonArrayNode.is(node)) { return } else { return } } else { if (!node || !JsonArrayNode.is(node)) { return } return <> } } function LiteralHead({ type }: Props) { return } function Body({ type, optional, node, makeEdit, ctx }: Props) { if (type.kind === 'union') { return } if (type.kind === 'struct') { if (!JsonObjectNode.is(node) || type.fields.length === 0) { return <> } return
} if (type.kind === 'list' || type.kind === 'byte_array' || type.kind === 'int_array' || type.kind === 'long_array') { if (!JsonArrayNode.is(node)) { return <> } if (type.lengthRange?.min !== undefined && type.lengthRange.min === type.lengthRange.max) { return
getItemType(type)), attributes: type.attributes }} node={node} makeEdit={makeEdit} ctx={ctx} />
} if (node.children?.length === 0) { return <> } return
} if (type.kind === 'tuple') { return
} return <> } function UnionBody({ type, optional, node, makeEdit, ctx }: Props>) { const selectedType = findSelectedMember(type, node) if (selectedType === undefined) { return <> } return } interface StructBodyProps extends Props { type: SimplifiedStructType } function StructBody({ type: outerType, node, makeEdit, ctx }: StructBodyProps) { const { locale } = useLocale() if (!JsonObjectNode.is(node)) { return <> } const type = node.typeDef?.kind === 'struct' ? node.typeDef : outerType const staticFields = type.fields.filter(field => field.key.kind === 'literal') const dynamicFields = type.fields.filter(field => field.key.kind !== 'literal') const staticChilds: core.PairNode[] = [] return <> {staticFields.map(field => { const key = (field.key as LiteralType).value.value.toString() const index = node.children.findIndex(p => p.key?.value === key) const pair = index === -1 ? undefined : node.children[index] if (pair) { staticChilds.push(pair) } const child = pair?.value const childType = simplifyType(field.type, ctx) const makeFieldEdit: MakeEdit = (edit) => { if (pair) { makeEdit(() => { const newChild = edit(child?.range ?? core.Range.create(pair.range.end)) if (newChild === undefined) { node.children.splice(index, 1) } else { node.children[index] = { type: 'pair', range: pair.range, key: pair.key, value: newChild, } } return node }) } else { const newChild = edit(core.Range.create(node.range.end)) if (newChild) { makeEdit(() => { node.children.push({ type: 'pair', range: newChild.range, key: { type: 'json:string', range: newChild.range, options: json.parser.JsonStringOptions, value: key, valueMap: [{ inner: core.Range.create(0), outer: newChild.range }], }, value: newChild, }) return node }) } } } return
{!field.optional && child === undefined && }
})} {dynamicFields.map((field, index) => { if (field.key.kind === 'any' && field.type.kind === 'any') { return <> } const incompletePairs = node.children.filter(pair => pair.value && core.Range.length(pair.value.range) === 0) const pair = index < incompletePairs.length ? incompletePairs[index] : undefined const keyType = simplifyType(field.key, ctx) const makeKeyEdit: MakeEdit = (edit) => { makeEdit(() => { const newKey = edit(pair?.key?.range ?? core.Range.create(node.range.end)) const index = pair ? node.children.indexOf(pair) : -1 if (!newKey || !JsonStringNode.is(newKey) || newKey.value.length === 0) { if (index !== -1) { node.children.splice(index, 1) } return node } const newPair: core.PairNode = { type: 'pair', range: newKey?.range, key: newKey, } newKey.parent = newPair if (index !== -1) { node.children.splice(index, 1, newPair) } else { node.children.push(newPair) } newPair.parent = node return node }) } const onAddKey = () => { const keyNode = pair?.key const index = pair ? node.children.indexOf(pair) : -1 if (!pair || !keyNode || index === -1) { return } makeEdit((range) => { const valueNode = getDefault(simplifyType(field.type, ctx), range, ctx) const newPair: core.PairNode = { type: 'pair', range: keyNode.range, key: keyNode, value: valueNode, } valueNode.parent = newPair node.children.splice(index, 1, newPair) newPair.parent = node return node }) } return
})} {node.children.map((pair, index) => { const key = pair.key?.value if (staticChilds.includes(pair) || !key) { return <> } if (pair.value && core.Range.length(pair.value.range) === 0) { return <> } const child = pair.value // TODO: correctly determine which dynamic field this is a key for const field = dynamicFields[0] as SimplifiedStructTypePairField | undefined if (!field) { return <> } const childType = simplifyType(field.type, ctx, child) const makeFieldEdit: MakeEdit = (edit) => { makeEdit(() => { const newChild = edit(child?.range ?? core.Range.create(pair.range.end)) if (newChild === undefined) { node.children.splice(index, 1) } else { node.children[index] = { type: 'pair', range: pair.range, key: pair.key, value: newChild, } } return node }) } return
})} } interface KeyProps { label: string | number | boolean raw?: boolean } function Key({ label, raw }: KeyProps) { return } function ListBody({ type: outerType, node, makeEdit, ctx }: Props) { const { locale } = useLocale() if (!JsonArrayNode.is(node)) { return <> } const type = (node.typeDef?.kind === 'list' || node.typeDef?.kind === 'byte_array' || node.typeDef?.kind === 'int_array' || node.typeDef?.kind === 'long_array') ? node.typeDef : outerType const canAdd = (type.lengthRange?.max ?? Infinity) > (node?.children?.length ?? 0) const onRemoveItem = useCallback((index: number) => { makeEdit(() => { node.children.splice(index, 1) return node }) }, [makeEdit, node]) const onMoveUp = useCallback((index: number) => { if (node.children.length <= 1 || index <= 0) { return } makeEdit(() => { const moved = node.children.splice(index, 1) node.children.splice(index - 1, 0, ...moved) return node }) }, [makeEdit]) const onMoveDown = useCallback((index: number) => { if (node.children.length <= 1 || index >= node.children.length - 1) { return } makeEdit(() => { const moved = node.children.splice(index, 1) node.children.splice(index + 1, 0, ...moved) return node }) }, [makeEdit]) const onAddBottom = useCallback(() => { if (canAdd) { makeEdit((range) => { const itemType = simplifyType(getItemType(type), ctx) const newValue = getDefault(itemType, range, ctx) const newItem: core.ItemNode = { type: 'item', range, children: [newValue], value: newValue, } newValue.parent = newItem if (JsonArrayNode.is(node)) { node.children.push(newItem) newItem.parent = node return node } const newArray: JsonArrayNode = { type: 'json:array', range, children: [newItem], } newItem.parent = newArray return newArray }) } }, [type, node, makeEdit, ctx, canAdd]) return <> {node.children.map((item, index) => { const child = item.value const itemType = getItemType(type) const childType = simplifyType(itemType, ctx) const makeItemEdit: MakeEdit = (edit) => { makeEdit(() => { const newChild = edit(child?.range ?? item.range) node.children[index] = { type: 'item', range: item.range, value: newChild, } return node }) } const canMoveUp = node.children.length > 1 && index > 0 const canMoveDown = node.children.length > 1 && index < (node.children.length - 1) return
{(canMoveUp || canMoveDown) &&
}
{childType.kind === 'struct' ?
: }
})} {node.children.length > 0 &&
} } function TupleBody({ type, node, makeEdit, ctx }: Props) { if (!JsonArrayNode.is(node)) { return <> } return <> {type.items.map((itemType, index) => { const item = node?.children?.[index] const child = item?.value const childType = simplifyType(itemType, ctx) const makeItemEdit: MakeEdit = (edit) => { makeEdit(() => { const newChild = edit(child?.range ?? node.range) if (newChild === undefined) { return node } node.children[index] = { type: 'item', range: newChild.range, value: newChild, } return node }) } return
})} } interface ErrorsProps { type: SimplifiedMcdocType node: JsonNode | undefined ctx: McdocContext } function Errors({ type, node, ctx }: ErrorsProps) { const errors = useMemo(() => { if (node === undefined) { return [] } const errors = ctx.err.errors // Get all errors inside the current node .filter(e => core.Range.containsRange(node.range, e.range, true)) // Unless they are inside a child node .filter(e => !node.children?.some(c => (c.type === 'item' || c.type === 'pair') && core.Range.containsRange(c.range, e.range, true))) // Filter out "Missing key" errors .filter(e => !(core.Range.length(e.range) === 1 && (type.kind === 'struct' || (type.kind === 'union' && (findSelectedMember(type, node) ?? type.members[0]).kind === 'struct')))) // Hide warnings if there are errors return errors.find(e => e.severity === 3) ? errors.filter(e => e.severity === 3) : errors }, [type, node, ctx]) return <> {errors.map(e => )} } interface ErrorIndicatorProps { error: core.LanguageError } function ErrorIndicator({ error }: ErrorIndicatorProps) { const [active, setActive] = useFocus() return
setActive()}> {Octicon.issue_opened} {error.message.replace(/ \(rule: [a-zA-Z]+\)$/, '')}
} interface DocsProps { desc: string | undefined } function Docs({ desc }: DocsProps) { if (!desc || desc.length === 0) { return <> } const [active, setActive] = useFocus() return
setActive()}> {Octicon.info} {desc}
} function getDefault(type: SimplifiedMcdocType, range: core.Range, ctx: McdocContext): JsonNode { if (type.kind === 'string') { return JsonStringNode.mock(range) } if (type.kind === 'boolean') { return { type: 'json:boolean', range, value: false } } if (type.kind === 'byte' || type.kind === 'short' || type.kind === 'int' || type.kind === 'long' || type.kind === 'float' || type.kind === 'double') { const value: core.LongNode = { type: 'long', range, value: BigInt(0) } return { type: 'json:number', range, value, children: [value] } } if (type.kind === 'struct' || type.kind === 'any' || type.kind === 'unsafe') { const object = JsonObjectNode.mock(range) if (type.kind === 'struct') { for (const field of type.fields) { if (field.kind === 'pair' && !field.optional && (typeof field.key === 'string' || field.key.kind === 'literal')) { const key: JsonStringNode = { type: 'json:string', range, options: json.parser.JsonStringOptions, value: typeof field.key === 'string' ? field.key : field.key.value.value.toString(), valueMap: [{ inner: core.Range.create(0), outer: core.Range.create(range.start) }], } const value = getDefault(simplifyType(field.type, ctx), range, ctx) const pair: core.PairNode = { type: 'pair', range, key: key, value: value, children: [key, value], } key.parent = pair value.parent = pair object.children.push(pair) pair.parent = object } } } return object } if (type.kind === 'list' || type.kind === 'byte_array' || type.kind === 'int_array' || type.kind === 'long_array') { const array = JsonArrayNode.mock(range) const minLength = type.lengthRange?.min ?? 0 if (minLength > 0) { for (let i = 0; i < minLength; i += 1) { const child = getDefault(simplifyType(getItemType(type), ctx), range, ctx) const itemNode: core.ItemNode = { type: 'item', range, children: [child], value: child, } child.parent = itemNode array.children.push(itemNode) itemNode.parent = array } } return array } if (type.kind === 'tuple') { return { type: 'json:array', range, children: type.items.map(item => { const valueNode = getDefault(simplifyType(item, ctx), range, ctx) const itemNode: core.ItemNode = { type: 'item', range, children: [valueNode], value: valueNode, } valueNode.parent = itemNode return itemNode }), } } if (type.kind === 'union') { return getDefault(type.members[0], range, ctx) } if (type.kind === 'enum') { return getDefault({ kind: 'literal', value: { kind: type.enumKind ?? 'string', value: type.values[0].value } as any }, range, ctx) } if (type.kind === 'literal') { if (type.value.kind === 'string') { return { type: 'json:string', range, options: json.parser.JsonStringOptions, value: type.value.value, valueMap: [{ inner: core.Range.create(0), outer: core.Range.create(range.start) }] } } if (type.value.kind === 'boolean') { return { type: 'json:boolean', range, value: type.value.value } } const value: core.FloatNode | core.LongNode = type.value.kind === 'float' || type.value.kind === 'double' ? { type: 'float', range, value: type.value.value } : { type: 'long', range, value: BigInt(type.value.value) } return { type: 'json:number', range, value, children: [value] } } return { type: 'json:null', range } } function formatIdentifier(id: string): string { if (id.startsWith('!')) { return '! ' + formatIdentifier(id.substring(1)) } const text = id.replace(/^minecraft:/, '').replaceAll('_', ' ') return text.charAt(0).toUpperCase() + text.substring(1) } function getCategory(type: McdocType) { if (type.kind === 'reference' && type.path) { switch (type.path) { case '::java::data::loot::LootPool': case '::java::data::worldgen::dimension::Dimension': case '::java::data::worldgen::surface_rule::SurfaceRule': case '::java::data::worldgen::template_pool::WeightedElement': return 'pool' case '::java::data::loot::LootCondition': case '::java::data::advancement::AdvancementCriterion': case '::java::data::worldgen::dimension::biome_source::BiomeSource': case '::java::data::worldgen::processor_list::ProcessorRule': case '::java::data::worldgen::feature::placement::PlacementModifier': return 'predicate' case '::java::data::loot::LootFunction': case '::java::data::worldgen::density_function::CubicSpline': case '::java::data::worldgen::processor_list::Processor': return 'function' } } return undefined } const selectRegistries = new Set([ 'block_predicate_type', 'chunk_status', 'consume_effect_type', 'creative_mode_tab', 'data_component_type', 'enchantment_effect_component_type', 'enchantment_entity_effect_type', 'enchantment_level_based_value_type', 'enchantment_location_based_effect_type', 'enchantment_provider_type', 'enchantment_value_effect_type', 'entity_sub_predicate_type', 'float_provider_type', 'frog_variant', 'height_provider_type', 'int_provider_type', 'item_sub_predicate_type', 'loot_condition_type', 'loot_function_type', 'loot_nbt_provider_type', 'loot_number_provider_type', 'loot_pool_entry_type', 'loot_score_provider_type', 'map_decoration_type', 'number_format_type', 'pos_rule_test', 'position_source_type', 'recipe_book_category', 'recipe_display', 'recipe_serializer', 'recipe_type', 'rule_block_entity_modifier', 'rule_test', 'slot_display', 'stat_type', 'trigger_type', 'worldgen/biome_source', 'worldgen/block_state_provider_type', 'worldgen/carver', 'worldgen/chunk_generator', 'worldgen/density_function_type', 'worldgen/feature', 'worldgen/feature_size_type', 'worldgen/foliage_placer_type', 'worldgen/material_condition', 'worldgen/material_rule', 'worldgen/placement_modifier_type', 'worldgen/pool_alias_binding', 'worldgen/root_placer_type', 'worldgen/structure_placement', 'worldgen/structure_pool_element', 'worldgen/structure_processor', 'worldgen/structure_type', 'worldgen/tree_decorator_type', 'worldgen/trunk_placer_type', ]) export function simplifyType(type: McdocType, ctx: McdocContext, node?: JsonNode): SimplifiedMcdocType { const key = node?.parent && JsonPairNode.is(node.parent) && JsonStringNode.is(node.parent.key) ? node.parent.key : undefined const simplifyNode: SimplifyValueNode = { entryNode: { parent: undefined, runtimeKey: key ? { originalNode: key, inferredType: { kind: 'literal', value: { kind: 'string', value: key.value } }, } : undefined, }, node: { originalNode: null, inferredType: { kind: 'any' }, }, } const context: McdocCheckerContext = { ...ctx, allowMissingKeys: false, requireCanonical: false, isEquivalent: () => false, getChildren: () => [], reportError: () => {}, attachTypeInfo: () => {}, nodeAttacher: () => {}, stringAttacher: () => {}, } const result = simplify(type, { node: simplifyNode, ctx: context }) return result.typeDef } function getItemType(type: ListType | PrimitiveArrayType): McdocType { return type.kind === 'list' ? type.item : type.kind === 'byte_array' ? { kind: 'byte' } : type.kind === 'int_array' ? { kind: 'int' } : type.kind === 'long_array' ? { kind: 'long' } : { kind: 'any' } } function quickEqualTypes(a: SimplifiedMcdocType, b: SimplifiedMcdocType) { if (a.kind !== b.kind) { return false } // TODO: improve this return true } function findSelectedMember(_union: UnionType, node: JsonNode | undefined) { const selectedType = node?.typeDef if (!selectedType) { return undefined } if (selectedType.kind === 'union') { // The node technically matches all members of this union, // ideally the editor should show a combination of all members return selectedType.members[0] } return selectedType }