Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ide/vscode/src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const Version = "9.0.1";
export const Version = "9.0.5";
82 changes: 75 additions & 7 deletions packages/bedrock-diagnoser/src/diagnostics/molang/expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
FunctionCallNode,
MolangData,
MolangFunction,
MolangParameter,
MolangSet,
MolangSyntaxError,
NodeType,
Expand Down Expand Up @@ -223,13 +224,80 @@ export function diagnose_molang_function(fn: FunctionCallNode, diagnoser: Diagno
}

if (fnData.parameters) {
if (fnData.parameters.length != fn.arguments.length) {
diagnoser.add(
OffsetWord.create(`${fn.scope}.${fn.names.join('.')}`, fn.position),
`wrong amount of arguments, expected ${fnData.parameters.length} but got ${fn.arguments.length}`,
DiagnosticSeverity.error,
'molang.function.arguments',
);
// Check if the last parameter is repeatable
const lastParam = fnData.parameters[fnData.parameters.length - 1];
const hasRepeatableParam = lastParam?.repeatable === true;
const minRequiredParams = fnData.parameters.length;

// Validate parameter count
if (hasRepeatableParam) {
// With repeatable parameter, we need at least the minimum parameters
if (fn.arguments.length < minRequiredParams) {
diagnoser.add(
OffsetWord.create(`${fn.scope}.${fn.names.join('.')}`, fn.position),
`wrong amount of arguments, expected at least ${minRequiredParams} but got ${fn.arguments.length}`,
DiagnosticSeverity.error,
'molang.function.arguments',
);
}
} else {
// Without repeatable parameter, we need exact match
if (fnData.parameters.length != fn.arguments.length) {
diagnoser.add(
OffsetWord.create(`${fn.scope}.${fn.names.join('.')}`, fn.position),
`wrong amount of arguments, expected ${fnData.parameters.length} but got ${fn.arguments.length}`,
DiagnosticSeverity.error,
'molang.function.arguments',
);
}
}

// Validate parameter types
for (let i = 0; i < fn.arguments.length; i++) {
const arg = fn.arguments[i];
let expectedParam: MolangParameter | undefined;

// Determine which parameter definition to use
if (i < fnData.parameters.length) {
// Use the parameter at this index
expectedParam = fnData.parameters[i];
} else if (hasRepeatableParam) {
// Use the last (repeatable) parameter for additional arguments
expectedParam = lastParam;
}

// Validate type if specified
if (expectedParam?.type) {
const actualType = getArgumentType(arg);
if (actualType && actualType !== expectedParam.type) {
diagnoser.add(
OffsetWord.create(`${fn.scope}.${fn.names.join('.')}`, fn.position),
`wrong argument type at position ${i + 1}, expected ${expectedParam.type} but got ${actualType}`,
DiagnosticSeverity.error,
'molang.function.arguments.type',
);
}
}
}
}
}

