From 89e4cf3ee68d5131e00a0f91c8ed6867f79f1328 Mon Sep 17 00:00:00 2001 From: danvervlad Date: Mon, 19 Jan 2026 12:43:02 +0200 Subject: [PATCH 1/7] add IsDeprecated flag for action, conditions, expressions --- Core/GDCore/Project/EventsFunction.cpp | 4 ++++ Core/GDCore/Project/EventsFunction.h | 14 +++++++++++ .../MetadataDeclarationHelper.cpp | 9 ++++++++ GDevelop.js/Bindings/Bindings.idl | 2 ++ GDevelop.js/types.d.ts | 2 ++ GDevelop.js/types/gdeventsfunction.js | 2 ++ .../EventsFunctionPropertiesEditor.js | 23 +++++++++++++++++++ .../EventsFunctionsExtensionEditor/index.js | 3 ++- .../src/EventsSheet/EventsTree/Instruction.js | 3 ++- 9 files changed, 60 insertions(+), 2 deletions(-) diff --git a/Core/GDCore/Project/EventsFunction.cpp b/Core/GDCore/Project/EventsFunction.cpp index 7c941232e3b1..fac3029d356e 100644 --- a/Core/GDCore/Project/EventsFunction.cpp +++ b/Core/GDCore/Project/EventsFunction.cpp @@ -75,6 +75,9 @@ void EventsFunction::SerializeTo(SerializerElement& element) const { if (isAsync) { element.SetBoolAttribute("async", isAsync); } + if (isDeprecated) { + element.SetBoolAttribute("deprecated", isDeprecated); + } events.SerializeTo(element.AddChild("events")); gd::String functionTypeStr = "Action"; @@ -116,6 +119,7 @@ void EventsFunction::UnserializeFrom(gd::Project& project, getterName = element.GetStringAttribute("getterName"); isPrivate = element.GetBoolAttribute("private"); isAsync = element.GetBoolAttribute("async"); + isDeprecated = element.GetBoolAttribute("deprecated"); events.UnserializeFrom(project, element.GetChild("events")); gd::String functionTypeStr = element.GetStringAttribute("functionType"); diff --git a/Core/GDCore/Project/EventsFunction.h b/Core/GDCore/Project/EventsFunction.h index 886b7401759c..bc24a5777a38 100644 --- a/Core/GDCore/Project/EventsFunction.h +++ b/Core/GDCore/Project/EventsFunction.h @@ -223,6 +223,19 @@ class GD_CORE_API EventsFunction { return *this; } + /** + * \brief Returns true if the function is deprecated. + */ + bool IsDeprecated() const { return isDeprecated; } + + /** + * \brief Sets whether the function is deprecated. + */ + EventsFunction& SetDeprecated(bool _isDeprecated) { + isDeprecated = _isDeprecated; + return *this; + } + /** * \brief Return the events. */ @@ -304,6 +317,7 @@ class GD_CORE_API EventsFunction { gd::ObjectGroupsContainer objectGroups; bool isPrivate = false; bool isAsync = false; + bool isDeprecated = false; }; } // namespace gd diff --git a/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp b/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp index 253ccb551fa1..fa978427a276 100644 --- a/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp @@ -1556,6 +1556,9 @@ MetadataDeclarationHelper::GenerateFreeFunctionMetadata( if (eventsFunction.IsPrivate()) instructionOrExpression.SetPrivate(); + if (eventsFunction.IsDeprecated()) + instructionOrExpression.SetHidden(); + return instructionOrExpression; }; @@ -1593,6 +1596,9 @@ gd::BehaviorMetadata &MetadataDeclarationHelper::GenerateBehaviorMetadata( if (eventsFunction.IsPrivate()) instructionOrExpression.SetPrivate(); + + if (eventsFunction.IsDeprecated()) + instructionOrExpression.SetHidden(); } return behaviorMetadata; @@ -1632,6 +1638,9 @@ gd::ObjectMetadata &MetadataDeclarationHelper::GenerateObjectMetadata( if (eventsFunction.IsPrivate()) instructionOrExpression.SetPrivate(); + + if (eventsFunction.IsDeprecated()) + instructionOrExpression.SetHidden(); } UpdateCustomObjectDefaultBehaviors(project, objectMetadata); diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 6efbb506b654..20861b108f97 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -3187,6 +3187,8 @@ interface EventsFunction { boolean IsPrivate(); [Ref] EventsFunction SetAsync(boolean isAsync); boolean IsAsync(); + [Ref] EventsFunction SetDeprecated(boolean isDeprecated); + boolean IsDeprecated(); boolean IsAction(); boolean IsExpression(); boolean IsCondition(); diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index 1760003455e9..12bf955a11b1 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -2291,6 +2291,8 @@ export class EventsFunction extends EmscriptenObject { isPrivate(): boolean; setAsync(isAsync: boolean): EventsFunction; isAsync(): boolean; + setDeprecated(isDeprecated: boolean): EventsFunction; + isDeprecated(): boolean; isAction(): boolean; isExpression(): boolean; isCondition(): boolean; diff --git a/GDevelop.js/types/gdeventsfunction.js b/GDevelop.js/types/gdeventsfunction.js index a6a92436c40e..b29b91f6b90c 100644 --- a/GDevelop.js/types/gdeventsfunction.js +++ b/GDevelop.js/types/gdeventsfunction.js @@ -25,6 +25,8 @@ declare class gdEventsFunction { isPrivate(): boolean; setAsync(isAsync: boolean): gdEventsFunction; isAsync(): boolean; + setDeprecated(isDeprecated: boolean): gdEventsFunction; + isDeprecated(): boolean; isAction(): boolean; isExpression(): boolean; isCondition(): boolean; diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js index 3ebc98b31ba8..7af8ce6c8653 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js @@ -552,6 +552,29 @@ export const EventsFunctionPropertiesEditor = ({ forceUpdate(); }} /> + Deprecated} + checked={eventsFunction.isDeprecated()} + onCheck={(e, checked) => { + eventsFunction.setDeprecated(checked); + if (onConfigurationUpdated) + onConfigurationUpdated('isDeprecated'); + forceUpdate(); + }} + tooltipOrHelperText={ + eventsFunction.isDeprecated() ? ( + + This function is marked as deprecated. It will be + displayed with a warning in the events editor. + + ) : ( + + Mark this function as deprecated to discourage its + use. + + ) + } + /> {eventsFunction.isAsync() && ( { [selectedArea]: props.selected, [warningInstruction]: showDeprecatedInstructionWarning && - !isInstructionVisible(scope, metadata), + (!isInstructionVisible(scope, metadata) || + metadata.isHidden()), })} onClick={e => { e.stopPropagation(); From 2123bdb4916d33094454318ba3c763de6a4db64f Mon Sep 17 00:00:00 2001 From: danvervlad Date: Tue, 20 Jan 2026 17:11:59 +0200 Subject: [PATCH 2/7] add DEPRECATED prefix for conditions and actions --- .../MetadataDeclarationHelper.cpp | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp b/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp index fa978427a276..a4a1d188ed2f 100644 --- a/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp @@ -462,6 +462,14 @@ gd::String MetadataDeclarationHelper::GetFullName(const gd::EventsFunction &even return GetTranslation(eventsFunction.GetFullName()) || eventsFunction.GetName(); }; +gd::String GetFullNameWithDeprecatedPrefix(const gd::EventsFunction &eventsFunction) { + gd::String fullName = GetTranslation(eventsFunction.GetFullName()) || eventsFunction.GetName(); + if (eventsFunction.IsDeprecated()) { + fullName = "[DEPRECATED] " + fullName; + } + return fullName; +}; + gd::String MetadataDeclarationHelper::GetDefaultSentence( const gd::EventsFunction &eventsFunction, const int firstParameterIndex, const int parameterIndexOffset) { @@ -486,27 +494,39 @@ gd::String MetadataDeclarationHelper::GetDefaultSentence( }; gd::String MetadataDeclarationHelper::GetFreeFunctionSentence(const gd::EventsFunction &eventsFunction) { - return GetTranslation(eventsFunction.GetSentence()).empty() + gd::String sentence = GetTranslation(eventsFunction.GetSentence()).empty() ? GetDefaultSentence(eventsFunction, 0, 1) : GetTranslation(eventsFunction.GetSentence()); + if (eventsFunction.IsDeprecated()) { + sentence = "[DEPRECATED] " + sentence; + } + return sentence; }; gd::String MetadataDeclarationHelper::GetBehaviorFunctionSentence( const gd::EventsFunction &eventsFunction, const bool excludeObjectParameter) { - return GetTranslation(eventsFunction.GetSentence()).empty() + gd::String sentence = GetTranslation(eventsFunction.GetSentence()).empty() ? GetDefaultSentence(eventsFunction, excludeObjectParameter ? 2 : 0, 0) : GetTranslation(eventsFunction.GetSentence()); + if (eventsFunction.IsDeprecated()) { + sentence = "[DEPRECATED] " + sentence; + } + return sentence; }; gd::String MetadataDeclarationHelper::GetObjectFunctionSentence( const gd::EventsFunction &eventsFunction, const bool excludeObjectParameter) { - return GetTranslation(eventsFunction.GetSentence()).empty() + gd::String sentence = GetTranslation(eventsFunction.GetSentence()).empty() ? GetDefaultSentence(eventsFunction, excludeObjectParameter ? 1 : 0, 0) : GetTranslation(eventsFunction.GetSentence()); + if (eventsFunction.IsDeprecated()) { + sentence = "[DEPRECATED] " + sentence; + } + return sentence; }; /** @@ -542,13 +562,13 @@ MetadataDeclarationHelper::DeclareExpressionMetadata( eventsFunction.GetExpressionType().IsNumber() ? extension.AddExpression( eventsFunction.GetName(), - GetFullName(eventsFunction), + GetFullNameWithDeprecatedPrefix(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup(), GetExtensionIconUrl(extension)) : extension.AddStrExpression( eventsFunction.GetName(), - GetFullName(eventsFunction), + GetFullNameWithDeprecatedPrefix(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup(), GetExtensionIconUrl(extension)); @@ -733,7 +753,7 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata( (eventsFunction.GetExpressionType().IsNumber()) ? behaviorMetadata.AddExpression( eventsFunction.GetName(), - GetFullName(eventsFunction), + GetFullNameWithDeprecatedPrefix(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup() || @@ -742,7 +762,7 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata( GetExtensionIconUrl(extension)) : behaviorMetadata.AddStrExpression( eventsFunction.GetName(), - GetFullName(eventsFunction), + GetFullNameWithDeprecatedPrefix(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup() || @@ -907,7 +927,7 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata( (eventsFunction.GetExpressionType().IsNumber()) ? objectMetadata.AddExpression( eventsFunction.GetName(), - GetFullName(eventsFunction), + GetFullNameWithDeprecatedPrefix(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup() || @@ -916,7 +936,7 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata( GetExtensionIconUrl(extension)) : objectMetadata.AddStrExpression( eventsFunction.GetName(), - GetFullName(eventsFunction), + GetFullNameWithDeprecatedPrefix(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup() || From ea9361fb5fd2bcbf2b75b95386f238808502b002 Mon Sep 17 00:00:00 2001 From: danvervlad Date: Tue, 20 Jan 2026 17:44:31 +0200 Subject: [PATCH 3/7] add deprecation message, display in tooltip for actions and conditions --- .../Metadata/AbstractFunctionMetadata.h | 7 +++++++ .../Extensions/Metadata/ExpressionMetadata.h | 16 ++++++++++++++++ .../Extensions/Metadata/InstructionMetadata.h | 16 ++++++++++++++++ .../Metadata/MultipleInstructionMetadata.h | 12 ++++++++++++ Core/GDCore/Project/EventsFunction.cpp | 4 ++++ Core/GDCore/Project/EventsFunction.h | 16 ++++++++++++++++ .../MetadataDeclarationHelper.cpp | 15 ++++++++++++--- GDevelop.js/Bindings/Bindings.idl | 3 +++ GDevelop.js/types.d.ts | 3 +++ GDevelop.js/types/gdeventsfunction.js | 2 ++ GDevelop.js/types/gdinstructionmetadata.js | 1 + .../EventsFunctionPropertiesEditor.js | 17 +++++++++++++++++ .../src/EventsSheet/EventsTree/Instruction.js | 12 +++++++++++- 13 files changed, 120 insertions(+), 4 deletions(-) diff --git a/Core/GDCore/Extensions/Metadata/AbstractFunctionMetadata.h b/Core/GDCore/Extensions/Metadata/AbstractFunctionMetadata.h index 71be1bda2346..fc9b3059ac1c 100644 --- a/Core/GDCore/Extensions/Metadata/AbstractFunctionMetadata.h +++ b/Core/GDCore/Extensions/Metadata/AbstractFunctionMetadata.h @@ -76,6 +76,13 @@ class GD_CORE_API AbstractFunctionMetadata { */ virtual AbstractFunctionMetadata &SetHidden() = 0; + /** + * \brief Set the deprecation message that explains why the function + * is deprecated and what to use instead. + */ + virtual AbstractFunctionMetadata & + SetDeprecationMessage(const gd::String &message) = 0; + /** * Set that the instruction is private - it can't be used outside of the * object/ behavior that it is attached too. diff --git a/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h b/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h index 00bd3d4a46ad..bb6442c285a0 100644 --- a/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h +++ b/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h @@ -72,6 +72,21 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata { */ ExpressionMetadata& SetHidden() override; + /** + * \brief Set the deprecation message that explains why the expression + * is deprecated and what to use instead. + */ + ExpressionMetadata& SetDeprecationMessage(const gd::String& message) override { + deprecationMessage = message; + return *this; + } + + /** + * \brief Get the deprecation message that explains why the expression + * is deprecated and what to use instead. + */ + const gd::String& GetDeprecationMessage() const { return deprecationMessage; } + /** * \brief Set the group of the instruction in the IDE. */ @@ -369,6 +384,7 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata { bool isPrivate; gd::String requiredBaseObjectCapability; gd::String relevantContext; + gd::String deprecationMessage; gd::ParameterMetadataContainer parameters; }; diff --git a/Core/GDCore/Extensions/Metadata/InstructionMetadata.h b/Core/GDCore/Extensions/Metadata/InstructionMetadata.h index f88af522fdc6..c52b358a7197 100644 --- a/Core/GDCore/Extensions/Metadata/InstructionMetadata.h +++ b/Core/GDCore/Extensions/Metadata/InstructionMetadata.h @@ -200,6 +200,21 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata { return *this; } + /** + * \brief Set the deprecation message that explains why the instruction + * is deprecated and what to use instead. + */ + InstructionMetadata &SetDeprecationMessage(const gd::String &message) override { + deprecationMessage = message; + return *this; + } + + /** + * \brief Get the deprecation message that explains why the instruction + * is deprecated and what to use instead. + */ + const gd::String &GetDeprecationMessage() const { return deprecationMessage; } + /** * \brief Set the group of the instruction in the IDE. */ @@ -586,6 +601,7 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata { bool isBehaviorInstruction; gd::String requiredBaseObjectCapability; gd::String relevantContext; + gd::String deprecationMessage; }; } // namespace gd diff --git a/Core/GDCore/Extensions/Metadata/MultipleInstructionMetadata.h b/Core/GDCore/Extensions/Metadata/MultipleInstructionMetadata.h index d1a13423eced..d96707bb0836 100644 --- a/Core/GDCore/Extensions/Metadata/MultipleInstructionMetadata.h +++ b/Core/GDCore/Extensions/Metadata/MultipleInstructionMetadata.h @@ -113,6 +113,18 @@ class GD_CORE_API MultipleInstructionMetadata : public AbstractFunctionMetadata return *this; }; + /** + * \brief Set the deprecation message that explains why the instruction + * is deprecated and what to use instead. + */ + MultipleInstructionMetadata &SetDeprecationMessage( + const gd::String &message) override { + if (expression) expression->SetDeprecationMessage(message); + if (condition) condition->SetDeprecationMessage(message); + if (action) action->SetDeprecationMessage(message); + return *this; + } + /** * \see gd::InstructionMetadata::SetRequiresBaseObjectCapability */ diff --git a/Core/GDCore/Project/EventsFunction.cpp b/Core/GDCore/Project/EventsFunction.cpp index fac3029d356e..b4742f82aae2 100644 --- a/Core/GDCore/Project/EventsFunction.cpp +++ b/Core/GDCore/Project/EventsFunction.cpp @@ -78,6 +78,9 @@ void EventsFunction::SerializeTo(SerializerElement& element) const { if (isDeprecated) { element.SetBoolAttribute("deprecated", isDeprecated); } + if (!deprecationMessage.empty()) { + element.SetAttribute("deprecationMessage", deprecationMessage); + } events.SerializeTo(element.AddChild("events")); gd::String functionTypeStr = "Action"; @@ -120,6 +123,7 @@ void EventsFunction::UnserializeFrom(gd::Project& project, isPrivate = element.GetBoolAttribute("private"); isAsync = element.GetBoolAttribute("async"); isDeprecated = element.GetBoolAttribute("deprecated"); + deprecationMessage = element.GetStringAttribute("deprecationMessage"); events.UnserializeFrom(project, element.GetChild("events")); gd::String functionTypeStr = element.GetStringAttribute("functionType"); diff --git a/Core/GDCore/Project/EventsFunction.h b/Core/GDCore/Project/EventsFunction.h index bc24a5777a38..f5f5e1fbba8b 100644 --- a/Core/GDCore/Project/EventsFunction.h +++ b/Core/GDCore/Project/EventsFunction.h @@ -236,6 +236,21 @@ class GD_CORE_API EventsFunction { return *this; } + /** + * \brief Get the deprecation message that explains why the function is + * deprecated and what to use instead. + */ + const gd::String& GetDeprecationMessage() const { return deprecationMessage; } + + /** + * \brief Set the deprecation message that explains why the function is + * deprecated and what to use instead. + */ + EventsFunction& SetDeprecationMessage(const gd::String& message) { + deprecationMessage = message; + return *this; + } + /** * \brief Return the events. */ @@ -318,6 +333,7 @@ class GD_CORE_API EventsFunction { bool isPrivate = false; bool isAsync = false; bool isDeprecated = false; + gd::String deprecationMessage; }; } // namespace gd diff --git a/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp b/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp index a4a1d188ed2f..89aa5803c77c 100644 --- a/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp @@ -1576,8 +1576,11 @@ MetadataDeclarationHelper::GenerateFreeFunctionMetadata( if (eventsFunction.IsPrivate()) instructionOrExpression.SetPrivate(); - if (eventsFunction.IsDeprecated()) + if (eventsFunction.IsDeprecated()) { instructionOrExpression.SetHidden(); + instructionOrExpression.SetDeprecationMessage( + eventsFunction.GetDeprecationMessage()); + } return instructionOrExpression; }; @@ -1617,8 +1620,11 @@ gd::BehaviorMetadata &MetadataDeclarationHelper::GenerateBehaviorMetadata( if (eventsFunction.IsPrivate()) instructionOrExpression.SetPrivate(); - if (eventsFunction.IsDeprecated()) + if (eventsFunction.IsDeprecated()) { instructionOrExpression.SetHidden(); + instructionOrExpression.SetDeprecationMessage( + eventsFunction.GetDeprecationMessage()); + } } return behaviorMetadata; @@ -1659,8 +1665,11 @@ gd::ObjectMetadata &MetadataDeclarationHelper::GenerateObjectMetadata( if (eventsFunction.IsPrivate()) instructionOrExpression.SetPrivate(); - if (eventsFunction.IsDeprecated()) + if (eventsFunction.IsDeprecated()) { instructionOrExpression.SetHidden(); + instructionOrExpression.SetDeprecationMessage( + eventsFunction.GetDeprecationMessage()); + } } UpdateCustomObjectDefaultBehaviors(project, objectMetadata); diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 20861b108f97..eae4014cb9d3 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -1726,6 +1726,7 @@ interface InstructionMetadata { [Const, Ref] ParameterMetadataContainer GetParameters(); long GetUsageComplexity(); boolean IsHidden(); + [Const, Ref] DOMString GetDeprecationMessage(); boolean IsPrivate(); boolean IsAsync(); boolean IsOptionallyAsync(); @@ -3189,6 +3190,8 @@ interface EventsFunction { boolean IsAsync(); [Ref] EventsFunction SetDeprecated(boolean isDeprecated); boolean IsDeprecated(); + [Ref] EventsFunction SetDeprecationMessage([Const] DOMString message); + [Const, Ref] DOMString GetDeprecationMessage(); boolean IsAction(); boolean IsExpression(); boolean IsCondition(); diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index 12bf955a11b1..abeca5cc808c 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -1427,6 +1427,7 @@ export class InstructionMetadata extends AbstractFunctionMetadata { getParameters(): ParameterMetadataContainer; getUsageComplexity(): number; isHidden(): boolean; + getDeprecationMessage(): string; isPrivate(): boolean; isAsync(): boolean; isOptionallyAsync(): boolean; @@ -2293,6 +2294,8 @@ export class EventsFunction extends EmscriptenObject { isAsync(): boolean; setDeprecated(isDeprecated: boolean): EventsFunction; isDeprecated(): boolean; + setDeprecationMessage(message: string): EventsFunction; + getDeprecationMessage(): string; isAction(): boolean; isExpression(): boolean; isCondition(): boolean; diff --git a/GDevelop.js/types/gdeventsfunction.js b/GDevelop.js/types/gdeventsfunction.js index b29b91f6b90c..b59eae40f80d 100644 --- a/GDevelop.js/types/gdeventsfunction.js +++ b/GDevelop.js/types/gdeventsfunction.js @@ -27,6 +27,8 @@ declare class gdEventsFunction { isAsync(): boolean; setDeprecated(isDeprecated: boolean): gdEventsFunction; isDeprecated(): boolean; + setDeprecationMessage(message: string): gdEventsFunction; + getDeprecationMessage(): string; isAction(): boolean; isExpression(): boolean; isCondition(): boolean; diff --git a/GDevelop.js/types/gdinstructionmetadata.js b/GDevelop.js/types/gdinstructionmetadata.js index c02e43bbe49f..037022c9583e 100644 --- a/GDevelop.js/types/gdinstructionmetadata.js +++ b/GDevelop.js/types/gdinstructionmetadata.js @@ -14,6 +14,7 @@ declare class gdInstructionMetadata extends gdAbstractFunctionMetadata { getParameters(): gdParameterMetadataContainer; getUsageComplexity(): number; isHidden(): boolean; + getDeprecationMessage(): string; isPrivate(): boolean; isAsync(): boolean; isOptionallyAsync(): boolean; diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js index 7af8ce6c8653..17869211f9c5 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionPropertiesEditor.js @@ -575,6 +575,23 @@ export const EventsFunctionPropertiesEditor = ({ ) } /> + {eventsFunction.isDeprecated() && ( + Deprecation message + } + translatableHintText={t`Example: Use "New Action Name" instead.`} + fullWidth + multiline + value={eventsFunction.getDeprecationMessage()} + onChange={text => { + eventsFunction.setDeprecationMessage(text); + if (onConfigurationUpdated) onConfigurationUpdated(); + forceUpdate(); + }} + /> + )} {eventsFunction.isAsync() && ( { {showDeprecatedInstructionWarning && metadata.isHidden() ? ( + {props.isCondition ? ( + Deprecated condition + ) : ( + Deprecated action + )} + {': '} + {metadata.getDeprecationMessage()} + + ) : props.isCondition ? ( Deprecated condition ) : ( Deprecated action From d950360c2cf86e516da39ff8e20b679bfa70c936 Mon Sep 17 00:00:00 2001 From: danvervlad Date: Tue, 20 Jan 2026 17:55:43 +0200 Subject: [PATCH 4/7] add warn lable for deprecated actions and conditions in detailed view --- .../InstructionParametersEditor.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/newIDE/app/src/EventsSheet/InstructionEditor/InstructionParametersEditor.js b/newIDE/app/src/EventsSheet/InstructionEditor/InstructionParametersEditor.js index 1ab2d124b092..86c6370d08ed 100644 --- a/newIDE/app/src/EventsSheet/InstructionEditor/InstructionParametersEditor.js +++ b/newIDE/app/src/EventsSheet/InstructionEditor/InstructionParametersEditor.js @@ -311,6 +311,20 @@ const InstructionParametersEditor = React.forwardRef< + {instructionMetadata.isHidden() && ( + + + {instructionMetadata.getDeprecationMessage() ? ( + <> + Deprecated:{' '} + {instructionMetadata.getDeprecationMessage()} + + ) : ( + Deprecated + )} + + + )} {instructionExtraInformation && ( {instructionExtraInformation.identifier === undefined ? ( From 252bfc97997c93773f717dcc85d5f57d02db8ab1 Mon Sep 17 00:00:00 2001 From: danvervlad Date: Tue, 20 Jan 2026 19:00:29 +0200 Subject: [PATCH 5/7] add highlights and display deprecation message for expressions --- .../Events/Parsers/ExpressionParser2Node.h | 3 +- .../Extensions/Metadata/ExpressionMetadata.h | 5 ++ .../GDCore/IDE/Events/ExpressionValidator.cpp | 10 ++++ Core/GDCore/IDE/InstructionValidator.cpp | 41 +++++++++++++++- Core/GDCore/IDE/InstructionValidator.h | 10 ++++ GDevelop.js/Bindings/Bindings.idl | 28 +++++++++++ GDevelop.js/Bindings/Wrapper.cpp | 3 ++ GDevelop.js/scripts/generate-types.js | 28 +++++++++++ GDevelop.js/types.d.ts | 22 +++++++++ .../types/expressionparsererror_errortype.js | 2 + GDevelop.js/types/gdexpressionmetadata.js | 2 + GDevelop.js/types/gdexpressionparsererror.js | 16 +++++++ GDevelop.js/types/gdinstructionvalidator.js | 1 + GDevelop.js/types/libgdevelop.js | 1 + .../src/EventsSheet/EventsTree/ClassNames.js | 1 + .../EventsTree/DeprecatedParameterValue.js | 14 ++++++ .../src/EventsSheet/EventsTree/Instruction.js | 13 +++++ .../ParameterFields/DefaultField.js | 5 ++ .../BackgroundHighlighting.js | 16 +++++-- .../GenericExpressionField/index.js | 48 +++++++++++++++---- .../ParameterInlineRenderer.flow.js | 6 +++ .../ParameterFields/VariableField.js | 13 +++-- .../app/src/UI/Theme/Global/EventsSheet.css | 6 +++ 23 files changed, 276 insertions(+), 18 deletions(-) create mode 100644 GDevelop.js/types/expressionparsererror_errortype.js create mode 100644 newIDE/app/src/EventsSheet/EventsTree/DeprecatedParameterValue.js diff --git a/Core/GDCore/Events/Parsers/ExpressionParser2Node.h b/Core/GDCore/Events/Parsers/ExpressionParser2Node.h index 402ea59b145d..4f7f36d43c19 100644 --- a/Core/GDCore/Events/Parsers/ExpressionParser2Node.h +++ b/Core/GDCore/Events/Parsers/ExpressionParser2Node.h @@ -59,6 +59,7 @@ struct GD_CORE_API ExpressionParserError { UnknownParameterType, MissingBehavior, VariableNameCollision, + DeprecatedExpression, }; ExpressionParserError(gd::ExpressionParserError::ErrorType type_, @@ -78,7 +79,7 @@ struct GD_CORE_API ExpressionParserError { location(startPosition_, endPosition_){}; virtual ~ExpressionParserError(){}; - gd::ExpressionParserError::ErrorType GetType() { return type; } + gd::ExpressionParserError::ErrorType GetType() const { return type; } const gd::String &GetMessage() { return message; } const gd::String &GetObjectName() { return objectName; } const gd::String &GetActualValue() { return actualValue; } diff --git a/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h b/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h index bb6442c285a0..bde656dd768f 100644 --- a/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h +++ b/Core/GDCore/Extensions/Metadata/ExpressionMetadata.h @@ -87,6 +87,11 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata { */ const gd::String& GetDeprecationMessage() const { return deprecationMessage; } + /** + * \brief Check if the expression is deprecated. + */ + bool IsDeprecated() const { return !deprecationMessage.empty(); } + /** * \brief Set the group of the instruction in the IDE. */ diff --git a/Core/GDCore/IDE/Events/ExpressionValidator.cpp b/Core/GDCore/IDE/Events/ExpressionValidator.cpp index da259aec477f..c86a3f7c2a4a 100644 --- a/Core/GDCore/IDE/Events/ExpressionValidator.cpp +++ b/Core/GDCore/IDE/Events/ExpressionValidator.cpp @@ -295,6 +295,16 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction( return returnType; } + // Check if the expression is deprecated + if (metadata.IsDeprecated()) { + gd::String deprecationMessage = metadata.GetDeprecationMessage(); + RaiseError(gd::ExpressionParserError::ErrorType::DeprecatedExpression, + _("This expression is deprecated.") + + (deprecationMessage.empty() ? "" : " " + deprecationMessage), + function.location, + /*isFatal=*/false); + } + // Validate the type of the function if (returnType == Type::Number) { if (parentType == Type::String) { diff --git a/Core/GDCore/IDE/InstructionValidator.cpp b/Core/GDCore/IDE/InstructionValidator.cpp index d32a731e2290..71f20db2d318 100644 --- a/Core/GDCore/IDE/InstructionValidator.cpp +++ b/Core/GDCore/IDE/InstructionValidator.cpp @@ -5,6 +5,7 @@ */ #include "InstructionValidator.h" +#include "GDCore/Events/Parsers/ExpressionParser2Node.h" #include "GDCore/Extensions/Metadata/AbstractFunctionMetadata.h" #include "GDCore/Extensions/Metadata/BehaviorMetadata.h" #include "GDCore/Extensions/Metadata/InstructionMetadata.h" @@ -52,7 +53,9 @@ bool InstructionValidator::IsParameterValid( parameterType, parameterMetadata.GetExtraInfo()); expressionNode.Visit(expressionValidator); - if (!expressionValidator.GetAllErrors().empty()) { + // Use GetFatalErrors() instead of GetAllErrors() to allow non-fatal + // warnings (like deprecation) to pass validation. + if (!expressionValidator.GetFatalErrors().empty()) { return false; } // New object variable instructions require the variable to be @@ -107,6 +110,42 @@ gd::String InstructionValidator::GetRootVariableName(const gd::String &name) { : squareBracketPosition); }; +bool InstructionValidator::HasDeprecationWarnings( + const gd::Platform &platform, + const gd::ProjectScopedContainers projectScopedContainers, + const gd::Instruction &instruction, const InstructionMetadata &metadata, + std::size_t parameterIndex, const gd::String &value) { + if (parameterIndex >= instruction.GetParametersCount() || + parameterIndex >= metadata.GetParametersCount()) { + return false; + } + const auto ¶meterMetadata = metadata.GetParameter(parameterIndex); + const auto ¶meterType = parameterMetadata.GetType() == "expression" + ? "number" + : parameterMetadata.GetType(); + + if (gd::ParameterMetadata::IsExpression("number", parameterType) || + gd::ParameterMetadata::IsExpression("string", parameterType) || + gd::ParameterMetadata::IsExpression("variable", parameterType)) { + auto &expressionNode = + *instruction.GetParameter(parameterIndex).GetRootNode(); + ExpressionValidator expressionValidator(platform, projectScopedContainers, + parameterType, + parameterMetadata.GetExtraInfo()); + expressionNode.Visit(expressionValidator); + + // Check if there are any deprecation warnings + const auto &allErrors = expressionValidator.GetAllErrors(); + for (const auto *error : allErrors) { + if (error->GetType() == + gd::ExpressionParserError::ErrorType::DeprecatedExpression) { + return true; + } + } + } + return false; +} + bool InstructionValidator::HasRequiredBehaviors( const gd::Instruction &instruction, const gd::InstructionMetadata &instructionMetadata, diff --git a/Core/GDCore/IDE/InstructionValidator.h b/Core/GDCore/IDE/InstructionValidator.h index 5978e68aefb5..da76bf262788 100644 --- a/Core/GDCore/IDE/InstructionValidator.h +++ b/Core/GDCore/IDE/InstructionValidator.h @@ -27,6 +27,16 @@ class GD_CORE_API InstructionValidator { const InstructionMetadata &metadata, std::size_t parameterIndex, const gd::String &value); + /** + * \brief Check if a parameter expression has deprecation warnings. + */ + static bool + HasDeprecationWarnings(const gd::Platform &platform, + const gd::ProjectScopedContainers projectScopedContainers, + const gd::Instruction &instruction, + const InstructionMetadata &metadata, + std::size_t parameterIndex, const gd::String &value); + static gd::String GetRootVariableName(const gd::String &name); private: diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index eae4014cb9d3..a795ef00f540 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -1793,6 +1793,8 @@ interface ExpressionMetadata { [Const, Ref] DOMString GetHelpPath(); boolean IsShown(); boolean IsPrivate(); + boolean IsDeprecated(); + [Const, Ref] DOMString GetDeprecationMessage(); boolean IsRelevantForLayoutEvents(); boolean IsRelevantForFunctionEvents(); boolean IsRelevantForAsynchronousFunctionEvents(); @@ -2931,6 +2933,13 @@ interface InstructionValidator { [Const, Ref] InstructionMetadata metadata, long parameterIndex, [Const] DOMString value); + boolean STATIC_HasDeprecationWarnings( + [Const, Ref] Platform platform, + [Const, Ref] ProjectScopedContainers projectScopedContainers, + [Const, Ref] Instruction instruction, + [Const, Ref] InstructionMetadata metadata, + long parameterIndex, + [Const] DOMString value); }; interface ObjectTools { @@ -3066,7 +3075,26 @@ interface WholeProjectDiagnosticReport { boolean HasAnyIssue(); }; +enum ExpressionParserError_ErrorType { + "ExpressionParserError::SyntaxError", + "ExpressionParserError::InvalidOperator", + "ExpressionParserError::MismatchedType", + "ExpressionParserError::UndeclaredVariable", + "ExpressionParserError::UnknownIdentifier", + "ExpressionParserError::BracketsNotAllowedForObjects", + "ExpressionParserError::TooFewParameters", + "ExpressionParserError::TooManyParameters", + "ExpressionParserError::InvalidFunctionName", + "ExpressionParserError::MalformedVariableParameter", + "ExpressionParserError::MalformedObjectParameter", + "ExpressionParserError::UnknownParameterType", + "ExpressionParserError::MissingBehavior", + "ExpressionParserError::VariableNameCollision", + "ExpressionParserError::DeprecatedExpression" +}; + interface ExpressionParserError { + ExpressionParserError_ErrorType GetType(); [Const, Ref] DOMString GetMessage(); unsigned long GetStartPosition(); unsigned long GetEndPosition(); diff --git a/GDevelop.js/Bindings/Wrapper.cpp b/GDevelop.js/Bindings/Wrapper.cpp index c5f29ed42c10..bc2492cbb466 100644 --- a/GDevelop.js/Bindings/Wrapper.cpp +++ b/GDevelop.js/Bindings/Wrapper.cpp @@ -449,6 +449,8 @@ typedef std::unique_ptr UniquePtrBehavior; typedef std::unique_ptr UniquePtrExpressionNode; typedef std::vector VectorExpressionParserError; +typedef gd::ExpressionParserError::ErrorType + ExpressionParserError_ErrorType; typedef gd::SerializableWithNameList EventsBasedBehaviorsList; typedef gd::SerializableWithNameList @@ -678,6 +680,7 @@ typedef std::vector VectorPropertyDescriptorChoice #define STATIC_IsBehaviorCompatibleWithObject IsBehaviorCompatibleWithObject #define STATIC_FillBehaviorParameters FillBehaviorParameters #define STATIC_IsParameterValid IsParameterValid +#define STATIC_HasDeprecationWarnings HasDeprecationWarnings #define STATIC_FixInvalidRequiredBehaviorProperties \ FixInvalidRequiredBehaviorProperties #define STATIC_RemoveLayerInScene RemoveLayerInScene diff --git a/GDevelop.js/scripts/generate-types.js b/GDevelop.js/scripts/generate-types.js index 715b5fbb5549..36963723ed99 100644 --- a/GDevelop.js/scripts/generate-types.js +++ b/GDevelop.js/scripts/generate-types.js @@ -229,6 +229,34 @@ type ExpressionCompletionDescription_CompletionKind = 0 | 1 | 2 | 3 | 4 | 5 | 6` ].join('\n'), 'types/gdexpressioncompletiondescription.js' ); + fs.writeFileSync( + 'types/expressionparsererror_errortype.js', + `// Automatically generated by GDevelop.js/scripts/generate-types.js +type ExpressionParserError_ErrorType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14` + ); + shell.sed( + '-i', + 'declare class gdExpressionParserError {', + [ + 'declare class gdExpressionParserError {', + ' static SyntaxError: 0;', + ' static InvalidOperator: 1;', + ' static MismatchedType: 2;', + ' static UndeclaredVariable: 3;', + ' static UnknownIdentifier: 4;', + ' static BracketsNotAllowedForObjects: 5;', + ' static TooFewParameters: 6;', + ' static TooManyParameters: 7;', + ' static InvalidFunctionName: 8;', + ' static MalformedVariableParameter: 9;', + ' static MalformedObjectParameter: 10;', + ' static UnknownParameterType: 11;', + ' static MissingBehavior: 12;', + ' static VariableNameCollision: 13;', + ' static DeprecatedExpression: 14;', + ].join('\n'), + 'types/gdexpressionparsererror.js' + ); fs.writeFileSync( 'types/particleemitterobject_renderertype.js', `// Automatically generated by GDevelop.js/scripts/generate-types.js diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index abeca5cc808c..b19713139db8 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -84,6 +84,24 @@ export enum ProjectDiagnostic_ErrorType { MismatchedObjectType = 3, } +export enum ExpressionParserError_ErrorType { + SyntaxError = 0, + InvalidOperator = 1, + MismatchedType = 2, + UndeclaredVariable = 3, + UnknownIdentifier = 4, + BracketsNotAllowedForObjects = 5, + TooFewParameters = 6, + TooManyParameters = 7, + InvalidFunctionName = 8, + MalformedVariableParameter = 9, + MalformedObjectParameter = 10, + UnknownParameterType = 11, + MissingBehavior = 12, + VariableNameCollision = 13, + DeprecatedExpression = 14, +} + export enum ExpressionCompletionDescription_CompletionKind { Object = 0, BehaviorWithPrefix = 1, @@ -1476,6 +1494,8 @@ export class ExpressionMetadata extends AbstractFunctionMetadata { getHelpPath(): string; isShown(): boolean; isPrivate(): boolean; + isDeprecated(): boolean; + getDeprecationMessage(): string; isRelevantForLayoutEvents(): boolean; isRelevantForFunctionEvents(): boolean; isRelevantForAsynchronousFunctionEvents(): boolean; @@ -2087,6 +2107,7 @@ export class BehaviorParameterFiller extends EmscriptenObject { export class InstructionValidator extends EmscriptenObject { static isParameterValid(platform: Platform, projectScopedContainers: ProjectScopedContainers, instruction: Instruction, metadata: InstructionMetadata, parameterIndex: number, value: string): boolean; + static hasDeprecationWarnings(platform: Platform, projectScopedContainers: ProjectScopedContainers, instruction: Instruction, metadata: InstructionMetadata, parameterIndex: number, value: string): boolean; } export class ObjectTools extends EmscriptenObject { @@ -2203,6 +2224,7 @@ export class WholeProjectDiagnosticReport extends EmscriptenObject { } export class ExpressionParserError extends EmscriptenObject { + getType(): ExpressionParserError_ErrorType; getMessage(): string; getStartPosition(): number; getEndPosition(): number; diff --git a/GDevelop.js/types/expressionparsererror_errortype.js b/GDevelop.js/types/expressionparsererror_errortype.js new file mode 100644 index 000000000000..9f2a9c08f1df --- /dev/null +++ b/GDevelop.js/types/expressionparsererror_errortype.js @@ -0,0 +1,2 @@ +// Automatically generated by GDevelop.js/scripts/generate-types.js +type ExpressionParserError_ErrorType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 \ No newline at end of file diff --git a/GDevelop.js/types/gdexpressionmetadata.js b/GDevelop.js/types/gdexpressionmetadata.js index 74bb1fa18193..6494288baa56 100644 --- a/GDevelop.js/types/gdexpressionmetadata.js +++ b/GDevelop.js/types/gdexpressionmetadata.js @@ -9,6 +9,8 @@ declare class gdExpressionMetadata extends gdAbstractFunctionMetadata { getHelpPath(): string; isShown(): boolean; isPrivate(): boolean; + isDeprecated(): boolean; + getDeprecationMessage(): string; isRelevantForLayoutEvents(): boolean; isRelevantForFunctionEvents(): boolean; isRelevantForAsynchronousFunctionEvents(): boolean; diff --git a/GDevelop.js/types/gdexpressionparsererror.js b/GDevelop.js/types/gdexpressionparsererror.js index 42887dbf83e9..d35b6acdfe97 100644 --- a/GDevelop.js/types/gdexpressionparsererror.js +++ b/GDevelop.js/types/gdexpressionparsererror.js @@ -1,5 +1,21 @@ // Automatically generated by GDevelop.js/scripts/generate-types.js declare class gdExpressionParserError { + static SyntaxError: 0; + static InvalidOperator: 1; + static MismatchedType: 2; + static UndeclaredVariable: 3; + static UnknownIdentifier: 4; + static BracketsNotAllowedForObjects: 5; + static TooFewParameters: 6; + static TooManyParameters: 7; + static InvalidFunctionName: 8; + static MalformedVariableParameter: 9; + static MalformedObjectParameter: 10; + static UnknownParameterType: 11; + static MissingBehavior: 12; + static VariableNameCollision: 13; + static DeprecatedExpression: 14; + getType(): ExpressionParserError_ErrorType; getMessage(): string; getStartPosition(): number; getEndPosition(): number; diff --git a/GDevelop.js/types/gdinstructionvalidator.js b/GDevelop.js/types/gdinstructionvalidator.js index e7042f447527..9bed2ee481f1 100644 --- a/GDevelop.js/types/gdinstructionvalidator.js +++ b/GDevelop.js/types/gdinstructionvalidator.js @@ -1,6 +1,7 @@ // Automatically generated by GDevelop.js/scripts/generate-types.js declare class gdInstructionValidator { static isParameterValid(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, instruction: gdInstruction, metadata: gdInstructionMetadata, parameterIndex: number, value: string): boolean; + static hasDeprecationWarnings(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, instruction: gdInstruction, metadata: gdInstructionMetadata, parameterIndex: number, value: string): boolean; delete(): void; ptr: number; }; \ No newline at end of file diff --git a/GDevelop.js/types/libgdevelop.js b/GDevelop.js/types/libgdevelop.js index f21211acb534..7d5581549e07 100644 --- a/GDevelop.js/types/libgdevelop.js +++ b/GDevelop.js/types/libgdevelop.js @@ -218,6 +218,7 @@ declare class libGDevelop { ProjectDiagnostic: Class; DiagnosticReport: Class; WholeProjectDiagnosticReport: Class; + ExpressionParserError_ErrorType: Class; ExpressionParserError: Class; VectorExpressionParserError: Class; ExpressionParser2NodeWorker: Class; diff --git a/newIDE/app/src/EventsSheet/EventsTree/ClassNames.js b/newIDE/app/src/EventsSheet/EventsTree/ClassNames.js index f88b713a538a..edde7d2de10d 100644 --- a/newIDE/app/src/EventsSheet/EventsTree/ClassNames.js +++ b/newIDE/app/src/EventsSheet/EventsTree/ClassNames.js @@ -17,6 +17,7 @@ export const instructionParameter = 'instruction-parameter'; export const instructionInvalidParameter = 'instruction-invalid-parameter'; export const instructionMissingParameter = 'instruction-missing-parameter'; export const instructionWarningParameter = 'instruction-warning-parameter'; +export const instructionDeprecatedParameter = 'instruction-deprecated-parameter'; export const disabledText = 'disabled-text'; export const background = 'background'; diff --git a/newIDE/app/src/EventsSheet/EventsTree/DeprecatedParameterValue.js b/newIDE/app/src/EventsSheet/EventsTree/DeprecatedParameterValue.js new file mode 100644 index 000000000000..d9977f108c63 --- /dev/null +++ b/newIDE/app/src/EventsSheet/EventsTree/DeprecatedParameterValue.js @@ -0,0 +1,14 @@ +// @flow +import * as React from 'react'; +import { instructionDeprecatedParameter } from './ClassNames'; + +type Props = {| children: React.Node |}; + +/** + * Displayed when a parameter uses a deprecated expression + */ +const DeprecatedParameterValue = ({ children }: Props) => ( + {children} +); + +export default DeprecatedParameterValue; diff --git a/newIDE/app/src/EventsSheet/EventsTree/Instruction.js b/newIDE/app/src/EventsSheet/EventsTree/Instruction.js index 8ca934cbef10..e4dcd3c41c9b 100644 --- a/newIDE/app/src/EventsSheet/EventsTree/Instruction.js +++ b/newIDE/app/src/EventsSheet/EventsTree/Instruction.js @@ -22,6 +22,7 @@ import InstructionsList from './InstructionsList'; import DropIndicator from './DropIndicator'; import ParameterRenderingService from '../ParameterRenderingService'; import InvalidParameterValue from './InvalidParameterValue'; +import DeprecatedParameterValue from './DeprecatedParameterValue'; import MissingParameterValue from './MissingParameterValue'; import { makeDragSourceAndDropTarget } from '../../UI/DragAndDrop/DragSourceAndDropTarget'; import { @@ -290,6 +291,7 @@ const Instruction = (props: Props) => { ? 'number' : parameterMetadata.getType(); let expressionIsValid = true; + let hasDeprecationWarning = false; if (!shouldNotBeValidated({ value, parameterType })) { expressionIsValid = gd.InstructionValidator.isParameterValid( platform, @@ -299,6 +301,15 @@ const Instruction = (props: Props) => { parameterIndex, value ); + // Check for deprecation warnings + hasDeprecationWarning = gd.InstructionValidator.hasDeprecationWarnings( + platform, + projectScopedContainers, + instruction, + metadata, + parameterIndex, + value + ); // TODO Move this code inside `InstructionValidator.isParameterValid` if ( expressionIsValid && @@ -379,9 +390,11 @@ const Instruction = (props: Props) => { scope, value: formattedValue, expressionIsValid, + hasDeprecationWarning, parameterMetadata, renderObjectThumbnail, InvalidParameterValue, + DeprecatedParameterValue, MissingParameterValue, useAssignmentOperators, projectScopedContainersAccessor: diff --git a/newIDE/app/src/EventsSheet/ParameterFields/DefaultField.js b/newIDE/app/src/EventsSheet/ParameterFields/DefaultField.js index 598aff891eed..e554e65d6874 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/DefaultField.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/DefaultField.js @@ -45,8 +45,10 @@ export default React.forwardRef( export const renderInlineDefaultField = ({ value, expressionIsValid, + hasDeprecationWarning, parameterMetadata, InvalidParameterValue, + DeprecatedParameterValue, MissingParameterValue, }: ParameterInlineRendererProps) => { if (!value && !parameterMetadata.isOptional()) { @@ -55,5 +57,8 @@ export const renderInlineDefaultField = ({ if (!expressionIsValid) { return {value}; } + if (hasDeprecationWarning) { + return {value}; + } return value; }; diff --git a/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/BackgroundHighlighting.js b/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/BackgroundHighlighting.js index 778b9854164c..b9e7677b34db 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/BackgroundHighlighting.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/BackgroundHighlighting.js @@ -4,7 +4,7 @@ import * as React from 'react'; export type Highlight = {| begin: number, end: number, - type?: 'error', + type?: 'error' | 'deprecated', message: string, |}; @@ -22,12 +22,18 @@ const defaultStyle = { color: 'transparent', }; -const highlightedText = { +const highlightedErrorText = { backgroundColor: 'rgba(244, 67, 54, 0.15)', borderSizing: 'border-box', borderBottom: '3px solid rgba(244, 67, 54, 0.7)', }; +const highlightedDeprecatedText = { + backgroundColor: 'rgba(255, 152, 0, 0.15)', + borderSizing: 'border-box', + borderBottom: '3px solid rgba(255, 152, 0, 0.7)', +}; + const BackgroundHighlighting = ({ value, style, highlights }: Props) => { const sortedHighlights = highlights .slice() @@ -47,8 +53,12 @@ const BackgroundHighlighting = ({ value, style, highlights }: Props) => { } if (lastPos < highlight.end) { + const highlightStyle = + highlight.type === 'deprecated' + ? highlightedDeprecatedText + : highlightedErrorText; elements.push( - + {value.substring(startPos, highlight.end)} ); diff --git a/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js b/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js index 73a8d482715f..179428648130 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js @@ -92,6 +92,9 @@ const styles = { paddingLeft: 0, paddingRight: 0, }, + warningText: { + color: '#ff9800', // Orange color for warnings + }, }; type State = {| @@ -101,6 +104,7 @@ type State = {| validatedValue: string, errorText: ?string, errorHighlights: Array, + isOnlyWarning: boolean, autocompletions: AutocompletionsState, |}; @@ -134,6 +138,7 @@ const extractErrors = ( ): {| errorText: ?string, errorHighlights: Array, + isOnlyWarning: boolean, |} => { const expressionValidator = new gd.ExpressionValidator( gd.JsPlatform.get(), @@ -143,13 +148,20 @@ const extractErrors = ( ); expressionNode.visit(expressionValidator); const errors = expressionValidator.getAllErrors(); - - const errorHighlights: Array = mapVector(errors, error => ({ - begin: error.getStartPosition(), - end: error.getEndPosition() + 1, - message: error.getMessage(), - type: 'error', - })); + const fatalErrors = expressionValidator.getFatalErrors(); + const hasFatalErrors = fatalErrors.size() > 0; + + const errorHighlights: Array = mapVector(errors, error => { + const errorType = error.getType(); + const isDeprecated = + errorType === gd.ExpressionParserError.DeprecatedExpression; + return { + begin: error.getStartPosition(), + end: error.getEndPosition() + 1, + message: error.getMessage(), + type: isDeprecated ? 'deprecated' : 'error', + }; + }); const otherErrorsCount = Math.max( 0, errorHighlights.length - MAX_ERRORS_COUNT @@ -168,9 +180,12 @@ const extractErrors = ( ) .join(' '); + // If there are warnings but no fatal errors, it's only a warning + const isOnlyWarning = errors.size() > 0 && !hasFatalErrors; + expressionValidator.delete(); - return { errorText, errorHighlights }; + return { errorText, errorHighlights, isOnlyWarning }; }; export default class ExpressionField extends React.Component { @@ -186,6 +201,7 @@ export default class ExpressionField extends React.Component { validatedValue: this.props.value, errorText: null, errorHighlights: [], + isOnlyWarning: false, autocompletions: getAutocompletionsInitialState(), }; @@ -443,7 +459,7 @@ export default class ExpressionField extends React.Component { const parser = new gd.ExpressionParser2(); const expressionNode = parser.parseExpression(expression).get(); - const { errorText, errorHighlights } = extractErrors( + const { errorText, errorHighlights, isOnlyWarning } = extractErrors( gd.JsPlatform.get(), project, projectScopedContainersAccessor, @@ -467,6 +483,7 @@ export default class ExpressionField extends React.Component { this.setState(state => ({ errorText: formattedErrorText, errorHighlights, + isOnlyWarning, autocompletions: getAutocompletionsInitialState(), })); return; @@ -504,6 +521,7 @@ export default class ExpressionField extends React.Component { this.setState(state => ({ errorText: formattedErrorText, errorHighlights, + isOnlyWarning, autocompletions: setNewAutocompletions( state.autocompletions, allNewAutocompletions @@ -583,7 +601,17 @@ export default class ExpressionField extends React.Component { onBlur={this._handleBlurEvent} ref={field => (this._field = field)} onFocus={this._handleFocus} - errorText={this.state.errorText} + errorText={ + this.state.errorText ? ( + this.state.isOnlyWarning ? ( + + {this.state.errorText} + + ) : ( + this.state.errorText + ) + ) : null + } onClick={() => this._enqueueValidation()} onKeyDown={event => { const autocompletions = handleAutocompletionsKeyDown( diff --git a/newIDE/app/src/EventsSheet/ParameterFields/ParameterInlineRenderer.flow.js b/newIDE/app/src/EventsSheet/ParameterFields/ParameterInlineRenderer.flow.js index f8fed0474328..8ac45bcfb217 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/ParameterInlineRenderer.flow.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/ParameterInlineRenderer.flow.js @@ -8,6 +8,10 @@ export type InvalidParameterValueProps = {| isEmpty?: boolean, |}; +export type DeprecatedParameterValueProps = {| + children: React.Node, +|}; + /** * The props expected by a function that renders a parameter in the events sheet */ @@ -17,8 +21,10 @@ export type ParameterInlineRendererProps = {| parameterMetadata: gdParameterMetadata, value: string, expressionIsValid: boolean, + hasDeprecationWarning: boolean, renderObjectThumbnail: string => React.Node, InvalidParameterValue: InvalidParameterValueProps => React.Node, + DeprecatedParameterValue: DeprecatedParameterValueProps => React.Node, MissingParameterValue: () => React.Node, useAssignmentOperators: boolean, |}; diff --git a/newIDE/app/src/EventsSheet/ParameterFields/VariableField.js b/newIDE/app/src/EventsSheet/ParameterFields/VariableField.js index fa583e443bd8..4c4ce80bd1a9 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/VariableField.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/VariableField.js @@ -509,7 +509,9 @@ export const renderVariableWithIcon = ( value, parameterMetadata, expressionIsValid, + hasDeprecationWarning, InvalidParameterValue, + DeprecatedParameterValue, MissingParameterValue, projectScopedContainersAccessor, }: ParameterInlineRendererProps, @@ -529,9 +531,14 @@ export const renderVariableWithIcon = ( ) ); - const IconAndNameContainer = expressionIsValid - ? React.Fragment - : InvalidParameterValue; + let IconAndNameContainer; + if (!expressionIsValid) { + IconAndNameContainer = InvalidParameterValue; + } else if (hasDeprecationWarning) { + IconAndNameContainer = DeprecatedParameterValue; + } else { + IconAndNameContainer = React.Fragment; + } return ( Date: Wed, 21 Jan 2026 12:15:58 +0200 Subject: [PATCH 6/7] respect showDeprecatedInstructionWarning flag in settings --- .../MetadataDeclarationHelper.cpp | 44 +++++++------------ .../src/EventsSheet/EventsTree/Instruction.js | 28 +++++++----- .../InstructionParametersEditor.js | 6 ++- .../GenericExpressionField/index.js | 19 ++++++-- 4 files changed, 54 insertions(+), 43 deletions(-) diff --git a/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp b/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp index 89aa5803c77c..e6e573f24179 100644 --- a/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/MetadataDeclarationHelper.cpp @@ -462,14 +462,6 @@ gd::String MetadataDeclarationHelper::GetFullName(const gd::EventsFunction &even return GetTranslation(eventsFunction.GetFullName()) || eventsFunction.GetName(); }; -gd::String GetFullNameWithDeprecatedPrefix(const gd::EventsFunction &eventsFunction) { - gd::String fullName = GetTranslation(eventsFunction.GetFullName()) || eventsFunction.GetName(); - if (eventsFunction.IsDeprecated()) { - fullName = "[DEPRECATED] " + fullName; - } - return fullName; -}; - gd::String MetadataDeclarationHelper::GetDefaultSentence( const gd::EventsFunction &eventsFunction, const int firstParameterIndex, const int parameterIndexOffset) { @@ -494,39 +486,33 @@ gd::String MetadataDeclarationHelper::GetDefaultSentence( }; gd::String MetadataDeclarationHelper::GetFreeFunctionSentence(const gd::EventsFunction &eventsFunction) { - gd::String sentence = GetTranslation(eventsFunction.GetSentence()).empty() + // Note: [DEPRECATED] prefix is now added in the UI layer (Instruction.js) + // based on user preference "showDeprecatedInstructionWarning" + return GetTranslation(eventsFunction.GetSentence()).empty() ? GetDefaultSentence(eventsFunction, 0, 1) : GetTranslation(eventsFunction.GetSentence()); - if (eventsFunction.IsDeprecated()) { - sentence = "[DEPRECATED] " + sentence; - } - return sentence; }; gd::String MetadataDeclarationHelper::GetBehaviorFunctionSentence( const gd::EventsFunction &eventsFunction, const bool excludeObjectParameter) { - gd::String sentence = GetTranslation(eventsFunction.GetSentence()).empty() + // Note: [DEPRECATED] prefix is now added in the UI layer (Instruction.js) + // based on user preference "showDeprecatedInstructionWarning" + return GetTranslation(eventsFunction.GetSentence()).empty() ? GetDefaultSentence(eventsFunction, excludeObjectParameter ? 2 : 0, 0) : GetTranslation(eventsFunction.GetSentence()); - if (eventsFunction.IsDeprecated()) { - sentence = "[DEPRECATED] " + sentence; - } - return sentence; }; gd::String MetadataDeclarationHelper::GetObjectFunctionSentence( const gd::EventsFunction &eventsFunction, const bool excludeObjectParameter) { - gd::String sentence = GetTranslation(eventsFunction.GetSentence()).empty() + // Note: [DEPRECATED] prefix is now added in the UI layer (Instruction.js) + // based on user preference "showDeprecatedInstructionWarning" + return GetTranslation(eventsFunction.GetSentence()).empty() ? GetDefaultSentence(eventsFunction, excludeObjectParameter ? 1 : 0, 0) : GetTranslation(eventsFunction.GetSentence()); - if (eventsFunction.IsDeprecated()) { - sentence = "[DEPRECATED] " + sentence; - } - return sentence; }; /** @@ -562,13 +548,13 @@ MetadataDeclarationHelper::DeclareExpressionMetadata( eventsFunction.GetExpressionType().IsNumber() ? extension.AddExpression( eventsFunction.GetName(), - GetFullNameWithDeprecatedPrefix(eventsFunction), + MetadataDeclarationHelper::GetFullName(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup(), GetExtensionIconUrl(extension)) : extension.AddStrExpression( eventsFunction.GetName(), - GetFullNameWithDeprecatedPrefix(eventsFunction), + MetadataDeclarationHelper::GetFullName(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup(), GetExtensionIconUrl(extension)); @@ -753,7 +739,7 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata( (eventsFunction.GetExpressionType().IsNumber()) ? behaviorMetadata.AddExpression( eventsFunction.GetName(), - GetFullNameWithDeprecatedPrefix(eventsFunction), + MetadataDeclarationHelper::GetFullName(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup() || @@ -762,7 +748,7 @@ MetadataDeclarationHelper::DeclareBehaviorExpressionMetadata( GetExtensionIconUrl(extension)) : behaviorMetadata.AddStrExpression( eventsFunction.GetName(), - GetFullNameWithDeprecatedPrefix(eventsFunction), + MetadataDeclarationHelper::GetFullName(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup() || @@ -927,7 +913,7 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata( (eventsFunction.GetExpressionType().IsNumber()) ? objectMetadata.AddExpression( eventsFunction.GetName(), - GetFullNameWithDeprecatedPrefix(eventsFunction), + MetadataDeclarationHelper::GetFullName(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup() || @@ -936,7 +922,7 @@ MetadataDeclarationHelper::DeclareObjectExpressionMetadata( GetExtensionIconUrl(extension)) : objectMetadata.AddStrExpression( eventsFunction.GetName(), - GetFullNameWithDeprecatedPrefix(eventsFunction), + MetadataDeclarationHelper::GetFullName(eventsFunction), eventsFunction.GetDescription() || GetFullName(eventsFunction), eventsFunction.GetGroup() || diff --git a/newIDE/app/src/EventsSheet/EventsTree/Instruction.js b/newIDE/app/src/EventsSheet/EventsTree/Instruction.js index e4dcd3c41c9b..ea3c23f56836 100644 --- a/newIDE/app/src/EventsSheet/EventsTree/Instruction.js +++ b/newIDE/app/src/EventsSheet/EventsTree/Instruction.js @@ -280,7 +280,13 @@ const Instruction = (props: Props) => { /> ); } - return {value}; + // Add [DEPRECATED] prefix for the first text segment if the preference is enabled + // and the instruction is deprecated (hidden) + const deprecatedPrefix = + i === 0 && showDeprecatedInstructionWarning && metadata.isHidden() + ? '[DEPRECATED] ' + : ''; + return {deprecatedPrefix + value}; } const parameterMetadata = metadata.getParameter(parameterIndex); @@ -301,15 +307,17 @@ const Instruction = (props: Props) => { parameterIndex, value ); - // Check for deprecation warnings - hasDeprecationWarning = gd.InstructionValidator.hasDeprecationWarnings( - platform, - projectScopedContainers, - instruction, - metadata, - parameterIndex, - value - ); + // Check for deprecation warnings (only if the preference is enabled) + if (showDeprecatedInstructionWarning) { + hasDeprecationWarning = gd.InstructionValidator.hasDeprecationWarnings( + platform, + projectScopedContainers, + instruction, + metadata, + parameterIndex, + value + ); + } // TODO Move this code inside `InstructionValidator.isParameterValid` if ( expressionIsValid && diff --git a/newIDE/app/src/EventsSheet/InstructionEditor/InstructionParametersEditor.js b/newIDE/app/src/EventsSheet/InstructionEditor/InstructionParametersEditor.js index 86c6370d08ed..b392f29500e3 100644 --- a/newIDE/app/src/EventsSheet/InstructionEditor/InstructionParametersEditor.js +++ b/newIDE/app/src/EventsSheet/InstructionEditor/InstructionParametersEditor.js @@ -28,6 +28,7 @@ import ScrollView from '../../UI/ScrollView'; import { getInstructionTutorialIds } from '../../Utils/GDevelopServices/Tutorial'; import useForceUpdate from '../../Utils/UseForceUpdate'; import GDevelopThemeContext from '../../UI/Theme/GDevelopThemeContext'; +import PreferencesContext from '../../MainFrame/Preferences/PreferencesContext'; import FlatButton from '../../UI/FlatButton'; import { type ParameterFieldInterface, @@ -139,6 +140,9 @@ const InstructionParametersEditor = React.forwardRef< const { palette: { type: paletteType }, } = React.useContext(GDevelopThemeContext); + const preferences = React.useContext(PreferencesContext); + const showDeprecatedInstructionWarning = + preferences.values.showDeprecatedInstructionWarning; const forceUpdate = useForceUpdate(); @@ -311,7 +315,7 @@ const InstructionParametersEditor = React.forwardRef< - {instructionMetadata.isHidden() && ( + {showDeprecatedInstructionWarning && instructionMetadata.isHidden() && ( {instructionMetadata.getDeprecationMessage() ? ( diff --git a/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js b/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js index 179428648130..b2c38b746dfe 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js @@ -49,6 +49,7 @@ import { } from '../../../UI/KeyboardShortcuts/InteractionKeys'; import Paper from '../../../UI/Paper'; import { ProjectScopedContainersAccessor } from '../../../InstructionOrExpression/EventsScope'; +import PreferencesContext from '../../../MainFrame/Preferences/PreferencesContext'; const gd: libGDevelop = global.gd; @@ -134,7 +135,8 @@ const extractErrors = ( projectScopedContainersAccessor: ProjectScopedContainersAccessor, expressionType: string, parameterMetadata: ?gdParameterMetadata, - expressionNode: gdExpressionNode + expressionNode: gdExpressionNode, + showDeprecatedInstructionWarning: boolean ): {| errorText: ?string, errorHighlights: Array, @@ -155,13 +157,17 @@ const extractErrors = ( const errorType = error.getType(); const isDeprecated = errorType === gd.ExpressionParserError.DeprecatedExpression; + // Skip deprecation warnings if the preference is disabled + if (isDeprecated && !showDeprecatedInstructionWarning) { + return null; + } return { begin: error.getStartPosition(), end: error.getEndPosition() + 1, message: error.getMessage(), type: isDeprecated ? 'deprecated' : 'error', }; - }); + }).filter(Boolean); const otherErrorsCount = Math.max( 0, errorHighlights.length - MAX_ERRORS_COUNT @@ -189,6 +195,8 @@ const extractErrors = ( }; export default class ExpressionField extends React.Component { + static contextType = PreferencesContext; + _field: ?SemiControlledTextFieldInterface = null; _fieldElementWidth: ?number = null; _inputElement: ?HTMLInputElement = null; @@ -459,13 +467,18 @@ export default class ExpressionField extends React.Component { const parser = new gd.ExpressionParser2(); const expressionNode = parser.parseExpression(expression).get(); + const showDeprecatedInstructionWarning = this.context + ? this.context.values.showDeprecatedInstructionWarning + : true; + const { errorText, errorHighlights, isOnlyWarning } = extractErrors( gd.JsPlatform.get(), project, projectScopedContainersAccessor, expressionType, parameterMetadata, - expressionNode + expressionNode, + showDeprecatedInstructionWarning ); const extraErrorText = onExtractAdditionalErrors ? onExtractAdditionalErrors(expression, expressionNode) From cac9e89686fb2d9db0bac0006cc2bd5131433c12 Mon Sep 17 00:00:00 2001 From: danvervlad Date: Wed, 21 Jan 2026 16:52:34 +0200 Subject: [PATCH 7/7] refactor --- Core/GDCore/IDE/InstructionValidator.cpp | 95 ++++++++++--------- Core/GDCore/IDE/InstructionValidator.h | 36 +++++++ GDevelop.js/Bindings/Bindings.idl | 12 +++ GDevelop.js/Bindings/Wrapper.cpp | 1 + GDevelop.js/scripts/generate-types.js | 3 +- GDevelop.js/types.d.ts | 6 ++ .../types/expressionparsererror_errortype.js | 2 +- GDevelop.js/types/gdinstructionvalidator.js | 1 + .../types/gdparametervalidationresult.js | 7 ++ GDevelop.js/types/libgdevelop.js | 1 + .../src/EventsSheet/EventsTree/Instruction.js | 14 +-- .../GenericExpressionField/index.js | 2 +- 12 files changed, 122 insertions(+), 58 deletions(-) create mode 100644 GDevelop.js/types/gdparametervalidationresult.js diff --git a/Core/GDCore/IDE/InstructionValidator.cpp b/Core/GDCore/IDE/InstructionValidator.cpp index 71f20db2d318..c54efef066c5 100644 --- a/Core/GDCore/IDE/InstructionValidator.cpp +++ b/Core/GDCore/IDE/InstructionValidator.cpp @@ -25,25 +25,31 @@ namespace gd { -bool InstructionValidator::IsParameterValid( +ParameterValidationResult InstructionValidator::ValidateParameter( const gd::Platform &platform, const gd::ProjectScopedContainers projectScopedContainers, const gd::Instruction &instruction, const InstructionMetadata &metadata, std::size_t parameterIndex, const gd::String &value) { + ParameterValidationResult result; + if (parameterIndex >= instruction.GetParametersCount() || parameterIndex >= metadata.GetParametersCount()) { - return false; + result.isValid = false; + return result; } + const auto ¶meterMetadata = metadata.GetParameter(parameterIndex); // TODO Remove the ternary when all parameter declarations use // "number" instead of "expression". const auto ¶meterType = parameterMetadata.GetType() == "expression" ? "number" : parameterMetadata.GetType(); + bool shouldNotBeValidated = parameterType == "layer" && value.empty(); if (shouldNotBeValidated) { - return true; + return result; // Valid by default, no deprecation warning } + if (gd::ParameterMetadata::IsExpression("number", parameterType) || gd::ParameterMetadata::IsExpression("string", parameterType) || gd::ParameterMetadata::IsExpression("variable", parameterType)) { @@ -53,15 +59,26 @@ bool InstructionValidator::IsParameterValid( parameterType, parameterMetadata.GetExtraInfo()); expressionNode.Visit(expressionValidator); - // Use GetFatalErrors() instead of GetAllErrors() to allow non-fatal - // warnings (like deprecation) to pass validation. + + // Check for fatal errors (validation) if (!expressionValidator.GetFatalErrors().empty()) { - return false; + result.isValid = false; + } + + // Check for deprecation warnings in the same pass + const auto &allErrors = expressionValidator.GetAllErrors(); + for (const auto *error : allErrors) { + if (error->GetType() == + gd::ExpressionParserError::ErrorType::DeprecatedExpression) { + result.hasDeprecationWarning = true; + break; + } } + // New object variable instructions require the variable to be // declared while legacy ones don't. // This is why it's done here instead of in the parser directly. - if (parameterType == "objectvar" && + if (result.isValid && parameterType == "objectvar" && gd::VariableInstructionSwitcher::IsSwitchableVariableInstruction( instruction.GetType())) { // Check at least the name of the root variable, it's the best we can @@ -75,7 +92,7 @@ bool InstructionValidator::IsParameterValid( objectName, gd::InstructionValidator::GetRootVariableName(variableName)) == gd::ObjectsContainersList::DoesNotExist) { - return false; + result.isValid = false; } } } else if (gd::ParameterMetadata::IsObject(parameterType)) { @@ -83,19 +100,31 @@ bool InstructionValidator::IsParameterValid( instruction.GetParameter(parameterIndex).GetPlainString(); const auto &objectsContainersList = projectScopedContainers.GetObjectsContainersList(); - return objectsContainersList.HasObjectOrGroupNamed(objectOrGroupName) && - (parameterMetadata.GetExtraInfo().empty() || - objectsContainersList.GetTypeOfObject(objectOrGroupName) == - parameterMetadata.GetExtraInfo()) && - InstructionValidator::HasRequiredBehaviors( - instruction, metadata, parameterIndex, objectsContainersList); + result.isValid = + objectsContainersList.HasObjectOrGroupNamed(objectOrGroupName) && + (parameterMetadata.GetExtraInfo().empty() || + objectsContainersList.GetTypeOfObject(objectOrGroupName) == + parameterMetadata.GetExtraInfo()) && + InstructionValidator::HasRequiredBehaviors( + instruction, metadata, parameterIndex, objectsContainersList); } else if (gd::ParameterMetadata::IsExpression("resource", parameterType)) { const auto &resourceName = instruction.GetParameter(parameterIndex).GetPlainString(); - return projectScopedContainers.GetResourcesContainersList() - .HasResourceNamed(resourceName); + result.isValid = projectScopedContainers.GetResourcesContainersList() + .HasResourceNamed(resourceName); } - return true; + + return result; +} + +bool InstructionValidator::IsParameterValid( + const gd::Platform &platform, + const gd::ProjectScopedContainers projectScopedContainers, + const gd::Instruction &instruction, const InstructionMetadata &metadata, + std::size_t parameterIndex, const gd::String &value) { + return ValidateParameter(platform, projectScopedContainers, instruction, + metadata, parameterIndex, value) + .isValid; } gd::String InstructionValidator::GetRootVariableName(const gd::String &name) { @@ -115,35 +144,9 @@ bool InstructionValidator::HasDeprecationWarnings( const gd::ProjectScopedContainers projectScopedContainers, const gd::Instruction &instruction, const InstructionMetadata &metadata, std::size_t parameterIndex, const gd::String &value) { - if (parameterIndex >= instruction.GetParametersCount() || - parameterIndex >= metadata.GetParametersCount()) { - return false; - } - const auto ¶meterMetadata = metadata.GetParameter(parameterIndex); - const auto ¶meterType = parameterMetadata.GetType() == "expression" - ? "number" - : parameterMetadata.GetType(); - - if (gd::ParameterMetadata::IsExpression("number", parameterType) || - gd::ParameterMetadata::IsExpression("string", parameterType) || - gd::ParameterMetadata::IsExpression("variable", parameterType)) { - auto &expressionNode = - *instruction.GetParameter(parameterIndex).GetRootNode(); - ExpressionValidator expressionValidator(platform, projectScopedContainers, - parameterType, - parameterMetadata.GetExtraInfo()); - expressionNode.Visit(expressionValidator); - - // Check if there are any deprecation warnings - const auto &allErrors = expressionValidator.GetAllErrors(); - for (const auto *error : allErrors) { - if (error->GetType() == - gd::ExpressionParserError::ErrorType::DeprecatedExpression) { - return true; - } - } - } - return false; + return ValidateParameter(platform, projectScopedContainers, instruction, + metadata, parameterIndex, value) + .hasDeprecationWarning; } bool InstructionValidator::HasRequiredBehaviors( diff --git a/Core/GDCore/IDE/InstructionValidator.h b/Core/GDCore/IDE/InstructionValidator.h index da76bf262788..29f438f53611 100644 --- a/Core/GDCore/IDE/InstructionValidator.h +++ b/Core/GDCore/IDE/InstructionValidator.h @@ -18,8 +18,42 @@ class String; namespace gd { +/** + * \brief Result of parameter validation containing both validity status + * and deprecation warning. + */ +struct GD_CORE_API ParameterValidationResult { + bool isValid = true; + bool hasDeprecationWarning = false; + + ParameterValidationResult() = default; + ParameterValidationResult(bool isValid_, bool hasDeprecationWarning_) + : isValid(isValid_), hasDeprecationWarning(hasDeprecationWarning_) {} + + bool IsValid() const { return isValid; } + bool HasDeprecationWarning() const { return hasDeprecationWarning; } +}; + class GD_CORE_API InstructionValidator { public: + /** + * \brief Validate a parameter and check for deprecation warnings in a single + * pass. + * + * This method is more efficient than calling IsParameterValid and + * HasDeprecationWarnings separately as it only parses the expression once. + */ + static ParameterValidationResult ValidateParameter( + const gd::Platform &platform, + const gd::ProjectScopedContainers projectScopedContainers, + const gd::Instruction &instruction, const InstructionMetadata &metadata, + std::size_t parameterIndex, const gd::String &value); + + /** + * \brief Check if a parameter is valid. + * \deprecated Use ValidateParameter instead for better performance when you + * also need to check for deprecation warnings. + */ static bool IsParameterValid(const gd::Platform &platform, const gd::ProjectScopedContainers projectScopedContainers, @@ -29,6 +63,8 @@ class GD_CORE_API InstructionValidator { /** * \brief Check if a parameter expression has deprecation warnings. + * \deprecated Use ValidateParameter instead for better performance when you + * also need to check for validity. */ static bool HasDeprecationWarnings(const gd::Platform &platform, diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index a795ef00f540..0c4f2d788b11 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -2925,7 +2925,19 @@ interface BehaviorParameterFiller { [Ref] Instruction instruction); }; +interface ParameterValidationResult { + boolean IsValid(); + boolean HasDeprecationWarning(); +}; + interface InstructionValidator { + [Value] ParameterValidationResult STATIC_ValidateParameter( + [Const, Ref] Platform platform, + [Const, Ref] ProjectScopedContainers projectScopedContainers, + [Const, Ref] Instruction instruction, + [Const, Ref] InstructionMetadata metadata, + long parameterIndex, + [Const] DOMString value); boolean STATIC_IsParameterValid( [Const, Ref] Platform platform, [Const, Ref] ProjectScopedContainers projectScopedContainers, diff --git a/GDevelop.js/Bindings/Wrapper.cpp b/GDevelop.js/Bindings/Wrapper.cpp index bc2492cbb466..890b027aad22 100644 --- a/GDevelop.js/Bindings/Wrapper.cpp +++ b/GDevelop.js/Bindings/Wrapper.cpp @@ -679,6 +679,7 @@ typedef std::vector VectorPropertyDescriptorChoice #define STATIC_GetBehaviorsWithType GetBehaviorsWithType #define STATIC_IsBehaviorCompatibleWithObject IsBehaviorCompatibleWithObject #define STATIC_FillBehaviorParameters FillBehaviorParameters +#define STATIC_ValidateParameter ValidateParameter #define STATIC_IsParameterValid IsParameterValid #define STATIC_HasDeprecationWarnings HasDeprecationWarnings #define STATIC_FixInvalidRequiredBehaviorProperties \ diff --git a/GDevelop.js/scripts/generate-types.js b/GDevelop.js/scripts/generate-types.js index 36963723ed99..292614690608 100644 --- a/GDevelop.js/scripts/generate-types.js +++ b/GDevelop.js/scripts/generate-types.js @@ -232,7 +232,8 @@ type ExpressionCompletionDescription_CompletionKind = 0 | 1 | 2 | 3 | 4 | 5 | 6` fs.writeFileSync( 'types/expressionparsererror_errortype.js', `// Automatically generated by GDevelop.js/scripts/generate-types.js -type ExpressionParserError_ErrorType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14` +type ExpressionParserError_ErrorType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 +` ); shell.sed( '-i', diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index b19713139db8..dc3f19a308e0 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -2105,7 +2105,13 @@ export class BehaviorParameterFiller extends EmscriptenObject { static fillBehaviorParameters(platform: Platform, projectScopedContainers: ProjectScopedContainers, instructionMetadata: InstructionMetadata, instruction: Instruction): boolean; } +export class ParameterValidationResult extends EmscriptenObject { + isValid(): boolean; + hasDeprecationWarning(): boolean; +} + export class InstructionValidator extends EmscriptenObject { + static validateParameter(platform: Platform, projectScopedContainers: ProjectScopedContainers, instruction: Instruction, metadata: InstructionMetadata, parameterIndex: number, value: string): ParameterValidationResult; static isParameterValid(platform: Platform, projectScopedContainers: ProjectScopedContainers, instruction: Instruction, metadata: InstructionMetadata, parameterIndex: number, value: string): boolean; static hasDeprecationWarnings(platform: Platform, projectScopedContainers: ProjectScopedContainers, instruction: Instruction, metadata: InstructionMetadata, parameterIndex: number, value: string): boolean; } diff --git a/GDevelop.js/types/expressionparsererror_errortype.js b/GDevelop.js/types/expressionparsererror_errortype.js index 9f2a9c08f1df..e434a1530aa4 100644 --- a/GDevelop.js/types/expressionparsererror_errortype.js +++ b/GDevelop.js/types/expressionparsererror_errortype.js @@ -1,2 +1,2 @@ // Automatically generated by GDevelop.js/scripts/generate-types.js -type ExpressionParserError_ErrorType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 \ No newline at end of file +type ExpressionParserError_ErrorType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 diff --git a/GDevelop.js/types/gdinstructionvalidator.js b/GDevelop.js/types/gdinstructionvalidator.js index 9bed2ee481f1..d6b5535886ce 100644 --- a/GDevelop.js/types/gdinstructionvalidator.js +++ b/GDevelop.js/types/gdinstructionvalidator.js @@ -1,5 +1,6 @@ // Automatically generated by GDevelop.js/scripts/generate-types.js declare class gdInstructionValidator { + static validateParameter(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, instruction: gdInstruction, metadata: gdInstructionMetadata, parameterIndex: number, value: string): gdParameterValidationResult; static isParameterValid(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, instruction: gdInstruction, metadata: gdInstructionMetadata, parameterIndex: number, value: string): boolean; static hasDeprecationWarnings(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, instruction: gdInstruction, metadata: gdInstructionMetadata, parameterIndex: number, value: string): boolean; delete(): void; diff --git a/GDevelop.js/types/gdparametervalidationresult.js b/GDevelop.js/types/gdparametervalidationresult.js new file mode 100644 index 000000000000..c0c7f060e397 --- /dev/null +++ b/GDevelop.js/types/gdparametervalidationresult.js @@ -0,0 +1,7 @@ +// Automatically generated by GDevelop.js/scripts/generate-types.js +declare class gdParameterValidationResult { + isValid(): boolean; + hasDeprecationWarning(): boolean; + delete(): void; + ptr: number; +}; \ No newline at end of file diff --git a/GDevelop.js/types/libgdevelop.js b/GDevelop.js/types/libgdevelop.js index 7d5581549e07..b86f9034fcba 100644 --- a/GDevelop.js/types/libgdevelop.js +++ b/GDevelop.js/types/libgdevelop.js @@ -199,6 +199,7 @@ declare class libGDevelop { VariablesChangeset: Class; WholeProjectRefactorer: Class; BehaviorParameterFiller: Class; + ParameterValidationResult: Class; InstructionValidator: Class; ObjectTools: Class; EventsBasedObjectDependencyFinder: Class; diff --git a/newIDE/app/src/EventsSheet/EventsTree/Instruction.js b/newIDE/app/src/EventsSheet/EventsTree/Instruction.js index ea3c23f56836..011175d1e9a9 100644 --- a/newIDE/app/src/EventsSheet/EventsTree/Instruction.js +++ b/newIDE/app/src/EventsSheet/EventsTree/Instruction.js @@ -299,7 +299,8 @@ const Instruction = (props: Props) => { let expressionIsValid = true; let hasDeprecationWarning = false; if (!shouldNotBeValidated({ value, parameterType })) { - expressionIsValid = gd.InstructionValidator.isParameterValid( + // Use validateParameter for combined validation (single pass) + const validationResult = gd.InstructionValidator.validateParameter( platform, projectScopedContainers, instruction, @@ -307,17 +308,12 @@ const Instruction = (props: Props) => { parameterIndex, value ); + expressionIsValid = validationResult.isValid(); // Check for deprecation warnings (only if the preference is enabled) if (showDeprecatedInstructionWarning) { - hasDeprecationWarning = gd.InstructionValidator.hasDeprecationWarnings( - platform, - projectScopedContainers, - instruction, - metadata, - parameterIndex, - value - ); + hasDeprecationWarning = validationResult.hasDeprecationWarning(); } + validationResult.delete(); // TODO Move this code inside `InstructionValidator.isParameterValid` if ( expressionIsValid && diff --git a/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js b/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js index b2c38b746dfe..c659de6d7385 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/GenericExpressionField/index.js @@ -94,7 +94,7 @@ const styles = { paddingRight: 0, }, warningText: { - color: '#ff9800', // Orange color for warnings + color: '#ff9800', }, };