Skip to content

Commit f1401b3

Browse files
authored
Add support for marking extension functions as deprecated, with visual warnings/explanations (#8156)
1 parent e1ab2da commit f1401b3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+680
-65
lines changed

Core/GDCore/Events/Parsers/ExpressionParser2Node.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ struct GD_CORE_API ExpressionParserError {
5959
UnknownParameterType,
6060
MissingBehavior,
6161
VariableNameCollision,
62+
DeprecatedExpression,
6263
};
6364

6465
ExpressionParserError(gd::ExpressionParserError::ErrorType type_,
@@ -78,7 +79,7 @@ struct GD_CORE_API ExpressionParserError {
7879
location(startPosition_, endPosition_){};
7980
virtual ~ExpressionParserError(){};
8081

81-
gd::ExpressionParserError::ErrorType GetType() { return type; }
82+
gd::ExpressionParserError::ErrorType GetType() const { return type; }
8283
const gd::String &GetMessage() { return message; }
8384
const gd::String &GetObjectName() { return objectName; }
8485
const gd::String &GetActualValue() { return actualValue; }

Core/GDCore/Extensions/Metadata/AbstractFunctionMetadata.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ class GD_CORE_API AbstractFunctionMetadata {
7676
*/
7777
virtual AbstractFunctionMetadata &SetHidden() = 0;
7878

79+
/**
80+
* \brief Set the deprecation message that explains why the function
81+
* is deprecated and what to use instead.
82+
*/
83+
virtual AbstractFunctionMetadata &
84+
SetDeprecationMessage(const gd::String &message) = 0;
85+
7986
/**
8087
* Set that the instruction is private - it can't be used outside of the
8188
* object/ behavior that it is attached too.

Core/GDCore/Extensions/Metadata/ExpressionMetadata.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,26 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
7272
*/
7373
ExpressionMetadata& SetHidden() override;
7474

75+
/**
76+
* \brief Set the deprecation message that explains why the expression
77+
* is deprecated and what to use instead.
78+
*/
79+
ExpressionMetadata& SetDeprecationMessage(const gd::String& message) override {
80+
deprecationMessage = message;
81+
return *this;
82+
}
83+
84+
/**
85+
* \brief Get the deprecation message that explains why the expression
86+
* is deprecated and what to use instead.
87+
*/
88+
const gd::String& GetDeprecationMessage() const { return deprecationMessage; }
89+
90+
/**
91+
* \brief Check if the expression is deprecated.
92+
*/
93+
bool IsDeprecated() const { return !deprecationMessage.empty(); }
94+
7595
/**
7696
* \brief Set the group of the instruction in the IDE.
7797
*/
@@ -369,6 +389,7 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
369389
bool isPrivate;
370390
gd::String requiredBaseObjectCapability;
371391
gd::String relevantContext;
392+
gd::String deprecationMessage;
372393

373394
gd::ParameterMetadataContainer parameters;
374395
};

Core/GDCore/Extensions/Metadata/InstructionMetadata.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,21 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata {
200200
return *this;
201201
}
202202

203+
/**
204+
* \brief Set the deprecation message that explains why the instruction
205+
* is deprecated and what to use instead.
206+
*/
207+
InstructionMetadata &SetDeprecationMessage(const gd::String &message) override {
208+
deprecationMessage = message;
209+
return *this;
210+
}
211+
212+
/**
213+
* \brief Get the deprecation message that explains why the instruction
214+
* is deprecated and what to use instead.
215+
*/
216+
const gd::String &GetDeprecationMessage() const { return deprecationMessage; }
217+
203218
/**
204219
* \brief Set the group of the instruction in the IDE.
205220
*/
@@ -586,6 +601,7 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata {
586601
bool isBehaviorInstruction;
587602
gd::String requiredBaseObjectCapability;
588603
gd::String relevantContext;
604+
gd::String deprecationMessage;
589605
};
590606

591607
} // namespace gd

Core/GDCore/Extensions/Metadata/MultipleInstructionMetadata.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ class GD_CORE_API MultipleInstructionMetadata : public AbstractFunctionMetadata
113113
return *this;
114114
};
115115

116+
/**
117+
* \brief Set the deprecation message that explains why the instruction
118+
* is deprecated and what to use instead.
119+
*/
120+
MultipleInstructionMetadata &SetDeprecationMessage(
121+
const gd::String &message) override {
122+
if (expression) expression->SetDeprecationMessage(message);
123+
if (condition) condition->SetDeprecationMessage(message);
124+
if (action) action->SetDeprecationMessage(message);
125+
return *this;
126+
}
127+
116128
/**
117129
* \see gd::InstructionMetadata::SetRequiresBaseObjectCapability
118130
*/

Core/GDCore/IDE/Events/ExpressionValidator.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,16 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(
295295
return returnType;
296296
}
297297

298+
// Check if the expression is deprecated
299+
if (metadata.IsDeprecated()) {
300+
gd::String deprecationMessage = metadata.GetDeprecationMessage();
301+
RaiseError(gd::ExpressionParserError::ErrorType::DeprecatedExpression,
302+
_("This expression is deprecated.") +
303+
(deprecationMessage.empty() ? "" : " " + deprecationMessage),
304+
function.location,
305+
/*isFatal=*/false);
306+
}
307+
298308
// Validate the type of the function
299309
if (returnType == Type::Number) {
300310
if (parentType == Type::String) {

Core/GDCore/IDE/InstructionValidator.cpp

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
#include "InstructionValidator.h"
77

8+
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
89
#include "GDCore/Extensions/Metadata/AbstractFunctionMetadata.h"
910
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
1011
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
@@ -24,25 +25,31 @@
2425

2526
namespace gd {
2627

27-
bool InstructionValidator::IsParameterValid(
28+
ParameterValidationResult InstructionValidator::ValidateParameter(
2829
const gd::Platform &platform,
2930
const gd::ProjectScopedContainers projectScopedContainers,
3031
const gd::Instruction &instruction, const InstructionMetadata &metadata,
3132
std::size_t parameterIndex, const gd::String &value) {
33+
ParameterValidationResult result;
34+
3235
if (parameterIndex >= instruction.GetParametersCount() ||
3336
parameterIndex >= metadata.GetParametersCount()) {
34-
return false;
37+
result.isValid = false;
38+
return result;
3539
}
40+
3641
const auto &parameterMetadata = metadata.GetParameter(parameterIndex);
3742
// TODO Remove the ternary when all parameter declarations use
3843
// "number" instead of "expression".
3944
const auto &parameterType = parameterMetadata.GetType() == "expression"
4045
? "number"
4146
: parameterMetadata.GetType();
47+
4248
bool shouldNotBeValidated = parameterType == "layer" && value.empty();
4349
if (shouldNotBeValidated) {
44-
return true;
50+
return result; // Valid by default, no deprecation warning
4551
}
52+
4653
if (gd::ParameterMetadata::IsExpression("number", parameterType) ||
4754
gd::ParameterMetadata::IsExpression("string", parameterType) ||
4855
gd::ParameterMetadata::IsExpression("variable", parameterType)) {
@@ -52,13 +59,26 @@ bool InstructionValidator::IsParameterValid(
5259
parameterType,
5360
parameterMetadata.GetExtraInfo());
5461
expressionNode.Visit(expressionValidator);
55-
if (!expressionValidator.GetAllErrors().empty()) {
56-
return false;
62+
63+
// Check for fatal errors (validation)
64+
if (!expressionValidator.GetFatalErrors().empty()) {
65+
result.isValid = false;
66+
}
67+
68+
// Check for deprecation warnings in the same pass
69+
const auto &allErrors = expressionValidator.GetAllErrors();
70+
for (const auto *error : allErrors) {
71+
if (error->GetType() ==
72+
gd::ExpressionParserError::ErrorType::DeprecatedExpression) {
73+
result.hasDeprecationWarning = true;
74+
break;
75+
}
5776
}
77+
5878
// New object variable instructions require the variable to be
5979
// declared while legacy ones don't.
6080
// This is why it's done here instead of in the parser directly.
61-
if (parameterType == "objectvar" &&
81+
if (result.isValid && parameterType == "objectvar" &&
6282
gd::VariableInstructionSwitcher::IsSwitchableVariableInstruction(
6383
instruction.GetType())) {
6484
// Check at least the name of the root variable, it's the best we can
@@ -72,27 +92,39 @@ bool InstructionValidator::IsParameterValid(
7292
objectName,
7393
gd::InstructionValidator::GetRootVariableName(variableName)) ==
7494
gd::ObjectsContainersList::DoesNotExist) {
75-
return false;
95+
result.isValid = false;
7696
}
7797
}
7898
} else if (gd::ParameterMetadata::IsObject(parameterType)) {
7999
const auto &objectOrGroupName =
80100
instruction.GetParameter(parameterIndex).GetPlainString();
81101
const auto &objectsContainersList =
82102
projectScopedContainers.GetObjectsContainersList();
83-
return objectsContainersList.HasObjectOrGroupNamed(objectOrGroupName) &&
84-
(parameterMetadata.GetExtraInfo().empty() ||
85-
objectsContainersList.GetTypeOfObject(objectOrGroupName) ==
86-
parameterMetadata.GetExtraInfo()) &&
87-
InstructionValidator::HasRequiredBehaviors(
88-
instruction, metadata, parameterIndex, objectsContainersList);
103+
result.isValid =
104+
objectsContainersList.HasObjectOrGroupNamed(objectOrGroupName) &&
105+
(parameterMetadata.GetExtraInfo().empty() ||
106+
objectsContainersList.GetTypeOfObject(objectOrGroupName) ==
107+
parameterMetadata.GetExtraInfo()) &&
108+
InstructionValidator::HasRequiredBehaviors(
109+
instruction, metadata, parameterIndex, objectsContainersList);
89110
} else if (gd::ParameterMetadata::IsExpression("resource", parameterType)) {
90111
const auto &resourceName =
91112
instruction.GetParameter(parameterIndex).GetPlainString();
92-
return projectScopedContainers.GetResourcesContainersList()
93-
.HasResourceNamed(resourceName);
113+
result.isValid = projectScopedContainers.GetResourcesContainersList()
114+
.HasResourceNamed(resourceName);
94115
}
95-
return true;
116+
117+
return result;
118+
}
119+
120+
bool InstructionValidator::IsParameterValid(
121+
const gd::Platform &platform,
122+
const gd::ProjectScopedContainers projectScopedContainers,
123+
const gd::Instruction &instruction, const InstructionMetadata &metadata,
124+
std::size_t parameterIndex, const gd::String &value) {
125+
return ValidateParameter(platform, projectScopedContainers, instruction,
126+
metadata, parameterIndex, value)
127+
.isValid;
96128
}
97129

98130
gd::String InstructionValidator::GetRootVariableName(const gd::String &name) {
@@ -107,6 +139,16 @@ gd::String InstructionValidator::GetRootVariableName(const gd::String &name) {
107139
: squareBracketPosition);
108140
};
109141

142+
bool InstructionValidator::HasDeprecationWarnings(
143+
const gd::Platform &platform,
144+
const gd::ProjectScopedContainers projectScopedContainers,
145+
const gd::Instruction &instruction, const InstructionMetadata &metadata,
146+
std::size_t parameterIndex, const gd::String &value) {
147+
return ValidateParameter(platform, projectScopedContainers, instruction,
148+
metadata, parameterIndex, value)
149+
.hasDeprecationWarning;
150+
}
151+
110152
bool InstructionValidator::HasRequiredBehaviors(
111153
const gd::Instruction &instruction,
112154
const gd::InstructionMetadata &instructionMetadata,

Core/GDCore/IDE/InstructionValidator.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,61 @@ class String;
1818

1919
namespace gd {
2020

21+
/**
22+
* \brief Result of parameter validation containing both validity status
23+
* and deprecation warning.
24+
*/
25+
struct GD_CORE_API ParameterValidationResult {
26+
bool isValid = true;
27+
bool hasDeprecationWarning = false;
28+
29+
ParameterValidationResult() = default;
30+
ParameterValidationResult(bool isValid_, bool hasDeprecationWarning_)
31+
: isValid(isValid_), hasDeprecationWarning(hasDeprecationWarning_) {}
32+
33+
bool IsValid() const { return isValid; }
34+
bool HasDeprecationWarning() const { return hasDeprecationWarning; }
35+
};
36+
2137
class GD_CORE_API InstructionValidator {
2238
public:
39+
/**
40+
* \brief Validate a parameter and check for deprecation warnings in a single
41+
* pass.
42+
*
43+
* This method is more efficient than calling IsParameterValid and
44+
* HasDeprecationWarnings separately as it only parses the expression once.
45+
*/
46+
static ParameterValidationResult ValidateParameter(
47+
const gd::Platform &platform,
48+
const gd::ProjectScopedContainers projectScopedContainers,
49+
const gd::Instruction &instruction, const InstructionMetadata &metadata,
50+
std::size_t parameterIndex, const gd::String &value);
51+
52+
/**
53+
* \brief Check if a parameter is valid.
54+
* \deprecated Use ValidateParameter instead for better performance when you
55+
* also need to check for deprecation warnings.
56+
*/
2357
static bool
2458
IsParameterValid(const gd::Platform &platform,
2559
const gd::ProjectScopedContainers projectScopedContainers,
2660
const gd::Instruction &instruction,
2761
const InstructionMetadata &metadata,
2862
std::size_t parameterIndex, const gd::String &value);
2963

64+
/**
65+
* \brief Check if a parameter expression has deprecation warnings.
66+
* \deprecated Use ValidateParameter instead for better performance when you
67+
* also need to check for validity.
68+
*/
69+
static bool
70+
HasDeprecationWarnings(const gd::Platform &platform,
71+
const gd::ProjectScopedContainers projectScopedContainers,
72+
const gd::Instruction &instruction,
73+
const InstructionMetadata &metadata,
74+
std::size_t parameterIndex, const gd::String &value);
75+
3076
static gd::String GetRootVariableName(const gd::String &name);
3177

3278
private:

Core/GDCore/Project/EventsFunction.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ void EventsFunction::SerializeTo(SerializerElement& element) const {
7878
if (!helpUrl.empty()) {
7979
element.SetAttribute("helpUrl", helpUrl);
8080
}
81+
if (isDeprecated) {
82+
element.SetBoolAttribute("deprecated", isDeprecated);
83+
}
84+
if (!deprecationMessage.empty()) {
85+
element.SetAttribute("deprecationMessage", deprecationMessage);
86+
}
8187
events.SerializeTo(element.AddChild("events"));
8288

8389
gd::String functionTypeStr = "Action";
@@ -120,6 +126,8 @@ void EventsFunction::UnserializeFrom(gd::Project& project,
120126
isPrivate = element.GetBoolAttribute("private");
121127
isAsync = element.GetBoolAttribute("async");
122128
helpUrl = element.GetStringAttribute("helpUrl");
129+
isDeprecated = element.GetBoolAttribute("deprecated");
130+
deprecationMessage = element.GetStringAttribute("deprecationMessage");
123131
events.UnserializeFrom(project, element.GetChild("events"));
124132

125133
gd::String functionTypeStr = element.GetStringAttribute("functionType");

Core/GDCore/Project/EventsFunction.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,34 @@ class GD_CORE_API EventsFunction {
236236
return *this;
237237
}
238238

239+
/**
240+
* \brief Returns true if the function is deprecated.
241+
*/
242+
bool IsDeprecated() const { return isDeprecated; }
243+
244+
/**
245+
* \brief Sets whether the function is deprecated.
246+
*/
247+
EventsFunction& SetDeprecated(bool _isDeprecated) {
248+
isDeprecated = _isDeprecated;
249+
return *this;
250+
}
251+
252+
/**
253+
* \brief Get the deprecation message that explains why the function is
254+
* deprecated and what to use instead.
255+
*/
256+
const gd::String& GetDeprecationMessage() const { return deprecationMessage; }
257+
258+
/**
259+
* \brief Set the deprecation message that explains why the function is
260+
* deprecated and what to use instead.
261+
*/
262+
EventsFunction& SetDeprecationMessage(const gd::String& message) {
263+
deprecationMessage = message;
264+
return *this;
265+
}
266+
239267
/**
240268
* \brief Return the events.
241269
*/
@@ -318,6 +346,8 @@ class GD_CORE_API EventsFunction {
318346
bool isPrivate = false;
319347
bool isAsync = false;
320348
gd::String helpUrl;
349+
bool isDeprecated = false;
350+
gd::String deprecationMessage;
321351
};
322352

323353
} // namespace gd

0 commit comments

Comments
 (0)