/**
* Helper function to determine the type of an argument node
*/
function getArgumentType(arg: ExpressionNode): 'string' | 'float' | 'boolean' | undefined {
switch (arg.type) {
case NodeType.StringLiteral:
return 'string';
case NodeType.Literal:
// Check if it's a boolean literal (true/false) or numeric
const value = (arg as any).value?.toLowerCase();
if (value === 'true' || value === 'false') {
return 'boolean';
}
return 'float';
default:
// For complex expressions, we can't determine the type statically
return undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ describe("Molang Syntax", () => {
name: "Some complex",
data: "v.temp_outfit!=q.property('foo:bar')+q.property('foo:bar')+q.property('foo:bar')",
},
{
name: "Variable parameters: is_owner_identifier_any with 1 arg",
data: "q.is_owner_identifier_any('minecraft:zombie')",
},
{
name: "Variable parameters: is_owner_identifier_any with 3 args",
data: "q.is_owner_identifier_any('minecraft:zombie', 'minecraft:skeleton', 'minecraft:creeper')",
},
{
name: "Variable parameters: equipped_item_all_tags with 2 args",
data: "q.equipped_item_all_tags('slot.weapon.mainhand', 'tag1')",
},
{
name: "Variable parameters: equipped_item_all_tags with 4 args",
data: "q.equipped_item_all_tags('slot.weapon.mainhand', 'tag1', 'tag2', 'tag3')",
},
{
name: "Variable parameters: is_name_any with multiple args",
data: "q.is_name_any('entity1', 'entity2', 'entity3', 'entity4')",
},
{
name: "Variable parameters: block_has_all_tags with 5 args",
data: "q.block_has_all_tags(0, 0, 0, 'tag1', 'tag2')",
},
];

for (const test of no_errors_tests) {
Expand All @@ -51,4 +75,44 @@ describe("Molang Syntax", () => {
diagnoser.expectEmpty();
});
}

const error_tests: TestCase[] = [
{
name: "Variable parameters: is_owner_identifier_any with 0 args",
data: "q.is_owner_identifier_any()",
},
{
name: "Variable parameters: equipped_item_all_tags with 0 args",
data: "q.equipped_item_all_tags()",
},
{
name: "Variable parameters: equipped_item_all_tags with 1 arg (needs at least 2)",
data: "q.equipped_item_all_tags('slot.weapon.mainhand')",
},
{
name: "Variable parameters: block_has_all_tags with 3 args (needs at least 4)",
data: "q.block_has_all_tags(0, 0, 0)",
},
{
name: "Type validation: block_has_all_tags with wrong type for tag (number instead of string)",
data: "q.block_has_all_tags(0, 0, 0, 2, 1)",
},
{
name: "Type validation: equipped_item_all_tags with wrong type for tag (number instead of string)",
data: "q.equipped_item_all_tags('slot.weapon.mainhand', 123)",
},
{
name: "Type validation: is_name_any with wrong type (number instead of string)",
data: "q.is_name_any(123)",
},
];

for (const test of error_tests) {
it(`error test: ${test.name}`, () => {
const diagnoser = new TestDiagnoser();
diagnose_molang_syntax_text("", diagnoser, test.data);

diagnoser.expectAny();
});
}
});
81 changes: 67 additions & 14 deletions packages/molang/src/data/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,18 +446,23 @@ export namespace General {
{
id: 'x',
documentation: 'World-origin-relative position on the x axis',
type: 'float',
},
{
id: 'y',
documentation: 'World-origin-relative position on the y axis',
type: 'float',
},
{
id: 'z',
documentation: 'World-origin-relative position on the z axis',
type: 'float',
},
{
id: 'tag',
documentation: 'The first tag',
documentation: 'tag name to check',
type: 'string',
repeatable: true,
},
],
},
Expand All @@ -469,18 +474,23 @@ export namespace General {
{
id: 'x',
documentation: 'World-origin-relative position on the x axis',
type: 'float',
},
{
id: 'y',
documentation: 'World-origin-relative position on the y axis',
type: 'float',
},
{
id: 'z',
documentation: 'World-origin-relative position on the z axis',
type: 'float',
},
{
id: 'tag',
documentation: 'The first tag',
documentation: 'tag name to check',
type: 'string',
repeatable: true,
},
],
},
Expand All @@ -492,18 +502,23 @@ export namespace General {
{
id: 'x',
documentation: 'block-relative position on the x axis',
type: 'float',
},
{
id: 'y',
documentation: 'block-relative position on the y axis',
type: 'float',
},
{
id: 'z',
documentation: 'block-relative position on the z axis',
type: 'float',
},
{
id: 'tag',
documentation: 'The first tag',
documentation: 'tag name to check',
type: 'string',
repeatable: true,
},
],
},
Expand All @@ -515,18 +530,23 @@ export namespace General {
{
id: 'x',
documentation: 'block-relative position on the x axis',
type: 'float',
},
{
id: 'y',
documentation: 'block-relative position on the y axis',
type: 'float',
},
{
id: 'z',
documentation: 'block-relative position on the z axis',
type: 'float',
},
{
id: 'tag',
documentation: 'The first tag',
documentation: 'tag name to check',
type: 'string',
repeatable: true,
},
],
},
Expand Down Expand Up @@ -731,11 +751,19 @@ export namespace General {
id: 'equipped_item_all_tags',
documentation:
"takes a slot name followed by any tag you want to check for in the form of 'tag_name' and returns 1 if all of the tags are on that equipped item, 0 otherwise",
parameters: [
{ id: 'slot_name', documentation: 'equipment slot name', type: 'string' },
{ id: 'tag', documentation: 'tag name to check', type: 'string', repeatable: true },
],
},
{
id: 'equipped_item_any_tag',
documentation:
"takes a slot name followed by any tag you want to check for in the form of 'tag_name' and returns 0 if none of the tags are on that equipped item or 1 if at least 1 tag exists",
parameters: [
{ id: 'slot_name', documentation: 'equipment slot name', type: 'string' },
{ id: 'tag', documentation: 'tag name to check', type: 'string', repeatable: true },
],
},
{
id: 'equipped_item_is_attachable',
Expand Down Expand Up @@ -783,6 +811,9 @@ export namespace General {
id: 'graphics_mode_is_any',
documentation:
"Takes in one or more arguments ('simple', 'fancy', 'deferred', 'raytraced'). If the graphics mode of the client matches any of the arguments, return 1.0. Available on the Client (Resource Packs) only.",
parameters: [
{ id: 'mode', documentation: "graphics mode ('simple', 'fancy', 'deferred', 'raytraced')", type: 'string', repeatable: true },
],
},
{
id: 'ground_speed',
Expand Down Expand Up @@ -1122,9 +1153,9 @@ export namespace General {
documentation:
"Takes an equipment slot name (see the replaceitem command) and an optional slot index value. After that, takes one or more full name (with 'namespace:') strings to check for. Returns 1.0 if an item in the specified slot has any of the specified names, otherwise returns 0.0. An empty string '' can be specified to check for an empty slot. Note that querying slot.enderchest, slot.saddle, slot.armor, or slot.chest will only work in behavior packs. A preferred query to query.get_equipped_item_name, as it can be adjusted by Mojang to avoid breaking content if names are changed.",
parameters: [
{ id: 'quipment slot name', documentation: 'quipment slot name' },
{ id: 'slot index', documentation: '' },
{ id: 'item', documentation: '' },
{ id: 'slot_name', documentation: 'equipment slot name', type: 'string' },
{ id: 'slot_index', documentation: 'optional slot index value', type: 'float' },
{ id: 'item', documentation: 'item name to check', type: 'string', repeatable: true },
],
},

Expand Down Expand Up @@ -1170,8 +1201,7 @@ export namespace General {
documentation:
"Takes one or more arguments. If the entity's name is any of the specified string values, returns 1.0. Otherwise returns 0.0. A preferred query to query.get_name, as it can be adjusted by Mojang to avoid breaking content if names are changed.",
parameters: [
{ id: 'name 1', documentation: 'possible entity name' },
{ id: 'name 2', documentation: 'possible entity name' },
{ id: 'name', documentation: 'possible entity name', type: 'string', repeatable: true },
],
},
{
Expand Down Expand Up @@ -1200,8 +1230,7 @@ export namespace General {
documentation:
'Takes one or more arguments. Returns whether the root actor identifier is any of the specified strings. A preferred query to query.owner_identifier, as it can be adjusted by Mojang to avoid breaking content if names are changed.',
parameters: [
{ id: 'name 1', documentation: 'possible entity name' },
{ id: 'name 2', documentation: 'possible entity name' },
{ id: 'name', documentation: 'possible entity name', type: 'string', repeatable: true },
],
},
{
Expand Down Expand Up @@ -1400,6 +1429,9 @@ export namespace General {
id: 'last_input_mode_is_any',
documentation:
"Takes one or more arguments ('keyboard_and_mouse', 'touch', or 'gamepad'). If the last input used is any of the specified string values, returns 1.0. Otherwise returns 0.0. Available on the Client (Resource Packs) only.",
parameters: [
{ id: 'mode', documentation: "input mode ('keyboard_and_mouse', 'touch', or 'gamepad')", type: 'string', repeatable: true },
],
},
{
id: 'lie_amount',
Expand Down Expand Up @@ -1527,11 +1559,23 @@ export namespace General {
id: 'relative_block_has_all_tags',
documentation:
'Takes an entity-relative position and one or more tag names, and returns either 0 or 1 based on if the block at that position has all of the tags provided.',
parameters: [
{ id: 'x', documentation: 'entity-relative position on the x axis', type: 'float' },
{ id: 'y', documentation: 'entity-relative position on the y axis', type: 'float' },
{ id: 'z', documentation: 'entity-relative position on the z axis', type: 'float' },
{ id: 'tag', documentation: 'tag name to check', type: 'string', repeatable: true },
],
},
{
id: 'relative_block_has_any_tag',
documentation:
'Takes an entity-relative position and one or more tag names, and returns either 0 or 1 based on if the block at that position has any of the tags provided.',
parameters: [
{ id: 'x', documentation: 'entity-relative position on the x axis', type: 'float' },
{ id: 'y', documentation: 'entity-relative position on the y axis', type: 'float' },
{ id: 'z', documentation: 'entity-relative position on the z axis', type: 'float' },
{ id: 'tag', documentation: 'tag name to check', type: 'string', repeatable: true },
],
},
{
id: 'ride_body_x_rotation',
Expand Down Expand Up @@ -1750,15 +1794,24 @@ export namespace General {
// Experimental
{
id: 'entity_biome_has_all_tags',
documentation: '(EXPERIMENTAL) Compares the biome the entity is standing in with one or more tag names, and returns either 0 or 1 based on if all of the tag names match. Only supported in resource packs (client-side).'
documentation: '(EXPERIMENTAL) Compares the biome the entity is standing in with one or more tag names, and returns either 0 or 1 based on if all of the tag names match. Only supported in resource packs (client-side).',
parameters: [
{ id: 'tag', documentation: 'biome tag name to check', type: 'string', repeatable: true },
],
},
{
id: 'entity_biome_has_any_identifier',
documentation: '(EXPERIMENTAL) Compares the biome the entity is standing in with one or more identifier names, and returns either 0 or 1 based on if any of the identifier names match. Only supported in resource packs (client-side).'
documentation: '(EXPERIMENTAL) Compares the biome the entity is standing in with one or more identifier names, and returns either 0 or 1 based on if any of the identifier names match. Only supported in resource packs (client-side).',
parameters: [
{ id: 'identifier', documentation: 'biome identifier to check', type: 'string', repeatable: true },
],
},
{
id: 'entity_biome_has_any_tags',
documentation: '(EXPERIMENTAL) Compares the biome the entity is standing in with one or more tag names, and returns either 0 or 1 based on if any of the tag names match. Only supported in resource packs (client-side).'
documentation: '(EXPERIMENTAL) Compares the biome the entity is standing in with one or more tag names, and returns either 0 or 1 based on if any of the tag names match. Only supported in resource packs (client-side).',
parameters: [
{ id: 'tag', documentation: 'biome tag name to check', type: 'string', repeatable: true },
],
},
{
id: 'get_pack_setting',
Expand Down
Loading