/*!
Returns true if \a name refers to a signal property, false otherwise.
- Signal property names are those that start with "on", followed by a capital
- letter.
+ Signal property names are those that start with "on", followed by a first
+ character which is either a capital letter or one or more underscores followed
+ by a capital letter, which is then followed by other allowed characters.
+
+ Note that although ECMA-262r3 supports dollarsigns and escaped unicode
+ character codes in property names, for simplicity and performance reasons
+ QML only supports letters, numbers and underscores.
*/
bool QDeclarativeCompiler::isSignalPropertyName(const QByteArray &name)
{
- return name.length() >= 3 && name.startsWith("on") &&
- 'A' <= name.at(2) && 'Z' >= name.at(2);
+ if (name.length() < 3) return false;
+ if (!name.startsWith("on")) return false;
+ int ns = name.size();
+ for (int i = 2; i < ns; ++i) {
+ char curr = name.at(i);
+ if (curr == '_') continue;
+ if (curr >= 'A' && curr <= 'Z') return true;
+ return false;
+ }
+ return false; // consists solely of underscores - invalid.
}
/*!
QByteArray name = prop->name;
Q_ASSERT(name.startsWith("on"));
name = name.mid(2);
- if(name[0] >= 'A' && name[0] <= 'Z')
- name[0] = name[0] - 'A' + 'a';
+
+ // Note that the property name could start with any alpha or '_' or '$' character,
+ // so we need to do the lower-casing of the first alpha character.
+ for (int firstAlphaIndex = 0; firstAlphaIndex < name.size(); ++firstAlphaIndex) {
+ if (name[firstAlphaIndex] >= 'A' && name[firstAlphaIndex] <= 'Z') {
+ name[firstAlphaIndex] = name[firstAlphaIndex] - 'A' + 'a';
+ break;
+ }
+ }
bool notInRevision = false;
int sigIdx = indexOfSignal(obj, name, ¬InRevision);
--- /dev/null
+import QtQuick 1.0
+
+Item {
+ property int changeCount: 0
+
+ property bool _nameWithUnderscore: false
+
+ // this should error, since the first alpha isn't capitalised.
+ on_nameWithUnderscoreChanged: {
+ changeCount = changeCount + 2;
+ }
+}
--- /dev/null
+import QtQuick 1.0
+
+Item {
+ property int changeCount: 0
+
+ property bool ____nameWithUnderscores: false
+
+ // this should error, since the first alpha isn't capitalised
+ on____nameWithUnderscoresChanged: {
+ changeCount = changeCount + 3;
+ }
+}
--- /dev/null
+import QtQuick 1.0
+
+Item {
+ property int changeCount: 0
+
+ // invalid property name - we don't allow $
+ property bool $nameWithDollarsign: false
+
+ on$NameWithDollarsignChanged: {
+ changeCount = changeCount + 4;
+ }
+}
--- /dev/null
+import QtQuick 1.0
+
+Item {
+ property int changeCount: 0
+
+ property bool _6nameWithUnderscoreNumber: false
+
+ // invalid property name - the first character after an underscore must be a letter
+ on_6NameWithUnderscoreNumberChanged: {
+ changeCount = changeCount + 3;
+ }
+}
--- /dev/null
+import QtQuick 1.0
+
+Item {
+ property int changeCount: 0
+
+ property bool normalName: false
+ property bool _nameWithUnderscore: false
+ property bool ____nameWithUnderscores: false
+
+ onNormalNameChanged: {
+ changeCount = changeCount + 1;
+ }
+
+ on_NameWithUnderscoreChanged: {
+ changeCount = changeCount + 2;
+ }
+
+ on____NameWithUnderscoresChanged: {
+ changeCount = changeCount + 3;
+ }
+
+ Component.onCompleted: {
+ normalName = true;
+ _nameWithUnderscore = true;
+ ____nameWithUnderscores = true;
+ }
+}
void moduleApi();
void importScripts();
void scarceResources();
+ void propertyChangeSlots();
void bug1();
void bug2();
delete object;
}
+void tst_qdeclarativeecmascript::propertyChangeSlots()
+{
+ // ensure that allowable property names are allowed and onPropertyNameChanged slots are generated correctly.
+ QDeclarativeComponent component(&engine, TEST_FILE("changeslots/propertyChangeSlots.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ delete object;
+
+ // ensure that invalid property names fail properly.
+ QTest::ignoreMessage(QtWarningMsg, "QDeclarativeComponent: Component is not ready");
+ QDeclarativeComponent e1(&engine, TEST_FILE("changeslots/propertyChangeSlotErrors.1.qml"));
+ QString expectedErrorString = e1.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on_nameWithUnderscoreChanged\"");
+ QCOMPARE(e1.errors().at(0).toString(), expectedErrorString);
+ object = e1.create();
+ QVERIFY(object == 0);
+ delete object;
+
+ QTest::ignoreMessage(QtWarningMsg, "QDeclarativeComponent: Component is not ready");
+ QDeclarativeComponent e2(&engine, TEST_FILE("changeslots/propertyChangeSlotErrors.2.qml"));
+ expectedErrorString = e2.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on____nameWithUnderscoresChanged\"");
+ QCOMPARE(e2.errors().at(0).toString(), expectedErrorString);
+ object = e2.create();
+ QVERIFY(object == 0);
+ delete object;
+
+ QTest::ignoreMessage(QtWarningMsg, "QDeclarativeComponent: Component is not ready");
+ QDeclarativeComponent e3(&engine, TEST_FILE("changeslots/propertyChangeSlotErrors.3.qml"));
+ expectedErrorString = e3.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on$NameWithDollarsignChanged\"");
+ QCOMPARE(e3.errors().at(0).toString(), expectedErrorString);
+ object = e3.create();
+ QVERIFY(object == 0);
+ delete object;
+
+ QTest::ignoreMessage(QtWarningMsg, "QDeclarativeComponent: Component is not ready");
+ QDeclarativeComponent e4(&engine, TEST_FILE("changeslots/propertyChangeSlotErrors.4.qml"));
+ expectedErrorString = e4.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on_6NameWithUnderscoreNumberChanged\"");
+ QCOMPARE(e4.errors().at(0).toString(), expectedErrorString);
+ object = e4.create();
+ QVERIFY(object == 0);
+ delete object;
+}
+
// Test that assigning a null object works
// Regressed with: df1788b4dbbb2826ae63f26bdf166342595343f4
void tst_qdeclarativeecmascript::nullObjectBinding()