mirror of
https://github.com/4ian/GDevelop.git
synced 2026-10-12 02:01:17 +00:00
Add support for unicode names (any character, including emojis) for object/group/behavior/extension/function names (#5564)
* In other words: this allows to use emojis, accented characters, CJK languages and any word from any language to name anything in GDevelop. This should be very useful for non english speaking users. * This must be activated in the preferences of GDevelop. This will be activated by default once this is proven to work without bugs.
This commit is contained in:
@@ -19,8 +19,10 @@
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "GDCore/Tools/MakeUnique.h"
|
||||
#include "GrammarTerminals.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace gd::GrammarTerminals;
|
||||
|
||||
namespace gd {
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "GDCore/String.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "GDCore/Tools/MakeUnique.h"
|
||||
#include "GrammarTerminals.h"
|
||||
namespace gd {
|
||||
class Expression;
|
||||
class ObjectsContainer;
|
||||
@@ -28,6 +29,8 @@ class ExpressionMetadata;
|
||||
|
||||
namespace gd {
|
||||
|
||||
using namespace gd::GrammarTerminals;
|
||||
|
||||
/** \brief Parse an expression, returning a tree of node corresponding
|
||||
* to the parsed expression.
|
||||
*
|
||||
@@ -211,7 +214,7 @@ class GD_CORE_API ExpressionParser2 {
|
||||
}
|
||||
SkipIfChar(IsClosingParenthesis);
|
||||
return factor;
|
||||
} else if (IsIdentifierAllowedChar()) {
|
||||
} else if (CheckIfChar(IsAllowedInIdentifier)) {
|
||||
return Identifier();
|
||||
}
|
||||
|
||||
@@ -606,95 +609,6 @@ class GD_CORE_API ExpressionParser2 {
|
||||
return predicate(character);
|
||||
}
|
||||
|
||||
bool IsIdentifierAllowedChar() {
|
||||
if (currentPosition >= expression.size()) return false;
|
||||
gd::String::value_type character = expression[currentPosition];
|
||||
|
||||
// Quickly compare if the character is a number or ASCII character.
|
||||
if ((character >= '0' && character <= '9') ||
|
||||
(character >= 'A' && character <= 'Z') ||
|
||||
(character >= 'a' && character <= 'z'))
|
||||
return true;
|
||||
|
||||
// Otherwise do the full check against separators forbidden in identifiers.
|
||||
if (!IsParameterSeparator(character) && !IsDot(character) &&
|
||||
!IsQuote(character) && !IsBracket(character) &&
|
||||
!IsExpressionOperator(character) && !IsTermOperator(character)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsWhitespace(gd::String::value_type character) {
|
||||
return character == ' ' || character == '\n' || character == '\r';
|
||||
}
|
||||
|
||||
static bool IsParameterSeparator(gd::String::value_type character) {
|
||||
return character == ',';
|
||||
}
|
||||
|
||||
static bool IsDot(gd::String::value_type character) {
|
||||
return character == '.';
|
||||
}
|
||||
|
||||
static bool IsQuote(gd::String::value_type character) {
|
||||
return character == '"';
|
||||
}
|
||||
|
||||
static bool IsBracket(gd::String::value_type character) {
|
||||
return character == '(' || character == ')' || character == '[' ||
|
||||
character == ']' || character == '{' || character == '}';
|
||||
}
|
||||
|
||||
static bool IsOpeningParenthesis(gd::String::value_type character) {
|
||||
return character == '(';
|
||||
}
|
||||
|
||||
static bool IsClosingParenthesis(gd::String::value_type character) {
|
||||
return character == ')';
|
||||
}
|
||||
|
||||
static bool IsOpeningSquareBracket(gd::String::value_type character) {
|
||||
return character == '[';
|
||||
}
|
||||
|
||||
static bool IsClosingSquareBracket(gd::String::value_type character) {
|
||||
return character == ']';
|
||||
}
|
||||
|
||||
static bool IsExpressionEndingChar(gd::String::value_type character) {
|
||||
return character == ',' || IsClosingParenthesis(character) ||
|
||||
IsClosingSquareBracket(character);
|
||||
}
|
||||
|
||||
static bool IsExpressionOperator(gd::String::value_type character) {
|
||||
return character == '+' || character == '-' || character == '<' ||
|
||||
character == '>' || character == '?' || character == '^' ||
|
||||
character == '=' || character == '\\' || character == ':' ||
|
||||
character == '!';
|
||||
}
|
||||
|
||||
static bool IsUnaryOperator(gd::String::value_type character) {
|
||||
return character == '+' || character == '-';
|
||||
}
|
||||
|
||||
static bool IsTermOperator(gd::String::value_type character) {
|
||||
return character == '/' || character == '*';
|
||||
}
|
||||
|
||||
static bool IsNumberFirstChar(gd::String::value_type character) {
|
||||
return character == '.' || (character >= '0' && character <= '9');
|
||||
}
|
||||
|
||||
static bool IsNonZeroDigit(gd::String::value_type character) {
|
||||
return (character >= '1' && character <= '9');
|
||||
}
|
||||
|
||||
static bool IsZeroDigit(gd::String::value_type character) {
|
||||
return character == '0';
|
||||
}
|
||||
|
||||
bool IsNamespaceSeparator() {
|
||||
// Namespace separator is a special kind of delimiter as it is 2 characters
|
||||
// long
|
||||
@@ -715,7 +629,7 @@ class GD_CORE_API ExpressionParser2 {
|
||||
gd::String name;
|
||||
size_t startPosition = currentPosition;
|
||||
while (currentPosition < expression.size() &&
|
||||
(IsIdentifierAllowedChar()
|
||||
(CheckIfChar(IsAllowedInIdentifier)
|
||||
// Allow whitespace in identifier name for compatibility
|
||||
|| expression[currentPosition] == ' ')) {
|
||||
name += expression[currentPosition];
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
/**
|
||||
* Contains functions to handle the grammar of the expressions accepted by GDevelop.
|
||||
*/
|
||||
namespace GrammarTerminals {
|
||||
|
||||
inline bool IsWhitespace(gd::String::value_type character) {
|
||||
return character == ' ' || character == '\n' || character == '\r';
|
||||
}
|
||||
|
||||
inline bool IsParameterSeparator(gd::String::value_type character) {
|
||||
return character == ',';
|
||||
}
|
||||
|
||||
inline bool IsDot(gd::String::value_type character) { return character == '.'; }
|
||||
|
||||
inline bool IsQuote(gd::String::value_type character) {
|
||||
return character == '"';
|
||||
}
|
||||
|
||||
inline bool IsBracket(gd::String::value_type character) {
|
||||
return character == '(' || character == ')' || character == '[' ||
|
||||
character == ']' || character == '{' || character == '}';
|
||||
}
|
||||
|
||||
inline bool IsOpeningParenthesis(gd::String::value_type character) {
|
||||
return character == '(';
|
||||
}
|
||||
|
||||
inline bool IsClosingParenthesis(gd::String::value_type character) {
|
||||
return character == ')';
|
||||
}
|
||||
|
||||
inline bool IsOpeningSquareBracket(gd::String::value_type character) {
|
||||
return character == '[';
|
||||
}
|
||||
|
||||
inline bool IsClosingSquareBracket(gd::String::value_type character) {
|
||||
return character == ']';
|
||||
}
|
||||
|
||||
inline bool IsExpressionEndingChar(gd::String::value_type character) {
|
||||
return character == ',' || IsClosingParenthesis(character) ||
|
||||
IsClosingSquareBracket(character);
|
||||
}
|
||||
|
||||
inline bool IsExpressionOperator(gd::String::value_type character) {
|
||||
return character == '+' || character == '-' || character == '<' ||
|
||||
character == '>' || character == '?' || character == '^' ||
|
||||
character == '=' || character == '\\' || character == ':' ||
|
||||
character == '!';
|
||||
}
|
||||
|
||||
inline bool IsUnaryOperator(gd::String::value_type character) {
|
||||
return character == '+' || character == '-';
|
||||
}
|
||||
|
||||
inline bool IsTermOperator(gd::String::value_type character) {
|
||||
return character == '/' || character == '*';
|
||||
}
|
||||
|
||||
inline bool IsNumberFirstChar(gd::String::value_type character) {
|
||||
return character == '.' || (character >= '0' && character <= '9');
|
||||
}
|
||||
|
||||
inline bool IsNonZeroDigit(gd::String::value_type character) {
|
||||
return (character >= '1' && character <= '9');
|
||||
}
|
||||
|
||||
inline bool IsZeroDigit(gd::String::value_type character) {
|
||||
return character == '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given character can be used in an identifier. This is
|
||||
* any unicode character, except for:
|
||||
* `, . " () [] {} + - < > ? ^ = \ : ! / *` and whitespaces (space, line break, carriage return).
|
||||
*
|
||||
* This is loosely based on what is allowed in languages like JavaScript
|
||||
* (see https://mathiasbynens.be/notes/javascript-properties), without support
|
||||
* for unicode escape syntax, and allowing all unicode ranges. The only
|
||||
* disallowed characters are the one used for the grammar.
|
||||
*/
|
||||
inline bool IsAllowedInIdentifier(gd::String::value_type character) {
|
||||
// Quickly compare if the character is a number or ASCII character.
|
||||
if ((character >= '0' && character <= '9') ||
|
||||
(character >= 'A' && character <= 'Z') ||
|
||||
(character >= 'a' && character <= 'z'))
|
||||
return true;
|
||||
|
||||
// Otherwise do the full check against separators forbidden in identifiers.
|
||||
if (!IsParameterSeparator(character) && !IsDot(character) &&
|
||||
!IsQuote(character) && !IsBracket(character) &&
|
||||
!IsExpressionOperator(character) && !IsTermOperator(character) &&
|
||||
!IsWhitespace(character)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace GrammarTerminals
|
||||
} // namespace gd
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/Events/Parsers/GrammarTerminals.h"
|
||||
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
@@ -49,6 +50,11 @@ using namespace std;
|
||||
|
||||
namespace gd {
|
||||
|
||||
// By default, disallow unicode in identifiers, but this can be set to true
|
||||
// by the IDE. In the future, this will be set to true by default, keeping backward compatibility.
|
||||
// We keep it disabled by default to progressively ask users to test it in real projects.
|
||||
bool Project::allowUsageOfUnicodeIdentifierNames = false;
|
||||
|
||||
Project::Project()
|
||||
: name(_("Project")),
|
||||
version("1.0.0"),
|
||||
@@ -630,8 +636,10 @@ void Project::UnserializeFrom(const SerializerElement& element) {
|
||||
SetAdaptGameResolutionAtRuntime(
|
||||
propElement.GetBoolAttribute("adaptGameResolutionAtRuntime", false));
|
||||
SetSizeOnStartupMode(propElement.GetStringAttribute("sizeOnStartupMode", ""));
|
||||
SetAntialiasingMode(propElement.GetStringAttribute("antialiasingMode", "MSAA"));
|
||||
SetAntialisingEnabledOnMobile(propElement.GetBoolAttribute("antialisingEnabledOnMobile", false));
|
||||
SetAntialiasingMode(
|
||||
propElement.GetStringAttribute("antialiasingMode", "MSAA"));
|
||||
SetAntialisingEnabledOnMobile(
|
||||
propElement.GetBoolAttribute("antialisingEnabledOnMobile", false));
|
||||
SetProjectUuid(propElement.GetStringAttribute("projectUuid", ""));
|
||||
SetAuthor(propElement.GetChild("author", 0, "Auteur").GetValue().GetString());
|
||||
SetPackageName(propElement.GetStringAttribute("packageName"));
|
||||
@@ -887,7 +895,8 @@ void Project::SerializeTo(SerializerElement& element) const {
|
||||
adaptGameResolutionAtRuntime);
|
||||
propElement.SetAttribute("sizeOnStartupMode", sizeOnStartupMode);
|
||||
propElement.SetAttribute("antialiasingMode", antialiasingMode);
|
||||
propElement.SetAttribute("antialisingEnabledOnMobile", isAntialisingEnabledOnMobile);
|
||||
propElement.SetAttribute("antialisingEnabledOnMobile",
|
||||
isAntialisingEnabledOnMobile);
|
||||
propElement.SetAttribute("projectUuid", projectUuid);
|
||||
propElement.SetAttribute("folderProject", folderProject);
|
||||
propElement.SetAttribute("packageName", packageName);
|
||||
@@ -993,14 +1002,28 @@ void Project::SerializeTo(SerializerElement& element) const {
|
||||
externalSourceFilesElement.AddChild("sourceFile"));
|
||||
}
|
||||
|
||||
void Project::AllowUsageOfUnicodeIdentifierNames(bool enable) {
|
||||
allowUsageOfUnicodeIdentifierNames = enable;
|
||||
}
|
||||
|
||||
bool Project::IsNameSafe(const gd::String& name) {
|
||||
if (name.empty()) return false;
|
||||
|
||||
if (isdigit(name[0])) return false;
|
||||
|
||||
gd::String allowedCharacters =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
||||
return !(name.find_first_not_of(allowedCharacters) != gd::String::npos);
|
||||
if (!allowUsageOfUnicodeIdentifierNames) {
|
||||
gd::String legacyAllowedCharacters =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
||||
return !(name.find_first_not_of(legacyAllowedCharacters) != gd::String::npos);
|
||||
} else {
|
||||
for (auto character : name) {
|
||||
if (!GrammarTerminals::IsAllowedInIdentifier(character)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
gd::String Project::GetSafeName(const gd::String& name) {
|
||||
@@ -1010,12 +1033,21 @@ gd::String Project::GetSafeName(const gd::String& name) {
|
||||
|
||||
if (isdigit(name[0])) newName = "_" + newName;
|
||||
|
||||
gd::String allowedCharacters =
|
||||
gd::String legacyAllowedCharacters =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
||||
|
||||
|
||||
for (size_t i = 0;i < newName.size();++i) {
|
||||
// Note that iterating on the characters is not super efficient (O(n^2), which
|
||||
// could be avoided with an iterator), but this function is not critical for performance
|
||||
// (only used to generate a name when a user creates a new entity or rename one).
|
||||
auto character = newName[i];
|
||||
bool isAllowed =
|
||||
allowUsageOfUnicodeIdentifierNames
|
||||
? GrammarTerminals::IsAllowedInIdentifier(character)
|
||||
: legacyAllowedCharacters.find(character) != gd::String::npos;
|
||||
|
||||
// Replace all unallowed letters by an underscore.
|
||||
if (allowedCharacters.find_first_of(std::u32string(1, newName[i])) == gd::String::npos) {
|
||||
if (!isAllowed) {
|
||||
newName.replace(i, 1, '_');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -975,10 +975,24 @@ class GD_CORE_API Project : public ObjectsContainer {
|
||||
|
||||
///@}
|
||||
|
||||
/** \name Other
|
||||
/** \name Identifier names
|
||||
*/
|
||||
///@{
|
||||
|
||||
/**
|
||||
* Check if unicode names are allowed in identifier names.
|
||||
* \see IsNameSafe
|
||||
* \see GetSafeName
|
||||
*/
|
||||
static bool IsUsageOfUnicodeIdentifierNamesAllowed() { return allowUsageOfUnicodeIdentifierNames; };
|
||||
|
||||
/**
|
||||
* Set if unicode names are allowed in identifier names.
|
||||
* \see IsNameSafe
|
||||
* \see GetSafeName
|
||||
*/
|
||||
static void AllowUsageOfUnicodeIdentifierNames(bool enable);
|
||||
|
||||
/**
|
||||
* Return true if \a name is valid (can be used safely for an object,
|
||||
* behavior, events function name, etc...).
|
||||
@@ -989,7 +1003,7 @@ class GD_CORE_API Project : public ObjectsContainer {
|
||||
* Return a name, based on the one passed in parameter, that can be safely used
|
||||
* for an object, behavior, events function name, etc...
|
||||
*/
|
||||
static gd::String GetSafeName(const gd::String& name);
|
||||
static gd::String GetSafeName(const gd::String& name);
|
||||
///@}
|
||||
|
||||
/** \name External source files
|
||||
@@ -1124,6 +1138,8 @@ class GD_CORE_API Project : public ObjectsContainer {
|
||||
///< time the project was saved.
|
||||
mutable unsigned int gdBuildVersion; ///< The GD build version used the last
|
||||
///< time the project was saved.
|
||||
|
||||
static bool allowUsageOfUnicodeIdentifierNames;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
@@ -26,7 +26,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
auto &myGroup = layout1.GetObjectGroups().InsertNew("MyGroup", 0);
|
||||
myGroup.AddObject(myObject.GetName());
|
||||
|
||||
|
||||
layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 1);
|
||||
layout1.InsertNewObject(project,
|
||||
"MyExtension::FakeObjectWithUnsupportedCapability",
|
||||
@@ -340,6 +340,20 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
"Cannot find an expression with this name: Idontexist\n"
|
||||
"Double check that you've not made any typo in the name.");
|
||||
}
|
||||
{
|
||||
auto node = parser.ParseExpression("🅸🅳🅾🅽🆃🅴🆇🅸🆂🆃😄(\"hello\"");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, project, layout1, "string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 2);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"The list of parameters is not terminated. Add a closing "
|
||||
"parenthesis to end the parameters.");
|
||||
REQUIRE(validator.GetFatalErrors()[1]->GetMessage() ==
|
||||
"Cannot find an expression with this name: 🅸🅳🅾🅽🆃🅴🆇🅸🆂🆃😄\n"
|
||||
"Double check that you've not made any typo in the name.");
|
||||
}
|
||||
{
|
||||
auto node = parser.ParseExpression("=\"test\"");
|
||||
REQUIRE(node != nullptr);
|
||||
@@ -694,6 +708,17 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
"You must enter a number.");
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 0);
|
||||
}
|
||||
{
|
||||
auto node = parser.ParseExpression("ab😊d");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, project, layout1, "number");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"You must enter a number.");
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 0);
|
||||
}
|
||||
{
|
||||
auto node = parser.ParseExpression("abcd[0]");
|
||||
REQUIRE(node != nullptr);
|
||||
@@ -917,6 +942,26 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
{
|
||||
auto node = parser.ParseExpression("😅");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &identifierNode = dynamic_cast<gd::IdentifierNode &>(*node);
|
||||
REQUIRE(identifierNode.identifierName == "😅");
|
||||
|
||||
gd::ExpressionValidator validator(platform, project, layout1, "object");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
{
|
||||
auto node = parser.ParseExpression("中文");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &identifierNode = dynamic_cast<gd::IdentifierNode &>(*node);
|
||||
REQUIRE(identifierNode.identifierName == "中文");
|
||||
|
||||
gd::ExpressionValidator validator(platform, project, layout1, "object");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto node = parser.ParseExpression("Hello World 1");
|
||||
@@ -1092,6 +1137,46 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
REQUIRE(textNode.text == "2");
|
||||
REQUIRE(identifierNode.identifierName == "three");
|
||||
}
|
||||
SECTION("identifier parameter (unicode)") {
|
||||
auto node = parser.ParseExpression(
|
||||
"WhateverObject.WhateverBehavior::WhateverFunction(1, \"2\", 😄)");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
|
||||
REQUIRE(functionNode.functionName == "WhateverFunction");
|
||||
REQUIRE(functionNode.objectName == "WhateverObject");
|
||||
REQUIRE(functionNode.behaviorName == "WhateverBehavior");
|
||||
REQUIRE(functionNode.parameters.size() == 3);
|
||||
auto &numberNode =
|
||||
dynamic_cast<gd::NumberNode &>(*functionNode.parameters[0]);
|
||||
auto &textNode =
|
||||
dynamic_cast<gd::TextNode &>(*functionNode.parameters[1]);
|
||||
auto &identifierNode =
|
||||
dynamic_cast<gd::IdentifierNode &>(*functionNode.parameters[2]);
|
||||
|
||||
REQUIRE(numberNode.number == "1");
|
||||
REQUIRE(textNode.text == "2");
|
||||
REQUIRE(identifierNode.identifierName == "😄");
|
||||
}
|
||||
SECTION("unicode for object, behavior and function") {
|
||||
auto node = parser.ParseExpression(
|
||||
"🧸.🗣️::🔔(1, \"2\", 😄)");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &functionNode = dynamic_cast<gd::FunctionCallNode &>(*node);
|
||||
REQUIRE(functionNode.functionName == "🔔");
|
||||
REQUIRE(functionNode.objectName == "🧸");
|
||||
REQUIRE(functionNode.behaviorName == "🗣️");
|
||||
REQUIRE(functionNode.parameters.size() == 3);
|
||||
auto &numberNode =
|
||||
dynamic_cast<gd::NumberNode &>(*functionNode.parameters[0]);
|
||||
auto &textNode =
|
||||
dynamic_cast<gd::TextNode &>(*functionNode.parameters[1]);
|
||||
auto &identifierNode =
|
||||
dynamic_cast<gd::IdentifierNode &>(*functionNode.parameters[2]);
|
||||
|
||||
REQUIRE(numberNode.number == "1");
|
||||
REQUIRE(textNode.text == "2");
|
||||
REQUIRE(identifierNode.identifierName == "😄");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Valid function calls ('number|string' type)") {
|
||||
@@ -1669,7 +1754,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
auto &identifierNode = dynamic_cast<gd::IdentifierNode &>(*node);
|
||||
REQUIRE(identifierNode.identifierName == "myVariable");
|
||||
REQUIRE(identifierNode.childIdentifierName == "myChild");
|
||||
|
||||
|
||||
gd::ExpressionValidator validator(platform, project, layout1, "scenevar");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
@@ -1766,14 +1851,14 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
REQUIRE(identifierObject2Node.identifierName == "MyObject2");
|
||||
REQUIRE(variable1Node.identifierName == "MyVar1");
|
||||
REQUIRE(variable2Node.identifierName == "MyVar2");
|
||||
|
||||
|
||||
auto variable1ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
|
||||
platform, project, layout1, "", variable1Node);
|
||||
REQUIRE(variable1ObjectName == "MyObject1");
|
||||
auto variable2ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
|
||||
platform, project, layout1, "", variable2Node);
|
||||
REQUIRE(variable2ObjectName == "MyObject2");
|
||||
|
||||
|
||||
gd::ExpressionValidator validator(platform, project, layout1, "string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
@@ -1797,14 +1882,14 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
REQUIRE(identifierObject1Node.identifierName == "MyObject1");
|
||||
REQUIRE(variable1Node.identifierName == "MyVar1");
|
||||
REQUIRE(variable2Node.identifierName == "MyVar2");
|
||||
|
||||
|
||||
auto variable1ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
|
||||
platform, project, layout1, "", variable1Node);
|
||||
REQUIRE(variable1ObjectName == "MyObject1");
|
||||
auto variable2ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
|
||||
platform, project, layout1, "", variable2Node);
|
||||
REQUIRE(variable2ObjectName == "MyObject1");
|
||||
|
||||
|
||||
gd::ExpressionValidator validator(platform, project, layout1, "string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
@@ -1821,11 +1906,11 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
dynamic_cast<gd::IdentifierNode &>(*functionNode.parameters[0]);
|
||||
|
||||
REQUIRE(variable1Node.identifierName == "MyVar1");
|
||||
|
||||
|
||||
auto variable1ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
|
||||
platform, project, layout1, "MySpriteObject", variable1Node);
|
||||
REQUIRE(variable1ObjectName == "MySpriteObject");
|
||||
|
||||
|
||||
gd::ExpressionValidator validator(platform, project, layout1, "number");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
@@ -1843,11 +1928,11 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
REQUIRE(variable1Node.identifierName == "MyVar1");
|
||||
REQUIRE(variable1Node.childIdentifierName == "MyChild");
|
||||
|
||||
|
||||
auto variable1ObjectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
|
||||
platform, project, layout1, "MySpriteObject", variable1Node);
|
||||
REQUIRE(variable1ObjectName == "MySpriteObject");
|
||||
|
||||
|
||||
gd::ExpressionValidator validator(platform, project, layout1, "number");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
|
||||
@@ -1352,7 +1352,7 @@ void MetadataDeclarationHelper::DeclareEventsFunctionParameters(
|
||||
}
|
||||
|
||||
gd::String MetadataDeclarationHelper::GetExtensionCodeNamespacePrefix(
|
||||
const gd::EventsFunctionsExtension eventsFunctionsExtension) {
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension) {
|
||||
return "gdjs.evtsExt__" + EventsCodeNameMangler::GetMangledName(
|
||||
eventsFunctionsExtension.GetName());
|
||||
}
|
||||
|
||||
@@ -66,10 +66,30 @@ public:
|
||||
const gd::EventsBasedObject &eventsBasedObject,
|
||||
std::map<gd::String, gd::String> &objectMethodMangledNames);
|
||||
|
||||
/** Generate the namespace prefix for an extension. */
|
||||
static gd::String GetExtensionCodeNamespacePrefix(
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension);
|
||||
|
||||
/** Generate the fully qualified name for a free function. */
|
||||
static gd::String GetFreeFunctionCodeName(
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::EventsFunction &eventsFunction);
|
||||
|
||||
/** Generate the namespace for a free function. */
|
||||
static gd::String
|
||||
GetFreeFunctionCodeNamespace(const gd::EventsFunction &eventsFunction,
|
||||
const gd::String &codeNamespacePrefix);
|
||||
|
||||
/** Generate the namespace for a behavior function. */
|
||||
static gd::String GetBehaviorFunctionCodeNamespace(
|
||||
const gd::EventsBasedBehavior &eventsBasedBehavior,
|
||||
const gd::String &codeNamespacePrefix);
|
||||
|
||||
/** Generate the namespace for an object function. */
|
||||
static gd::String
|
||||
GetObjectFunctionCodeNamespace(const gd::EventsBasedObject &eventsBasedObject,
|
||||
const gd::String &codeNamespacePrefix);
|
||||
|
||||
/**
|
||||
* Declare an extension from an events based extension.
|
||||
*/
|
||||
@@ -292,24 +312,6 @@ private:
|
||||
gd::MultipleInstructionMetadata &multipleInstructionMetadata,
|
||||
const int userDefinedFirstParameterIndex);
|
||||
|
||||
static gd::String GetExtensionCodeNamespacePrefix(
|
||||
const gd::EventsFunctionsExtension eventsFunctionsExtension);
|
||||
|
||||
/** Generate the namespace for a free function. */
|
||||
static gd::String
|
||||
GetFreeFunctionCodeNamespace(const gd::EventsFunction &eventsFunction,
|
||||
const gd::String &codeNamespacePrefix);
|
||||
|
||||
/** Generate the namespace for a behavior function. */
|
||||
static gd::String GetBehaviorFunctionCodeNamespace(
|
||||
const gd::EventsBasedBehavior &eventsBasedBehavior,
|
||||
const gd::String &codeNamespacePrefix);
|
||||
|
||||
/** Generate the namespace for an object function. */
|
||||
static gd::String
|
||||
GetObjectFunctionCodeNamespace(const gd::EventsBasedObject &eventsBasedObject,
|
||||
const gd::String &codeNamespacePrefix);
|
||||
|
||||
static gd::String RemoveTrailingDot(const gd::String &description);
|
||||
|
||||
static gd::String
|
||||
|
||||
@@ -521,6 +521,8 @@ interface Project {
|
||||
void SerializeTo([Ref] SerializerElement element);
|
||||
void UnserializeFrom([Const, Ref] SerializerElement element);
|
||||
|
||||
boolean STATIC_IsUsageOfUnicodeIdentifierNamesAllowed();
|
||||
void STATIC_AllowUsageOfUnicodeIdentifierNames(boolean enable);
|
||||
boolean STATIC_IsNameSafe([Const] DOMString name);
|
||||
[Const, Value] DOMString STATIC_GetSafeName([Const] DOMString name);
|
||||
|
||||
@@ -1291,7 +1293,7 @@ interface AbstractFunctionMetadata {
|
||||
[Ref] AbstractFunctionMetadata SetDefaultValue([Const] DOMString defaultValue);
|
||||
[Ref] AbstractFunctionMetadata SetParameterLongDescription([Const] DOMString longDescription);
|
||||
[Ref] AbstractFunctionMetadata SetParameterExtraInfo([Const] DOMString extraInfo);
|
||||
|
||||
|
||||
[Ref] AbstractFunctionMetadata SetHidden();
|
||||
[Ref] AbstractFunctionMetadata SetPrivate();
|
||||
|
||||
@@ -1354,7 +1356,7 @@ interface InstructionMetadata {
|
||||
[Ref] InstructionMetadata MarkAsComplex();
|
||||
|
||||
[Ref] InstructionMetadata GetCodeExtraInformation();
|
||||
|
||||
|
||||
[Ref] InstructionMetadata SetFunctionName([Const] DOMString functionName_);
|
||||
[Ref] InstructionMetadata SetAsyncFunctionName([Const] DOMString functionName_);
|
||||
[Const, Ref] DOMString GetFunctionName();
|
||||
@@ -1415,7 +1417,7 @@ interface ExpressionMetadata {
|
||||
[Const, Ref] DOMString GetRequiredBaseObjectCapability();
|
||||
|
||||
[Ref] ExpressionMetadata GetCodeExtraInformation();
|
||||
|
||||
|
||||
[Ref] ExpressionMetadata SetFunctionName([Const] DOMString functionName);
|
||||
[Const, Ref] DOMString GetFunctionName();
|
||||
[Ref] ExpressionMetadata SetStatic();
|
||||
@@ -3397,7 +3399,7 @@ interface JsCodeEvent {
|
||||
[Prefix="gdjs::"]
|
||||
interface MetadataDeclarationHelper {
|
||||
void MetadataDeclarationHelper();
|
||||
|
||||
|
||||
void STATIC_DeclareExtension(
|
||||
[Ref] PlatformExtension extension,
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension);
|
||||
@@ -3422,10 +3424,25 @@ interface MetadataDeclarationHelper {
|
||||
[Const, Ref] EventsBasedObject eventsBasedObject,
|
||||
[Ref] MapStringString objectMethodMangledNames);
|
||||
|
||||
[Const, Value] DOMString STATIC_GetExtensionCodeNamespacePrefix(
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension);
|
||||
|
||||
[Const, Value] DOMString STATIC_GetFreeFunctionCodeName(
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
[Const, Ref] EventsFunction eventsFunction);
|
||||
|
||||
[Const, Value] DOMString STATIC_GetFreeFunctionCodeNamespace(
|
||||
[Const, Ref] EventsFunction eventsFunction,
|
||||
[Const] DOMString codeNamespacePrefix);
|
||||
|
||||
[Const, Value] DOMString STATIC_GetBehaviorFunctionCodeNamespace(
|
||||
[Const, Ref] EventsBasedBehavior eventsBasedBehavior,
|
||||
[Const] DOMString codeNamespacePrefix);
|
||||
|
||||
[Const, Value] DOMString STATIC_GetObjectFunctionCodeNamespace(
|
||||
[Const, Ref] EventsBasedObject eventsBasedObject,
|
||||
[Const] DOMString codeNamespacePrefix);
|
||||
|
||||
boolean STATIC_IsBehaviorLifecycleEventsFunction([Const] DOMString functionName);
|
||||
boolean STATIC_IsObjectLifecycleEventsFunction([Const] DOMString functionName);
|
||||
boolean STATIC_IsExtensionLifecycleEventsFunction([Const] DOMString functionName);
|
||||
|
||||
@@ -522,6 +522,8 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
|
||||
#define STATIC_CreateNewGDJSProject CreateNewGDJSProject
|
||||
#define STATIC_InitializePlatforms InitializePlatforms
|
||||
#define STATIC_IsUsageOfUnicodeIdentifierNamesAllowed IsUsageOfUnicodeIdentifierNamesAllowed
|
||||
#define STATIC_AllowUsageOfUnicodeIdentifierNames AllowUsageOfUnicodeIdentifierNames
|
||||
#define STATIC_IsNameSafe IsNameSafe
|
||||
#define STATIC_GetSafeName GetSafeName
|
||||
#define STATIC_ToJSON ToJSON
|
||||
@@ -698,7 +700,11 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
#define STATIC_DeclareExtension DeclareExtension
|
||||
#define STATIC_GenerateBehaviorMetadata GenerateBehaviorMetadata
|
||||
#define STATIC_GenerateObjectMetadata GenerateObjectMetadata
|
||||
#define STATIC_GetExtensionCodeNamespacePrefix GetExtensionCodeNamespacePrefix
|
||||
#define STATIC_GetFreeFunctionCodeName GetFreeFunctionCodeName
|
||||
#define STATIC_GetFreeFunctionCodeNamespace GetFreeFunctionCodeNamespace
|
||||
#define STATIC_GetBehaviorFunctionCodeNamespace GetBehaviorFunctionCodeNamespace
|
||||
#define STATIC_GetObjectFunctionCodeNamespace GetObjectFunctionCodeNamespace
|
||||
#define STATIC_IsBehaviorLifecycleEventsFunction IsBehaviorLifecycleEventsFunction
|
||||
#define STATIC_IsObjectLifecycleEventsFunction IsObjectLifecycleEventsFunction
|
||||
#define STATIC_IsExtensionLifecycleEventsFunction IsExtensionLifecycleEventsFunction
|
||||
|
||||
@@ -99,7 +99,8 @@ describe('libGD.js', function () {
|
||||
expect(project.hasExternalLayoutNamed('My layout')).toBe(false);
|
||||
});
|
||||
|
||||
it('should validate object names', function () {
|
||||
it('should validate object names (legacy)', function () {
|
||||
gd.Project.allowUsageOfUnicodeIdentifierNames(false);
|
||||
expect(gd.Project.isNameSafe('ThisNameIs_Ok_123')).toBe(true);
|
||||
expect(gd.Project.isNameSafe('ThisName IsNot_Ok_123')).toBe(false);
|
||||
expect(gd.Project.isNameSafe('ThisNameIsNot_Ok!')).toBe(false);
|
||||
@@ -113,6 +114,25 @@ describe('libGD.js', function () {
|
||||
expect(gd.Project.getSafeName('9')).toBe('_9');
|
||||
});
|
||||
|
||||
it('should validate object names (unicode)', function () {
|
||||
gd.Project.allowUsageOfUnicodeIdentifierNames(true);
|
||||
expect(gd.Project.isNameSafe('ThisNameIs_Ok_123')).toBe(true);
|
||||
expect(gd.Project.isNameSafe('ThisNameIs_👍_123')).toBe(true);
|
||||
expect(gd.Project.isNameSafe('ThisName IsNot_Ok_123')).toBe(false);
|
||||
expect(gd.Project.isNameSafe('ThisName()IsNot_Ok_123')).toBe(false);
|
||||
expect(gd.Project.isNameSafe('ThisNameIsNot_Ok!')).toBe(false);
|
||||
expect(gd.Project.isNameSafe('1ThisNameIsNot_Ok_123')).toBe(false);
|
||||
expect(gd.Project.getSafeName('ThisNameIs_Ok_123')).toBe('ThisNameIs_Ok_123');
|
||||
expect(gd.Project.getSafeName('ThisNameIs_👍_123')).toBe('ThisNameIs_👍_123');
|
||||
expect(gd.Project.getSafeName('ThisName IsNot_Ok_123')).toBe('ThisName_IsNot_Ok_123');
|
||||
expect(gd.Project.getSafeName('ThisName()IsNot_Ok_123')).toBe('ThisName__IsNot_Ok_123');
|
||||
expect(gd.Project.getSafeName('ThisNameIsNot_Ok!')).toBe('ThisNameIsNot_Ok_');
|
||||
expect(gd.Project.getSafeName('1ThisNameIsNot_Ok_123')).toBe('_1ThisNameIsNot_Ok_123');
|
||||
expect(gd.Project.getSafeName('官话 name')).toBe('官话_name');
|
||||
expect(gd.Project.getSafeName('')).toBe('Unnamed');
|
||||
expect(gd.Project.getSafeName('9')).toBe('_9');
|
||||
});
|
||||
|
||||
it('should have a list of extensions', function () {
|
||||
expect(
|
||||
gd.UsedExtensionsFinder.scanProject(project)
|
||||
|
||||
@@ -45,16 +45,16 @@ describe('MetadataDeclarationHelper', () => {
|
||||
project.delete();
|
||||
});
|
||||
|
||||
it('can create metadata for free actions with an underscore', () => {
|
||||
it('can create metadata for free actions with an underscore and unicode characters', () => {
|
||||
const extension = new gd.PlatformExtension();
|
||||
const project = new gd.Project();
|
||||
|
||||
const eventExtension = project.insertNewEventsFunctionsExtension(
|
||||
'MyExtension',
|
||||
'My🧩Extension',
|
||||
0
|
||||
);
|
||||
const eventFunction = eventExtension.insertNewEventsFunction(
|
||||
'My_Function',
|
||||
'My_📝Function',
|
||||
0
|
||||
);
|
||||
eventFunction.setFunctionType(gd.EventsFunction.Action);
|
||||
@@ -68,10 +68,10 @@ describe('MetadataDeclarationHelper', () => {
|
||||
);
|
||||
metadataDeclarationHelper.delete();
|
||||
|
||||
expect(extension.getAllActions().has('My_Function')).toBe(true);
|
||||
const action = extension.getAllActions().get('My_Function');
|
||||
expect(extension.getAllActions().has('My_📝Function')).toBe(true);
|
||||
const action = extension.getAllActions().get('My_📝Function');
|
||||
expect(action.getFunctionName()).toBe(
|
||||
'gdjs.evtsExt__MyExtension__My_Function.func'
|
||||
'gdjs.evtsExt__My_129513Extension__My__128221Function.func'
|
||||
);
|
||||
|
||||
extension.delete();
|
||||
|
||||
@@ -5,7 +5,11 @@ declare class gdMetadataDeclarationHelper {
|
||||
generateFreeFunctionMetadata(project: gdProject, extension: gdPlatformExtension, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsFunction: gdEventsFunction): gdAbstractFunctionMetadata;
|
||||
static generateBehaviorMetadata(project: gdProject, extension: gdPlatformExtension, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, behaviorMethodMangledNames: gdMapStringString): gdBehaviorMetadata;
|
||||
static generateObjectMetadata(project: gdProject, extension: gdPlatformExtension, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, objectMethodMangledNames: gdMapStringString): gdObjectMetadata;
|
||||
static getExtensionCodeNamespacePrefix(eventsFunctionsExtension: gdEventsFunctionsExtension): string;
|
||||
static getFreeFunctionCodeName(eventsFunctionsExtension: gdEventsFunctionsExtension, eventsFunction: gdEventsFunction): string;
|
||||
static getFreeFunctionCodeNamespace(eventsFunction: gdEventsFunction, codeNamespacePrefix: string): string;
|
||||
static getBehaviorFunctionCodeNamespace(eventsBasedBehavior: gdEventsBasedBehavior, codeNamespacePrefix: string): string;
|
||||
static getObjectFunctionCodeNamespace(eventsBasedObject: gdEventsBasedObject, codeNamespacePrefix: string): string;
|
||||
static isBehaviorLifecycleEventsFunction(functionName: string): boolean;
|
||||
static isObjectLifecycleEventsFunction(functionName: string): boolean;
|
||||
static isExtensionLifecycleEventsFunction(functionName: string): boolean;
|
||||
|
||||
@@ -104,6 +104,8 @@ declare class gdProject extends gdObjectsContainer {
|
||||
exposeResources(worker: gdArbitraryResourceWorker): void;
|
||||
serializeTo(element: gdSerializerElement): void;
|
||||
unserializeFrom(element: gdSerializerElement): void;
|
||||
static isUsageOfUnicodeIdentifierNamesAllowed(): boolean;
|
||||
static allowUsageOfUnicodeIdentifierNames(enable: boolean): void;
|
||||
static isNameSafe(name: string): boolean;
|
||||
static getSafeName(name: string): string;
|
||||
getTypeOfBehavior(layout: gdLayout, name: string, searchInGroups: boolean): string;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import { mapVector, mapFor } from '../Utils/MapFor';
|
||||
import { caseSensitiveSlug } from '../Utils/CaseSensitiveSlug';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
@@ -32,56 +31,10 @@ type OptionsForGeneration = {
|
||||
};
|
||||
|
||||
type CodeGenerationContext = {|
|
||||
codeNamespacePrefix: string,
|
||||
codeNamespacePrefix: string, // TODO: could this reworked to avoid this entirely?
|
||||
extensionIncludeFiles: Array<string>,
|
||||
|};
|
||||
|
||||
const mangleName = (name: string) => {
|
||||
return caseSensitiveSlug(name, '_', []);
|
||||
};
|
||||
|
||||
const getExtensionCodeNamespacePrefix = (
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension
|
||||
) => {
|
||||
return 'gdjs.evtsExt__' + mangleName(eventsFunctionsExtension.getName());
|
||||
};
|
||||
|
||||
/** Generate the namespace for a free function. */
|
||||
const getFreeFunctionCodeNamespace = (
|
||||
eventsFunction: gdEventsFunction,
|
||||
codeNamespacePrefix: string
|
||||
) => {
|
||||
return codeNamespacePrefix + '__' + mangleName(eventsFunction.getName());
|
||||
};
|
||||
|
||||
export const getFreeFunctionCodeName = (
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension,
|
||||
eventsFunction: gdEventsFunction
|
||||
) => {
|
||||
return (
|
||||
getFreeFunctionCodeNamespace(
|
||||
eventsFunction,
|
||||
getExtensionCodeNamespacePrefix(eventsFunctionsExtension)
|
||||
) + '.func'
|
||||
);
|
||||
};
|
||||
|
||||
/** Generate the namespace for a behavior function. */
|
||||
const getBehaviorFunctionCodeNamespace = (
|
||||
eventsBasedBehavior: gdEventsBasedBehavior,
|
||||
codeNamespacePrefix: string
|
||||
) => {
|
||||
return codeNamespacePrefix + '__' + mangleName(eventsBasedBehavior.getName());
|
||||
};
|
||||
|
||||
/** Generate the namespace for an object function. */
|
||||
const getObjectFunctionCodeNamespace = (
|
||||
eventsBasedObject: gdEventsBasedObject,
|
||||
codeNamespacePrefix: string
|
||||
) => {
|
||||
return codeNamespacePrefix + '__' + mangleName(eventsBasedObject.getName());
|
||||
};
|
||||
|
||||
/**
|
||||
* Load all events functions of a project in extensions
|
||||
*/
|
||||
@@ -160,18 +113,15 @@ const loadProjectEventsFunctionsExtension = (
|
||||
const getExtensionIncludeFiles = (
|
||||
project: gdProject,
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension,
|
||||
options: Options,
|
||||
codeNamespacePrefix: string
|
||||
options: Options
|
||||
): Array<string> => {
|
||||
return mapFor(0, eventsFunctionsExtension.getEventsFunctionsCount(), i => {
|
||||
const eventsFunction = eventsFunctionsExtension.getEventsFunctionAt(i);
|
||||
|
||||
const codeNamespace = getFreeFunctionCodeNamespace(
|
||||
eventsFunction,
|
||||
codeNamespacePrefix
|
||||
const functionName = gd.MetadataDeclarationHelper.getFreeFunctionCodeName(
|
||||
eventsFunctionsExtension,
|
||||
eventsFunction
|
||||
);
|
||||
// TODO Use getFreeFunctionCodeName.
|
||||
const functionName = codeNamespace + '.func';
|
||||
|
||||
return options.eventsFunctionCodeWriter.getIncludeFileFor(functionName);
|
||||
}).filter(Boolean);
|
||||
@@ -191,14 +141,14 @@ const generateEventsFunctionExtension = (
|
||||
eventsFunctionsExtension
|
||||
);
|
||||
|
||||
const codeNamespacePrefix =
|
||||
'gdjs.evtsExt__' + mangleName(eventsFunctionsExtension.getName());
|
||||
const codeNamespacePrefix = gd.MetadataDeclarationHelper.getExtensionCodeNamespacePrefix(
|
||||
eventsFunctionsExtension
|
||||
);
|
||||
|
||||
const extensionIncludeFiles = getExtensionIncludeFiles(
|
||||
project,
|
||||
eventsFunctionsExtension,
|
||||
options,
|
||||
codeNamespacePrefix
|
||||
options
|
||||
);
|
||||
const codeGenerationContext = {
|
||||
codeNamespacePrefix,
|
||||
@@ -276,14 +226,14 @@ const generateEventsFunctionExtensionMetadata = (
|
||||
eventsFunctionsExtension
|
||||
);
|
||||
|
||||
const codeNamespacePrefix =
|
||||
'gdjs.evtsExt__' + mangleName(eventsFunctionsExtension.getName());
|
||||
const codeNamespacePrefix = gd.MetadataDeclarationHelper.getExtensionCodeNamespacePrefix(
|
||||
eventsFunctionsExtension
|
||||
);
|
||||
|
||||
const extensionIncludeFiles = getExtensionIncludeFiles(
|
||||
project,
|
||||
eventsFunctionsExtension,
|
||||
options,
|
||||
codeNamespacePrefix
|
||||
options
|
||||
);
|
||||
const codeGenerationContext = {
|
||||
codeNamespacePrefix,
|
||||
@@ -369,7 +319,7 @@ const generateFreeFunction = (
|
||||
const eventsFunctionsExtensionCodeGenerator = new gd.EventsFunctionsExtensionCodeGenerator(
|
||||
project
|
||||
);
|
||||
const codeNamespace = getFreeFunctionCodeNamespace(
|
||||
const codeNamespace = gd.MetadataDeclarationHelper.getFreeFunctionCodeNamespace(
|
||||
eventsFunction,
|
||||
codeGenerationContext.codeNamespacePrefix
|
||||
);
|
||||
@@ -398,8 +348,10 @@ const generateFreeFunction = (
|
||||
eventsFunctionsExtensionCodeGenerator.delete();
|
||||
metadataDeclarationHelper.delete();
|
||||
|
||||
// TODO Implement an helper function for free function names.
|
||||
const functionName = codeNamespace + '.func';
|
||||
const functionName = gd.MetadataDeclarationHelper.getFreeFunctionCodeName(
|
||||
eventsFunctionsExtension,
|
||||
eventsFunction
|
||||
);
|
||||
return options.eventsFunctionCodeWriter
|
||||
.writeFunctionCode(functionName, code)
|
||||
.then(() => {});
|
||||
@@ -476,7 +428,7 @@ function generateBehavior(
|
||||
|
||||
// Generate code for the behavior and its methods
|
||||
if (!options.skipCodeGeneration) {
|
||||
const codeNamespace = getBehaviorFunctionCodeNamespace(
|
||||
const codeNamespace = gd.MetadataDeclarationHelper.getBehaviorFunctionCodeNamespace(
|
||||
eventsBasedBehavior,
|
||||
codeGenerationContext.codeNamespacePrefix
|
||||
);
|
||||
@@ -538,7 +490,7 @@ function generateBehaviorMetadata(
|
||||
behaviorMethodMangledNames
|
||||
);
|
||||
|
||||
const codeNamespace = getBehaviorFunctionCodeNamespace(
|
||||
const codeNamespace = gd.MetadataDeclarationHelper.getBehaviorFunctionCodeNamespace(
|
||||
eventsBasedBehavior,
|
||||
codeGenerationContext.codeNamespacePrefix
|
||||
);
|
||||
@@ -578,7 +530,7 @@ function generateObject(
|
||||
|
||||
// Generate code for the object and its methods
|
||||
if (!options.skipCodeGeneration) {
|
||||
const codeNamespace = getObjectFunctionCodeNamespace(
|
||||
const codeNamespace = gd.MetadataDeclarationHelper.getObjectFunctionCodeNamespace(
|
||||
eventsBasedObject,
|
||||
codeGenerationContext.codeNamespacePrefix
|
||||
);
|
||||
@@ -640,7 +592,7 @@ function generateObjectMetadata(
|
||||
objectMethodMangledNames
|
||||
);
|
||||
|
||||
const codeNamespace = getObjectFunctionCodeNamespace(
|
||||
const codeNamespace = gd.MetadataDeclarationHelper.getObjectFunctionCodeNamespace(
|
||||
eventsBasedObject,
|
||||
codeGenerationContext.codeNamespacePrefix
|
||||
);
|
||||
|
||||
@@ -212,6 +212,7 @@ export type PreferencesValues = {|
|
||||
newProjectsDefaultFolder: string,
|
||||
newProjectsDefaultStorageProviderName: string,
|
||||
useShortcutToClosePreviewWindow: boolean,
|
||||
allowUsageOfUnicodeIdentifierNames: boolean,
|
||||
|};
|
||||
|
||||
/**
|
||||
@@ -289,6 +290,7 @@ export type Preferences = {|
|
||||
|}) => ?InAppTutorialUserProgress,
|
||||
setNewProjectsDefaultFolder: (newProjectsDefaultFolder: string) => void,
|
||||
setUseShortcutToClosePreviewWindow: (enabled: boolean) => void,
|
||||
setAllowUsageOfUnicodeIdentifierNames: (enabled: boolean) => void,
|
||||
|};
|
||||
|
||||
export const initialPreferences = {
|
||||
@@ -333,6 +335,7 @@ export const initialPreferences = {
|
||||
newProjectsDefaultFolder: app ? findDefaultFolder(app) : '',
|
||||
newProjectsDefaultStorageProviderName: 'Cloud',
|
||||
useShortcutToClosePreviewWindow: true,
|
||||
allowUsageOfUnicodeIdentifierNames: false,
|
||||
},
|
||||
setLanguage: () => {},
|
||||
setThemeName: () => {},
|
||||
@@ -389,6 +392,7 @@ export const initialPreferences = {
|
||||
setNewProjectsDefaultFolder: () => {},
|
||||
setNewProjectsDefaultStorageProviderName: () => {},
|
||||
setUseShortcutToClosePreviewWindow: () => {},
|
||||
setAllowUsageOfUnicodeIdentifierNames: () => {},
|
||||
};
|
||||
|
||||
const PreferencesContext = React.createContext<Preferences>(initialPreferences);
|
||||
|
||||
@@ -69,6 +69,7 @@ const PreferencesDialog = ({ i18n, onClose }: Props) => {
|
||||
setUse3DEditor,
|
||||
setNewProjectsDefaultFolder,
|
||||
setUseShortcutToClosePreviewWindow,
|
||||
setAllowUsageOfUnicodeIdentifierNames,
|
||||
} = React.useContext(PreferencesContext);
|
||||
|
||||
const initialUse3DEditor = React.useRef<boolean>(values.use3DEditor);
|
||||
@@ -354,6 +355,19 @@ const PreferencesDialog = ({ i18n, onClose }: Props) => {
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
<Toggle
|
||||
onToggle={(e, check) =>
|
||||
setAllowUsageOfUnicodeIdentifierNames(check)
|
||||
}
|
||||
toggled={values.allowUsageOfUnicodeIdentifierNames}
|
||||
labelPosition="right"
|
||||
label={
|
||||
<Trans>
|
||||
Allow unicode characters (non English languages and emojis) in
|
||||
object, behavior and other names (experimental)
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
<Toggle
|
||||
onToggle={(e, check) => setUse3DEditor(check)}
|
||||
toggled={values.use3DEditor}
|
||||
|
||||
@@ -61,6 +61,12 @@ export const loadPreferencesFromLocalStorage = (): ?PreferencesValues => {
|
||||
values.themeName = 'Blue Dark';
|
||||
}
|
||||
|
||||
// Synchronize the global state(s).
|
||||
const gd: libGDevelop = global.gd;
|
||||
gd.Project.allowUsageOfUnicodeIdentifierNames(
|
||||
values.allowUsageOfUnicodeIdentifierNames
|
||||
);
|
||||
|
||||
return values;
|
||||
} catch (e) {
|
||||
return null;
|
||||
@@ -159,6 +165,9 @@ export default class PreferencesProvider extends React.Component<Props, State> {
|
||||
setUseShortcutToClosePreviewWindow: this._setUseShortcutToClosePreviewWindow.bind(
|
||||
this
|
||||
),
|
||||
setAllowUsageOfUnicodeIdentifierNames: this._setAllowUsageOfUnicodeIdentifierNames.bind(
|
||||
this
|
||||
),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@@ -819,6 +828,21 @@ export default class PreferencesProvider extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
_setAllowUsageOfUnicodeIdentifierNames(enable: boolean) {
|
||||
const gd: libGDevelop = global.gd;
|
||||
gd.Project.allowUsageOfUnicodeIdentifierNames(enable);
|
||||
|
||||
this.setState(
|
||||
state => ({
|
||||
values: {
|
||||
...state.values,
|
||||
allowUsageOfUnicodeIdentifierNames: enable,
|
||||
},
|
||||
}),
|
||||
() => this._persistValuesToLocalStorage(this.state)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<PreferencesContext.Provider value={this.state}>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// @flow
|
||||
|
||||
/*
|
||||
* Case Sensitive Slug method
|
||||
* Inspired by Aaron Stannard module (https://github.com/Aaronontheweb/node-slugs)
|
||||
*/
|
||||
export const caseSensitiveSlug = (
|
||||
incString: string,
|
||||
separator?: string,
|
||||
preserved?: Array<string>
|
||||
): string => {
|
||||
var p = ['.', '=', '-'];
|
||||
var s = '-';
|
||||
|
||||
if (typeof preserved != 'undefined') {
|
||||
p = preserved;
|
||||
}
|
||||
|
||||
if (typeof separator != 'undefined') {
|
||||
s = separator;
|
||||
}
|
||||
|
||||
return incString
|
||||
.replace(/ü/g, 'ue')
|
||||
.replace(/ä/g, 'ae')
|
||||
.replace(/ö/g, 'oe')
|
||||
.replace(/ß/g, 'ss')
|
||||
.replace(new RegExp('[' + p.join('') + ']', 'g'), ' ') // replace preserved characters with spaces
|
||||
.replace(/-{2,}/g, ' ') // remove duplicate spaces
|
||||
.replace(/^\s\s*/, '')
|
||||
.replace(/\s\s*$/, '') // trim both sides of string
|
||||
.replace(/[^\w ]/gi, '') // replaces all non-alphanumeric with empty string
|
||||
.replace(/[ ]/gi, s); // Convert spaces to dashes
|
||||
};
|
||||
Reference in New Issue
Block a user