Tighten XCUI schema consistency rules
This commit is contained in:
@@ -74,7 +74,7 @@ Current gap:
|
|||||||
- `XCNewEditor` Debug target builds successfully
|
- `XCNewEditor` Debug target builds successfully
|
||||||
- `core_ui_tests`: `14/14`
|
- `core_ui_tests`: `14/14`
|
||||||
- `core_ui_style_tests`: `5/5`
|
- `core_ui_style_tests`: `5/5`
|
||||||
- `ui_resource_tests`: `7/7`
|
- `ui_resource_tests`: `11/11`
|
||||||
- `editor_tests` targeted bridge smoke: `3/3`
|
- `editor_tests` targeted bridge smoke: `3/3`
|
||||||
|
|
||||||
## Landed This Phase
|
## Landed This Phase
|
||||||
@@ -94,6 +94,10 @@ Current gap:
|
|||||||
- artifact schema version bump for UI documents
|
- artifact schema version bump for UI documents
|
||||||
- loader/resource accessors and memory accounting
|
- loader/resource accessors and memory accounting
|
||||||
- schema compile/load/artifact regression coverage
|
- schema compile/load/artifact regression coverage
|
||||||
|
- schema consistency rules for:
|
||||||
|
- `allowedValues` only on `enum`
|
||||||
|
- `documentKind` / `restrictDocumentKind` only on `document`
|
||||||
|
- explicit `documentKind` required when `restrictDocumentKind=true`
|
||||||
- Engine runtime layer added:
|
- Engine runtime layer added:
|
||||||
- `UIScreenPlayer`
|
- `UIScreenPlayer`
|
||||||
- `UIDocumentScreenHost`
|
- `UIDocumentScreenHost`
|
||||||
|
|||||||
@@ -569,6 +569,10 @@ bool BuildSchemaAttributeDefinition(
|
|||||||
Containers::Array<UIDocumentDiagnostic>& diagnostics,
|
Containers::Array<UIDocumentDiagnostic>& diagnostics,
|
||||||
Containers::String& inOutErrorMessage) {
|
Containers::String& inOutErrorMessage) {
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
|
bool hasExplicitDocumentKind = false;
|
||||||
|
bool hasExplicitRestrictDocumentKind = false;
|
||||||
|
bool hasExplicitAllowedValues = false;
|
||||||
|
|
||||||
if (!node.children.Empty()) {
|
if (!node.children.Empty()) {
|
||||||
AppendDiagnostic(
|
AppendDiagnostic(
|
||||||
sourcePath,
|
sourcePath,
|
||||||
@@ -640,6 +644,7 @@ bool BuildSchemaAttributeDefinition(
|
|||||||
const UIDocumentAttribute* documentKindAttribute =
|
const UIDocumentAttribute* documentKindAttribute =
|
||||||
FindAttributeByName(node, "documentKind", "kind");
|
FindAttributeByName(node, "documentKind", "kind");
|
||||||
if (documentKindAttribute != nullptr && !documentKindAttribute->value.Trim().Empty()) {
|
if (documentKindAttribute != nullptr && !documentKindAttribute->value.Trim().Empty()) {
|
||||||
|
hasExplicitDocumentKind = true;
|
||||||
if (!TryParseDocumentKindValueExtended(documentKindAttribute->value, outDefinition.documentKind)) {
|
if (!TryParseDocumentKindValueExtended(documentKindAttribute->value, outDefinition.documentKind)) {
|
||||||
AppendDiagnostic(
|
AppendDiagnostic(
|
||||||
sourcePath,
|
sourcePath,
|
||||||
@@ -653,11 +658,23 @@ bool BuildSchemaAttributeDefinition(
|
|||||||
valid = false;
|
valid = false;
|
||||||
} else if (outDefinition.valueType == UISchemaValueType::Document) {
|
} else if (outDefinition.valueType == UISchemaValueType::Document) {
|
||||||
outDefinition.restrictDocumentKind = true;
|
outDefinition.restrictDocumentKind = true;
|
||||||
|
} else {
|
||||||
|
AppendDiagnostic(
|
||||||
|
sourcePath,
|
||||||
|
diagnostics,
|
||||||
|
inOutErrorMessage,
|
||||||
|
UIDocumentDiagnosticSeverity::Error,
|
||||||
|
node.location,
|
||||||
|
Containers::String("Schema attribute '") +
|
||||||
|
outDefinition.name +
|
||||||
|
"' can only declare 'documentKind' for 'document' value types.");
|
||||||
|
valid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const UIDocumentAttribute* restrictDocumentKindAttribute = FindAttributeByName(node, "restrictDocumentKind");
|
if (const UIDocumentAttribute* restrictDocumentKindAttribute = FindAttributeByName(node, "restrictDocumentKind");
|
||||||
restrictDocumentKindAttribute != nullptr && !restrictDocumentKindAttribute->value.Trim().Empty()) {
|
restrictDocumentKindAttribute != nullptr && !restrictDocumentKindAttribute->value.Trim().Empty()) {
|
||||||
|
hasExplicitRestrictDocumentKind = true;
|
||||||
if (!TryParseBooleanValueExtended(
|
if (!TryParseBooleanValueExtended(
|
||||||
restrictDocumentKindAttribute->value,
|
restrictDocumentKindAttribute->value,
|
||||||
outDefinition.restrictDocumentKind)) {
|
outDefinition.restrictDocumentKind)) {
|
||||||
@@ -671,14 +688,39 @@ bool BuildSchemaAttributeDefinition(
|
|||||||
restrictDocumentKindAttribute->value +
|
restrictDocumentKindAttribute->value +
|
||||||
"' for schema attribute 'restrictDocumentKind'.");
|
"' for schema attribute 'restrictDocumentKind'.");
|
||||||
valid = false;
|
valid = false;
|
||||||
|
} else if (outDefinition.valueType != UISchemaValueType::Document) {
|
||||||
|
AppendDiagnostic(
|
||||||
|
sourcePath,
|
||||||
|
diagnostics,
|
||||||
|
inOutErrorMessage,
|
||||||
|
UIDocumentDiagnosticSeverity::Error,
|
||||||
|
node.location,
|
||||||
|
Containers::String("Schema attribute '") +
|
||||||
|
outDefinition.name +
|
||||||
|
"' can only declare 'restrictDocumentKind' for 'document' value types.");
|
||||||
|
valid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const UIDocumentAttribute* allowedValuesAttribute = FindAttributeByName(node, "allowedValues", "values");
|
if (const UIDocumentAttribute* allowedValuesAttribute = FindAttributeByName(node, "allowedValues", "values");
|
||||||
allowedValuesAttribute != nullptr) {
|
allowedValuesAttribute != nullptr) {
|
||||||
|
hasExplicitAllowedValues = true;
|
||||||
outDefinition.allowedValues = ParseAllowedValueList(allowedValuesAttribute->value);
|
outDefinition.allowedValues = ParseAllowedValueList(allowedValuesAttribute->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasExplicitAllowedValues && outDefinition.valueType != UISchemaValueType::Enum) {
|
||||||
|
AppendDiagnostic(
|
||||||
|
sourcePath,
|
||||||
|
diagnostics,
|
||||||
|
inOutErrorMessage,
|
||||||
|
UIDocumentDiagnosticSeverity::Error,
|
||||||
|
node.location,
|
||||||
|
Containers::String("Schema attribute '") +
|
||||||
|
outDefinition.name +
|
||||||
|
"' can only declare 'allowedValues' for 'enum' value types.");
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (outDefinition.valueType == UISchemaValueType::Enum && outDefinition.allowedValues.Empty()) {
|
if (outDefinition.valueType == UISchemaValueType::Enum && outDefinition.allowedValues.Empty()) {
|
||||||
AppendDiagnostic(
|
AppendDiagnostic(
|
||||||
sourcePath,
|
sourcePath,
|
||||||
@@ -692,6 +734,21 @@ bool BuildSchemaAttributeDefinition(
|
|||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (outDefinition.valueType == UISchemaValueType::Document &&
|
||||||
|
outDefinition.restrictDocumentKind &&
|
||||||
|
!hasExplicitDocumentKind) {
|
||||||
|
AppendDiagnostic(
|
||||||
|
sourcePath,
|
||||||
|
diagnostics,
|
||||||
|
inOutErrorMessage,
|
||||||
|
UIDocumentDiagnosticSeverity::Error,
|
||||||
|
node.location,
|
||||||
|
Containers::String("Schema attribute '") +
|
||||||
|
outDefinition.name +
|
||||||
|
"' must declare 'documentKind' when 'restrictDocumentKind' is true.");
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,31 @@ void WriteTextFile(const std::filesystem::path& path, const std::string& content
|
|||||||
ASSERT_TRUE(static_cast<bool>(output));
|
ASSERT_TRUE(static_cast<bool>(output));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExpectSchemaCompileFailure(
|
||||||
|
const char* testFolderName,
|
||||||
|
const char* markup,
|
||||||
|
const char* expectedMessageFragment) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
const fs::path root = fs::temp_directory_path() / testFolderName;
|
||||||
|
const fs::path schemaPath = root / "invalid.xcschema";
|
||||||
|
fs::remove_all(root);
|
||||||
|
|
||||||
|
WriteTextFile(schemaPath, markup);
|
||||||
|
|
||||||
|
UISchemaLoader loader;
|
||||||
|
UIDocumentCompileResult compileResult = {};
|
||||||
|
EXPECT_FALSE(loader.CompileDocument(schemaPath.string().c_str(), compileResult));
|
||||||
|
EXPECT_FALSE(compileResult.succeeded);
|
||||||
|
EXPECT_FALSE(compileResult.errorMessage.Empty());
|
||||||
|
ASSERT_FALSE(compileResult.document.diagnostics.Empty());
|
||||||
|
|
||||||
|
const std::string errorMessage = compileResult.errorMessage.CStr();
|
||||||
|
EXPECT_NE(errorMessage.find(expectedMessageFragment), std::string::npos);
|
||||||
|
|
||||||
|
fs::remove_all(root);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(UISchemaDocument, CompileAndArtifactLoadPopulateSchemaDefinition) {
|
TEST(UISchemaDocument, CompileAndArtifactLoadPopulateSchemaDefinition) {
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
@@ -150,4 +175,48 @@ TEST(UISchemaDocument, CompileRejectsDuplicateAttributeDefinitions) {
|
|||||||
fs::remove_all(root);
|
fs::remove_all(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(UISchemaDocument, CompileRejectsAllowedValuesOnNonEnumAttribute) {
|
||||||
|
ExpectSchemaCompileFailure(
|
||||||
|
"xc_ui_schema_allowed_values_non_enum_test",
|
||||||
|
"<Schema>\n"
|
||||||
|
" <Element tag=\"View\">\n"
|
||||||
|
" <Attribute name=\"gap\" type=\"number\" allowedValues=\"8,12\" />\n"
|
||||||
|
" </Element>\n"
|
||||||
|
"</Schema>\n",
|
||||||
|
"allowedValues");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UISchemaDocument, CompileRejectsDocumentKindOnNonDocumentAttribute) {
|
||||||
|
ExpectSchemaCompileFailure(
|
||||||
|
"xc_ui_schema_document_kind_non_document_test",
|
||||||
|
"<Schema>\n"
|
||||||
|
" <Element tag=\"View\">\n"
|
||||||
|
" <Attribute name=\"id\" type=\"string\" documentKind=\"theme\" />\n"
|
||||||
|
" </Element>\n"
|
||||||
|
"</Schema>\n",
|
||||||
|
"documentKind");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UISchemaDocument, CompileRejectsRestrictDocumentKindOnNonDocumentAttribute) {
|
||||||
|
ExpectSchemaCompileFailure(
|
||||||
|
"xc_ui_schema_restrict_non_document_test",
|
||||||
|
"<Schema>\n"
|
||||||
|
" <Element tag=\"View\">\n"
|
||||||
|
" <Attribute name=\"id\" type=\"string\" restrictDocumentKind=\"true\" />\n"
|
||||||
|
" </Element>\n"
|
||||||
|
"</Schema>\n",
|
||||||
|
"restrictDocumentKind");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UISchemaDocument, CompileRejectsRestrictDocumentKindWithoutExplicitDocumentKind) {
|
||||||
|
ExpectSchemaCompileFailure(
|
||||||
|
"xc_ui_schema_restrict_without_kind_test",
|
||||||
|
"<Schema>\n"
|
||||||
|
" <Element tag=\"View\">\n"
|
||||||
|
" <Attribute name=\"theme\" type=\"document\" restrictDocumentKind=\"true\" />\n"
|
||||||
|
" </Element>\n"
|
||||||
|
"</Schema>\n",
|
||||||
|
"must declare 'documentKind'");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
Reference in New Issue
Block a user