diff --git a/src/main/java/sirius/pasta/tagliatelle/compiler/TemplateCompiler.java b/src/main/java/sirius/pasta/tagliatelle/compiler/TemplateCompiler.java index 87e30f5bc..deeabe168 100644 --- a/src/main/java/sirius/pasta/tagliatelle/compiler/TemplateCompiler.java +++ b/src/main/java/sirius/pasta/tagliatelle/compiler/TemplateCompiler.java @@ -8,14 +8,14 @@ package sirius.pasta.tagliatelle.compiler; -import sirius.kernel.tokenizer.Char; -import sirius.kernel.tokenizer.ParseError; -import sirius.kernel.tokenizer.Position; import sirius.kernel.commons.Explain; import sirius.kernel.commons.Strings; import sirius.kernel.di.std.PriorityParts; import sirius.kernel.health.Exceptions; import sirius.kernel.health.HandledException; +import sirius.kernel.tokenizer.Char; +import sirius.kernel.tokenizer.ParseError; +import sirius.kernel.tokenizer.Position; import sirius.pasta.Pasta; import sirius.pasta.noodle.Callable; import sirius.pasta.noodle.ConstantCall; @@ -33,7 +33,10 @@ import javax.annotation.Nullable; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.function.Predicate; /** * Compiles a sources file into a {@link sirius.pasta.tagliatelle.Template}. @@ -369,6 +372,8 @@ private void handleTag(TagHandler handler, CompositeEmitter block) { * @param handler the handler to add the attributes to */ private void parseAttributes(TagHandler handler) { + // Collect all parsed attributes. + Set attributes = new HashSet<>(); while (true) { skipWhitespaces(); if (reader.current().isEndOfInput() || reader.current().is('>', '/')) { @@ -379,6 +384,10 @@ private void parseAttributes(TagHandler handler) { verifyAttributeNameAndType(handler, name, attributeType); if (attributeType == null) { attributeType = String.class; + } else { + // verifyAttributeNameAndType(...) already reports an error if the attributeType is unknown. + // We only save the found and defined attributes. + attributes.add(name); } skipWhitespaces(); @@ -417,6 +426,24 @@ private void parseAttributes(TagHandler handler) { } consumeExpectedCharacter('"'); } + + checkMissingAttributes(handler, attributes); + } + + /** + * Checks if all required attributes are present. + * + * @param handler the tag handler + * @param attributes the set of attributes which were parsed + */ + private void checkMissingAttributes(TagHandler handler, Set attributes) { + handler.getRequiredAttributeNames() + .stream() + .filter(Predicate.not(attributes::contains)) + .forEach(attr -> context.error(reader.current(), + "Missing required attribute. %s missing the required attribute '%s'.", + handler.getTagName(), + attr)); } private void parseAttributeExpression(TagHandler handler, diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/ArgTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/ArgTag.java index b258d4972..7dcfef7ad 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/ArgTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/ArgTag.java @@ -19,6 +19,7 @@ import javax.annotation.Nonnull; import java.util.Arrays; import java.util.List; +import java.util.Set; /** * Handles i:arg which specifies a template argument. @@ -157,4 +158,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(PARAM_NAME, PARAM_TYPE); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/BlockTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/BlockTag.java index 49abe85c6..6b4463dcd 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/BlockTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/BlockTag.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Handles i:block which specifies a template section passed into a tag invocation. @@ -85,4 +86,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(PARAM_NAME); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/DefineTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/DefineTag.java index b04325906..384a4bbb7 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/DefineTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/DefineTag.java @@ -17,6 +17,7 @@ import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Handles i:define which defines a string as the evaluation result of its body. @@ -81,4 +82,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(PARAM_NAME); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/DynamicInvokeTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/DynamicInvokeTag.java index a0bfdaa19..3f24cd5a1 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/DynamicInvokeTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/DynamicInvokeTag.java @@ -17,6 +17,7 @@ import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Handles i:dynamicInvoke which invokes or inlines a given template. @@ -69,4 +70,9 @@ public Class getExpectedAttributeType(String name) { // Accept anything, we don't know yet what to expect at runtime.... return Callable.class; } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(ATTR_TEMPLATE); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/ExtensionsTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/ExtensionsTag.java index d21c634aa..1ebda309b 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/ExtensionsTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/ExtensionsTag.java @@ -20,6 +20,7 @@ import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Handles i:extensions which invokes all extensions with the given name. @@ -86,4 +87,9 @@ public Class getExpectedAttributeType(String name) { return Callable.class; } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(ATTR_TARGET); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/ExtraBlockTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/ExtraBlockTag.java index b5e5ca65c..283caec2e 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/ExtraBlockTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/ExtraBlockTag.java @@ -18,6 +18,7 @@ import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Permits to add an extra block to the global render context. @@ -86,4 +87,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(PARAM_NAME); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/ForTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/ForTag.java index 04aab6555..d5faab676 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/ForTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/ForTag.java @@ -19,6 +19,7 @@ import javax.annotation.Nonnull; import java.util.Arrays; import java.util.List; +import java.util.Set; /** * Handles i:for which emits its body for each item in an {@link Iterable}. @@ -116,4 +117,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(PARAM_ITEMS, PARAM_TYPE, PARAM_VAR); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/IfTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/IfTag.java index b9b5aae3e..ec95afab0 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/IfTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/IfTag.java @@ -16,6 +16,7 @@ import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Handles i:if which emits its body if a condition is met. @@ -71,4 +72,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of("test"); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/IncludeTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/IncludeTag.java index 08118c1ff..923e0746d 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/IncludeTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/IncludeTag.java @@ -18,6 +18,7 @@ import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Handles i:include which includes the contents of the given resource without any processing. @@ -74,4 +75,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(ATTR_NAME); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/InvokeTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/InvokeTag.java index 29849dd0c..3543c36a7 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/InvokeTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/InvokeTag.java @@ -18,7 +18,9 @@ import javax.annotation.Nonnull; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Handles i:invoke which invokes or inlines a given template. @@ -116,6 +118,21 @@ public Class getExpectedAttributeType(String name) { } } + @Override + public Set getRequiredAttributeNames() { + Set result = new HashSet<>(); + result.add(ATTR_TEMPLATE); + + if (ensureThatTemplateIsPresent()) { + for (TemplateArgument arg : template.getArguments()) { + if (arg.getDefaultValue() == null) { + result.add(arg.getName()); + } + } + } + return result; + } + private boolean ensureThatTemplateIsPresent() { if (template == null) { if (templateResolved) { diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/LocalTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/LocalTag.java index 0d29f6cd1..e46b8b797 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/LocalTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/LocalTag.java @@ -18,6 +18,7 @@ import javax.annotation.Nonnull; import java.util.Arrays; import java.util.List; +import java.util.Set; /** * Handles i:local which defines a local variable. @@ -92,4 +93,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(PARAM_NAME, PARAM_VALUE); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/PragmaTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/PragmaTag.java index 3bd309949..b9adaf34b 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/PragmaTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/PragmaTag.java @@ -16,6 +16,7 @@ import javax.annotation.Nonnull; import java.util.Arrays; import java.util.List; +import java.util.Set; /** * Handles i:pragma which defines a pragma (key / value pair) for a template. @@ -79,4 +80,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(PARAM_NAME); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/RenderTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/RenderTag.java index 9ae3d5d70..a78ae2f83 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/RenderTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/RenderTag.java @@ -17,6 +17,7 @@ import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Handles i:render which emits the block with the given name. @@ -79,4 +80,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(PARAM_NAME); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/SassTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/SassTag.java index c2ee2ec7b..37ecdac69 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/SassTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/SassTag.java @@ -24,6 +24,7 @@ import java.io.StringReader; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Handles i:sass which renders a SASS file into the current template as compiled CSS. @@ -93,4 +94,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of(SOURCE_ATTRIBUTE); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/SwitchTag.java b/src/main/java/sirius/pasta/tagliatelle/tags/SwitchTag.java index c566922c9..7ee1e7d97 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/SwitchTag.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/SwitchTag.java @@ -16,6 +16,7 @@ import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Handles i:switch which can hold multiple blocks which are only rendered if their name matches an expression. @@ -68,4 +69,9 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + return Set.of("test"); + } } diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/TagHandler.java b/src/main/java/sirius/pasta/tagliatelle/tags/TagHandler.java index f85d6a355..6fe2b7313 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/TagHandler.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/TagHandler.java @@ -24,9 +24,11 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.Set; /** * Handles a tag detected by the {@link sirius.pasta.tagliatelle.compiler.TemplateCompiler}. @@ -93,6 +95,14 @@ public Callable getAttribute(String name) { return attributes.get(name); } + /** + * Returns all required attribute names. + * @return a set of all attribute names. + */ + public Set getRequiredAttributeNames() { + return Collections.emptySet(); + } + /** * Fetches the attribute with the given name, expecting that a constant value is present. * diff --git a/src/main/java/sirius/pasta/tagliatelle/tags/TaglibTagHandler.java b/src/main/java/sirius/pasta/tagliatelle/tags/TaglibTagHandler.java index 4c69a3c3a..e870d6537 100644 --- a/src/main/java/sirius/pasta/tagliatelle/tags/TaglibTagHandler.java +++ b/src/main/java/sirius/pasta/tagliatelle/tags/TaglibTagHandler.java @@ -13,6 +13,9 @@ import sirius.pasta.tagliatelle.TemplateArgument; import sirius.pasta.tagliatelle.emitter.CompositeEmitter; +import java.util.HashSet; +import java.util.Set; + /** * Handles the invocation of a user-defined tag. *

@@ -50,4 +53,15 @@ public Class getExpectedAttributeType(String name) { return super.getExpectedAttributeType(name); } + + @Override + public Set getRequiredAttributeNames() { + Set result = new HashSet<>(); + for (TemplateArgument arg : template.getArguments()) { + if (arg.getDefaultValue() == null) { + result.add(arg.getName()); + } + } + return result; + } }