Skip to content
Open
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
3 changes: 2 additions & 1 deletion Core/GDCore/Events/Parsers/ExpressionParser2Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct GD_CORE_API ExpressionParserError {
UnknownParameterType,
MissingBehavior,
VariableNameCollision,
DeprecatedExpression,
};

ExpressionParserError(gd::ExpressionParserError::ErrorType type_,
Expand All @@ -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; }
Expand Down
7 changes: 7 additions & 0 deletions Core/GDCore/Extensions/Metadata/AbstractFunctionMetadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
21 changes: 21 additions & 0 deletions Core/GDCore/Extensions/Metadata/ExpressionMetadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ 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 Check if the expression is deprecated.
*/
bool IsDeprecated() const { return !deprecationMessage.empty(); }

/**
* \brief Set the group of the instruction in the IDE.
*/
Expand Down Expand Up @@ -369,6 +389,7 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
bool isPrivate;
gd::String requiredBaseObjectCapability;
gd::String relevantContext;
gd::String deprecationMessage;

gd::ParameterMetadataContainer parameters;
};
Expand Down
16 changes: 16 additions & 0 deletions Core/GDCore/Extensions/Metadata/InstructionMetadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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
12 changes: 12 additions & 0 deletions Core/GDCore/Extensions/Metadata/MultipleInstructionMetadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
10 changes: 10 additions & 0 deletions Core/GDCore/IDE/Events/ExpressionValidator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
74 changes: 58 additions & 16 deletions Core/GDCore/IDE/InstructionValidator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -24,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 &parameterMetadata = metadata.GetParameter(parameterIndex);
// TODO Remove the ternary when all parameter declarations use
// "number" instead of "expression".
const auto &parameterType = 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)) {
Expand All @@ -52,13 +59,26 @@ bool InstructionValidator::IsParameterValid(
parameterType,
parameterMetadata.GetExtraInfo());
expressionNode.Visit(expressionValidator);
if (!expressionValidator.GetAllErrors().empty()) {
return false;

// Check for fatal errors (validation)
if (!expressionValidator.GetFatalErrors().empty()) {
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
Expand All @@ -72,27 +92,39 @@ bool InstructionValidator::IsParameterValid(
objectName,
gd::InstructionValidator::GetRootVariableName(variableName)) ==
gd::ObjectsContainersList::DoesNotExist) {
return false;
result.isValid = false;
}
}
} else if (gd::ParameterMetadata::IsObject(parameterType)) {
const auto &objectOrGroupName =
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) {
Expand All @@ -107,6 +139,16 @@ 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) {
return ValidateParameter(platform, projectScopedContainers, instruction,
metadata, parameterIndex, value)
.hasDeprecationWarning;
}

bool InstructionValidator::HasRequiredBehaviors(
const gd::Instruction &instruction,
const gd::InstructionMetadata &instructionMetadata,
Expand Down
46 changes: 46 additions & 0 deletions Core/GDCore/IDE/InstructionValidator.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,61 @@ 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,
const gd::Instruction &instruction,
const InstructionMetadata &metadata,
std::size_t parameterIndex, const gd::String &value);

/**
* \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,
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:
Expand Down
8 changes: 8 additions & 0 deletions Core/GDCore/Project/EventsFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ void EventsFunction::SerializeTo(SerializerElement& element) const {
if (isAsync) {
element.SetBoolAttribute("async", isAsync);
}
if (isDeprecated) {
element.SetBoolAttribute("deprecated", isDeprecated);
}
if (!deprecationMessage.empty()) {
element.SetAttribute("deprecationMessage", deprecationMessage);
}
events.SerializeTo(element.AddChild("events"));

gd::String functionTypeStr = "Action";
Expand Down Expand Up @@ -116,6 +122,8 @@ void EventsFunction::UnserializeFrom(gd::Project& project,
getterName = element.GetStringAttribute("getterName");
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");
Expand Down
30 changes: 30 additions & 0 deletions Core/GDCore/Project/EventsFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,34 @@ 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 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.
*/
Expand Down Expand Up @@ -304,6 +332,8 @@ class GD_CORE_API EventsFunction {
gd::ObjectGroupsContainer objectGroups;
bool isPrivate = false;
bool isAsync = false;
bool isDeprecated = false;
gd::String deprecationMessage;
};

} // namespace gd
Loading