You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2019/05/24 13:38:23 UTC

[sling-org-apache-sling-scripting-sightly-compiler] branch issue/SLING-6779 updated: SLING-6779 - The HTL compiler and Maven Plugin should warn when using potentially invalid options

This is an automated email from the ASF dual-hosted git repository.

radu pushed a commit to branch issue/SLING-6779
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly-compiler.git


The following commit(s) were added to refs/heads/issue/SLING-6779 by this push:
     new 7e38340  SLING-6779 - The HTL compiler and Maven Plugin should warn when using potentially invalid options
7e38340 is described below

commit 7e383407b96a03c63fc54c7e201e8cbbee5fa258
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Fri May 24 15:38:12 2019 +0200

    SLING-6779 - The HTL compiler and Maven Plugin should warn when using potentially invalid options
    
    * collected known options from filters
    * added a warn message in the compiler if a certain expression option is not handled by
    any of the filters and the expression context doesn't allow arbitrary options
---
 bnd.bnd                                            |  2 +
 pom.xml                                            | 93 ++++++++++++----------
 .../impl/compiler/frontend/CompilerContext.java    |  5 +-
 .../impl/compiler/frontend/ExpressionWrapper.java  | 25 +++++-
 .../sightly/impl/filter/AbstractFilter.java        | 27 ++++---
 .../scripting/sightly/impl/filter/Filter.java      | 20 +++++
 .../sightly/impl/filter/FormatFilter.java          | 39 ++++-----
 .../scripting/sightly/impl/filter/I18nFilter.java  | 26 ++++--
 .../scripting/sightly/impl/filter/JoinFilter.java  | 33 +++++---
 .../sightly/impl/filter/URIManipulationFilter.java | 43 +++++-----
 .../scripting/sightly/impl/filter/XSSFilter.java   | 16 ++--
 .../sightly/impl/html/dom/MarkupHandler.java       |  2 +-
 .../sightly/impl/compiler/SightlyCompilerTest.java | 21 +++--
 .../impl/frontend/ExpressionWrapperTest.java       |  9 ++-
 14 files changed, 218 insertions(+), 143 deletions(-)

diff --git a/bnd.bnd b/bnd.bnd
new file mode 100644
index 0000000..b036455
--- /dev/null
+++ b/bnd.bnd
@@ -0,0 +1,2 @@
+Provide-Capability: io.sightly.compiler; version:Version="1.0", io.sightly.compiler; version:Version="1.1", io.sightly.compiler; version:Version="1.2", io.sightly.compiler; version:Version="1.3", io.sightly.compiler; version:Version="1.3.1", io.sightly.compiler; version:Version="1.4",
+Require-Capability: io.sightly.runtime; filter:="(&(version>=1.0)(!(version>=2.0)))"
diff --git a/pom.xml b/pom.xml
index 8797fb0..5a9d563 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,8 +24,8 @@
     <!-- ======================================================================= -->
     <parent>
         <groupId>org.apache.sling</groupId>
-        <artifactId>sling</artifactId>
-        <version>34</version>
+        <artifactId>sling-bundle-parent</artifactId>
+        <version>35</version>
         <relativePath />
     </parent>
 
@@ -38,7 +38,6 @@
         releasing to only increase the first part, unless the module provides support for a newer version of the HTL specification.
     -->
     <version>1.1.3-1.4.0-SNAPSHOT</version>
-    <packaging>bundle</packaging>
 
     <name>Apache Sling Scripting HTL Compiler</name>
 
@@ -64,35 +63,35 @@
     <!-- ======================================================================= -->
     <build>
         <plugins>
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <extensions>true</extensions>
-                <executions>
-                    <execution>
-                        <id>scr-metadata</id>
-                        <goals>
-                            <goal>manifest</goal>
-                        </goals>
-                    </execution>
-                </executions>
-                <configuration>
-                    <exportScr>true</exportScr>
-                    <instructions>
-                        <Provide-Capability>
-                            io.sightly.compiler; version:Version=1.0,
-                            io.sightly.compiler; version:Version=1.1,
-                            io.sightly.compiler; version:Version=1.2,
-                            io.sightly.compiler; version:Version=1.3,
-                            io.sightly.compiler; version:Version=1.3.1,
-                            io.sightly.compiler; version:Version=1.4
-                        </Provide-Capability>
-                        <Require-Capability>
-                            io.sightly.runtime; filter:="(&amp;(version&gt;=1.0)(!(version&gt;=2.0)))"
-                        </Require-Capability>
-                    </instructions>
-                </configuration>
-            </plugin>
+<!--            <plugin>-->
+<!--                <groupId>org.apache.felix</groupId>-->
+<!--                <artifactId>maven-bundle-plugin</artifactId>-->
+<!--                <extensions>true</extensions>-->
+<!--                <executions>-->
+<!--                    <execution>-->
+<!--                        <id>scr-metadata</id>-->
+<!--                        <goals>-->
+<!--                            <goal>manifest</goal>-->
+<!--                        </goals>-->
+<!--                    </execution>-->
+<!--                </executions>-->
+<!--                <configuration>-->
+<!--                    <exportScr>true</exportScr>-->
+<!--                    <instructions>-->
+<!--                        <Provide-Capability>-->
+<!--                            io.sightly.compiler; version:Version=1.0,-->
+<!--                            io.sightly.compiler; version:Version=1.1,-->
+<!--                            io.sightly.compiler; version:Version=1.2,-->
+<!--                            io.sightly.compiler; version:Version=1.3,-->
+<!--                            io.sightly.compiler; version:Version=1.3.1,-->
+<!--                            io.sightly.compiler; version:Version=1.4-->
+<!--                        </Provide-Capability>-->
+<!--                        <Require-Capability>-->
+<!--                            io.sightly.runtime; filter:="(&amp;(version&gt;=1.0)(!(version&gt;=2.0)))"-->
+<!--                        </Require-Capability>-->
+<!--                    </instructions>-->
+<!--                </configuration>-->
+<!--            </plugin>-->
             <plugin>
                 <groupId>org.antlr</groupId>
                 <artifactId>antlr4-maven-plugin</artifactId>
@@ -126,9 +125,9 @@
                 </executions>
             </plugin>
             <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>findbugs-maven-plugin</artifactId>
-                <version>3.0.5</version>
+                <groupId>com.github.spotbugs</groupId>
+                <artifactId>spotbugs-maven-plugin</artifactId>
+                <version>3.1.11</version>
                 <configuration>
                     <effort>Max</effort>
                     <xmlOutput>true</xmlOutput>
@@ -257,22 +256,28 @@
         </dependency>
         <dependency>
             <groupId>org.powermock</groupId>
-            <artifactId>powermock-reflect</artifactId>
-            <version>1.6.5</version>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <version>2.0.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.powermock</groupId>
             <artifactId>powermock-module-junit4</artifactId>
-            <version>1.6.5</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.powermock</groupId>
-            <artifactId>powermock-api-mockito</artifactId>
-            <version>1.6.5</version>
+            <version>2.0.2</version>
             <scope>test</scope>
         </dependency>
+<!--        <dependency>-->
+<!--            <groupId>org.powermock</groupId>-->
+<!--            <artifactId>powermock-module-junit4</artifactId>-->
+<!--            <version>2.0.2</version>-->
+<!--            <scope>test</scope>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>org.powermock</groupId>-->
+<!--            <artifactId>powermock-api-mockito</artifactId>-->
+<!--            <version>2.0.2</version>-->
+<!--            <scope>test</scope>-->
+<!--        </dependency>-->
     </dependencies>
 
 </project>
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java
index 60b4247..3e8cf0b 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java
@@ -47,10 +47,7 @@ public class CompilerContext {
     }
 
     public Expression adjustToContext(Expression expression, MarkupContext context, ExpressionContext expressionContext) {
-        if (!expression.getOptions().containsKey(Syntax.CONTEXT_OPTION)) {
-            return expressionWrapper.adjustToContext(expression, context, expressionContext);
-        }
-        return expression;
+        return expressionWrapper.adjustToContext(expression, context, expressionContext);
     }
 
     public PushStream getPushStream() {
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/ExpressionWrapper.java b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/ExpressionWrapper.java
index cf26b1c..53d1024 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/ExpressionWrapper.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/ExpressionWrapper.java
@@ -19,15 +19,20 @@
 package org.apache.sling.scripting.sightly.impl.compiler.frontend;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.sling.scripting.sightly.compiler.expression.Expression;
 import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.BinaryOperation;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.BinaryOperator;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.StringConstant;
+import org.apache.sling.scripting.sightly.impl.compiler.PushStream;
 import org.apache.sling.scripting.sightly.impl.compiler.Syntax;
+import org.apache.sling.scripting.sightly.impl.filter.AbstractFilter;
 import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext;
 import org.apache.sling.scripting.sightly.impl.filter.Filter;
 import org.apache.sling.scripting.sightly.compiler.expression.MarkupContext;
@@ -38,9 +43,18 @@ import org.apache.sling.scripting.sightly.compiler.expression.MarkupContext;
 public class ExpressionWrapper {
 
     private final List<Filter> filters;
+    private final Set<String> knownOptions;
+    private final PushStream stream;
 
-    public ExpressionWrapper(List<Filter> filters) {
+    public ExpressionWrapper(PushStream stream, List<Filter> filters) {
+        this.stream = stream;
         this.filters = filters;
+        Set<String> options = new HashSet<>();
+        for (Filter filter : filters) {
+            options.addAll(filter.getOptions());
+        }
+        options.add(Syntax.CONTEXT_OPTION);
+        knownOptions = Collections.unmodifiableSet(options);
     }
 
     public Expression transform(Interpolation interpolation, MarkupContext markupContext, ExpressionContext expressionContext) {
@@ -51,13 +65,20 @@ public class ExpressionWrapper {
                 nodes.add(new StringConstant(fragment.getText()));
             } else {
                 Expression expression = fragment.getExpression();
+                if (AbstractFilter.NON_PARAMETRIZABLE_CONTEXTS.contains(expressionContext)) {
+                    for (String option : expression.getOptions().keySet()) {
+                        if (!knownOptions.contains(option)) {
+                            stream.warn(new PushStream.StreamMessage(String.format("Unknown option '%s'.", option), expression.getRawText()));
+                        }
+                    }
+                }
                 Expression transformed = adjustToContext(expression, markupContext, expressionContext);
                 nodes.add(transformed.getRoot());
                 options.putAll(transformed.getOptions());
             }
         }
         ExpressionNode root = join(nodes);
-        if (interpolation.size() > 1 && options.containsKey(Syntax.CONTEXT_OPTION)) {
+        if (interpolation.size() > 1) {
             //context must not be calculated by merging
             options.remove(Syntax.CONTEXT_OPTION);
         }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/AbstractFilter.java b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/AbstractFilter.java
index 9f2f931..53ee22c 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/AbstractFilter.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/AbstractFilter.java
@@ -18,11 +18,12 @@
  ******************************************************************************/
 package org.apache.sling.scripting.sightly.impl.filter;
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
-import java.util.function.Supplier;
 
 import org.apache.sling.scripting.sightly.compiler.expression.Expression;
 import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
@@ -30,6 +31,14 @@ import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
 public abstract class AbstractFilter implements Filter {
 
     protected int priority = 100;
+    public static final Set<ExpressionContext> NON_PARAMETRIZABLE_CONTEXTS;
+    static {
+        Set<ExpressionContext> contexts = new HashSet<>(Arrays.asList(ExpressionContext.values()));
+        contexts.remove(ExpressionContext.PLUGIN_DATA_SLY_USE);
+        contexts.remove(ExpressionContext.PLUGIN_DATA_SLY_TEMPLATE);
+        contexts.remove(ExpressionContext.PLUGIN_DATA_SLY_CALL);
+        NON_PARAMETRIZABLE_CONTEXTS = Collections.unmodifiableSet(contexts);
+    }
 
     @Override
     public int priority() {
@@ -48,19 +57,15 @@ public abstract class AbstractFilter implements Filter {
 
     @Override
     public Expression apply(Expression expression, ExpressionContext expressionContext) {
-        boolean filter = false;
-        for (String option : getOptions()) {
-            if (expression.containsOption(option)) {
-                filter = true;
-                break;
-            }
-        }
-        if (filter) {
+        Set<String> expressionOptions = expression.getOptions().keySet();
+        if (getApplicableContexts().contains(expressionContext) && expressionOptions.containsAll(getRequiredOptions())) {
             return apply(expression, getFilterOptions(expression, getOptions()));
         }
         return expression;
     }
 
+    protected abstract Expression apply(Expression expression, Map<String, ExpressionNode> options);
+
     /**
      * Collects the options passed in the {@code options} array into a new map while removing them from the original expression.
      *
@@ -79,8 +84,6 @@ public abstract class AbstractFilter implements Filter {
         return collector;
     }
 
-    protected abstract Expression apply(Expression expression, Map<String, ExpressionNode> options);
-
     @Override
     public int hashCode() {
         return super.hashCode();
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/Filter.java b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/Filter.java
index 44d7288..4607df1 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/Filter.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/Filter.java
@@ -18,7 +18,9 @@
  ******************************************************************************/
 package org.apache.sling.scripting.sightly.impl.filter;
 
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Set;
 
 import org.apache.sling.scripting.sightly.compiler.expression.Expression;
@@ -57,4 +59,22 @@ public interface Filter extends Comparable<Filter> {
         return Collections.emptySet();
     }
 
+    /**
+     * Provides the option names that will trigger a filter's execution.
+     *
+     * @return the required options from an expression in order to trigger the filter
+     */
+    default Set<String> getRequiredOptions() {
+        return Collections.emptySet();
+    }
+
+    /**
+     * Provides the applicable contexts for this filter.
+     *
+     * @return the applicable contexts for this filter
+     */
+    default Set<ExpressionContext> getApplicableContexts() {
+        return new HashSet<>(Arrays.asList(ExpressionContext.values()));
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/FormatFilter.java b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/FormatFilter.java
index 0af52dd..349e13e 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/FormatFilter.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/FormatFilter.java
@@ -19,8 +19,9 @@
 package org.apache.sling.scripting.sightly.impl.filter;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
-import java.util.Optional;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.sling.scripting.sightly.compiler.expression.Expression;
@@ -38,8 +39,8 @@ public class FormatFilter extends AbstractFilter {
     public static final String FORMAT_LOCALE_OPTION = "formatLocale";
     public static final String TIMEZONE_OPTION = "timezone";
 
-    private static final Set<String> OPTIONS =
-            new HashSet<>(Arrays.asList(FORMAT_OPTION, TYPE_OPTION, FORMAT_LOCALE_OPTION, TIMEZONE_OPTION));
+    private static final Set<String> OPTIONS = new HashSet<>(Arrays.asList(FORMAT_OPTION, TYPE_OPTION, FORMAT_LOCALE_OPTION, TIMEZONE_OPTION));
+    private static final Set<String> REQUIRED_OPTIONS = Collections.singleton(FORMAT_OPTION);
 
     private static final class FormatFilterLoader {
         private static final FormatFilter INSTANCE = new FormatFilter();
@@ -53,27 +54,9 @@ public class FormatFilter extends AbstractFilter {
     }
 
     @Override
-    public Expression apply(Expression expression, ExpressionContext expressionContext) {
-        //todo: if the expression is a string constant, we can produce the transformation at
-        //compile time, with no need of a runtime function
-        boolean filter = false;
-        for (String option : OPTIONS) {
-            if (expression.containsOption(option)) {
-                filter = true;
-                break;
-            }
-        }
-        if (!filter) {
-            return expression;
-        }
+    protected Expression apply(Expression expression, Map<String, ExpressionNode> options) {
         ExpressionNode translation =
-                new RuntimeCall(RuntimeCall.FORMAT, expression.getRoot(),
-                        new MapLiteral(getFilterOptions(expression,
-                                FORMAT_OPTION,
-                                TYPE_OPTION,
-                                I18nFilter.LOCALE_OPTION,
-                                FORMAT_LOCALE_OPTION,
-                                TIMEZONE_OPTION)));
+                new RuntimeCall(RuntimeCall.FORMAT, expression.getRoot(), new MapLiteral(getFilterOptions(expression, getOptions())));
         return expression.withNode(translation);
     }
 
@@ -81,4 +64,14 @@ public class FormatFilter extends AbstractFilter {
     public Set<String> getOptions() {
         return OPTIONS;
     }
+
+    @Override
+    public Set<String> getRequiredOptions() {
+        return REQUIRED_OPTIONS;
+    }
+
+    @Override
+    public Set<ExpressionContext> getApplicableContexts() {
+        return NON_PARAMETRIZABLE_CONTEXTS;
+    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/I18nFilter.java b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/I18nFilter.java
index 16b9228..94353f0 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/I18nFilter.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/I18nFilter.java
@@ -19,6 +19,7 @@
 package org.apache.sling.scripting.sightly.impl.filter;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -39,15 +40,13 @@ public final class I18nFilter extends AbstractFilter {
     public static final String BASENAME_OPTION = "basename";
 
     private static final Set<String> OPTIONS = new HashSet<>(Arrays.asList(I18N_OPTION, HINT_OPTION, LOCALE_OPTION, BASENAME_OPTION));
+    private static final Set<String> REQUIRED_OPTIONS = Collections.singleton(I18N_OPTION);
 
     private static final class I18nFilterLoader {
         private static final I18nFilter INSTANCE = new I18nFilter();
     }
 
     private I18nFilter() {
-        if (I18nFilterLoader.INSTANCE != null) {
-            throw new IllegalStateException("INSTANCE was already defined.");
-        }
         priority = 90;
     }
 
@@ -57,14 +56,27 @@ public final class I18nFilter extends AbstractFilter {
 
     @Override
     protected Expression apply(Expression expression, Map<String, ExpressionNode> options) {
-        ExpressionNode translation = new RuntimeCall(RuntimeCall.I18N, expression.getRoot(), new MapLiteral(options));
-        expression.removeOption(I18N_OPTION);
-        expression.getOptions().put(FormatFilter.FORMAT_LOCALE_OPTION, options.get(LOCALE_OPTION));
-        return expression.withNode(translation);
+        if (options.containsKey(I18N_OPTION)) {
+            ExpressionNode translation = new RuntimeCall(RuntimeCall.I18N, expression.getRoot(), new MapLiteral(options));
+            expression.removeOption(I18N_OPTION);
+            expression.getOptions().put(FormatFilter.FORMAT_LOCALE_OPTION, options.get(LOCALE_OPTION));
+            return expression.withNode(translation);
+        }
+        return expression;
     }
 
     @Override
     public Set<String> getOptions() {
         return OPTIONS;
     }
+
+    @Override
+    public Set<String> getRequiredOptions() {
+        return REQUIRED_OPTIONS;
+    }
+
+    @Override
+    public Set<ExpressionContext> getApplicableContexts() {
+        return NON_PARAMETRIZABLE_CONTEXTS;
+    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/JoinFilter.java b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/JoinFilter.java
index 113b3f5..73cdc62 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/JoinFilter.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/JoinFilter.java
@@ -18,6 +18,10 @@
  ******************************************************************************/
 package org.apache.sling.scripting.sightly.impl.filter;
 
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
 import org.apache.sling.scripting.sightly.compiler.expression.Expression;
 import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.RuntimeCall;
@@ -28,29 +32,38 @@ import org.apache.sling.scripting.sightly.compiler.expression.nodes.RuntimeCall;
 public class JoinFilter extends AbstractFilter {
 
     public static final String JOIN_OPTION = "join";
+    private static final Set<String> OPTIONS = Collections.singleton(JOIN_OPTION);
 
     private static final class JoinFilterLoader {
         private static final JoinFilter INSTANCE = new JoinFilter();
     }
 
-    private JoinFilter() {
-        if (JoinFilterLoader.INSTANCE != null) {
-            throw new IllegalStateException("INSTANCE was already defined.");
-        }
-    }
+    private JoinFilter() {}
 
     public static JoinFilter getInstance() {
         return JoinFilterLoader.INSTANCE;
     }
 
     @Override
-    public Expression apply(Expression expression, ExpressionContext expressionContext) {
-        if (!expression.containsOption(JOIN_OPTION) || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_USE || expressionContext
-                == ExpressionContext.PLUGIN_DATA_SLY_TEMPLATE || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_CALL) {
-            return expression;
-        }
+    protected Expression apply(Expression expression, Map<String, ExpressionNode> options) {
         ExpressionNode translation =
                 new RuntimeCall(RuntimeCall.JOIN, expression.getRoot(), expression.removeOption(JOIN_OPTION));
         return expression.withNode(translation);
     }
+
+    @Override
+    public Set<String> getOptions() {
+        return OPTIONS;
+    }
+
+    @Override
+    public Set<String> getRequiredOptions() {
+        return OPTIONS;
+    }
+
+    @Override
+    public Set<ExpressionContext> getApplicableContexts() {
+        return NON_PARAMETRIZABLE_CONTEXTS;
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/URIManipulationFilter.java b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/URIManipulationFilter.java
index 8ba4b22..40ba54b 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/URIManipulationFilter.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/URIManipulationFilter.java
@@ -18,7 +18,10 @@
  ******************************************************************************/
 package org.apache.sling.scripting.sightly.impl.filter;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.sling.scripting.sightly.compiler.expression.Expression;
 import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
@@ -48,43 +51,37 @@ public class URIManipulationFilter extends AbstractFilter {
     public static final String ADD_QUERY = "addQuery";
     public static final String REMOVE_QUERY = "removeQuery";
 
+    private static final Set<String> OPTIONS = new HashSet<>(Arrays.asList(SCHEME, DOMAIN, PATH, APPEND_PATH, PREPEND_PATH, SELECTORS,
+            ADD_SELECTORS, REMOVE_SELECTORS, EXTENSION, SUFFIX, PREPEND_SUFFIX, APPEND_SUFFIX, FRAGMENT, QUERY, ADD_QUERY, REMOVE_QUERY));
+
 
     private static final class URIManipulationFilterLoader {
         private static final URIManipulationFilter INSTANCE = new URIManipulationFilter();
     }
 
-    private URIManipulationFilter() {
-        if (URIManipulationFilterLoader.INSTANCE != null) {
-            throw new IllegalStateException("INSTANCE was already defined.");
-        }
-    }
+    private URIManipulationFilter() {}
 
     public static URIManipulationFilter getInstance() {
         return URIManipulationFilterLoader.INSTANCE;
     }
 
     @Override
-    public Expression apply(Expression expression, ExpressionContext expressionContext) {
-        if ((expression.containsOption(SCHEME) || expression.containsOption(DOMAIN) || expression.containsOption(PATH) || expression
-                .containsOption(APPEND_PATH) || expression.containsOption(PREPEND_PATH) || expression.containsOption(SELECTORS) ||
-                expression.containsOption(ADD_SELECTORS) || expression.containsOption(REMOVE_SELECTORS) || expression.containsOption
-                (EXTENSION) || expression.containsOption(SUFFIX) || expression.containsOption(PREPEND_SUFFIX) || expression
-                .containsOption(APPEND_SUFFIX) || expression.containsOption(FRAGMENT) || expression.containsOption(QUERY) || expression
-                .containsOption(ADD_QUERY) || expression.containsOption(REMOVE_QUERY)) && expressionContext != ExpressionContext
-                .PLUGIN_DATA_SLY_USE && expressionContext
-                != ExpressionContext.PLUGIN_DATA_SLY_TEMPLATE && expressionContext != ExpressionContext.PLUGIN_DATA_SLY_CALL &&
-                expressionContext != ExpressionContext.PLUGIN_DATA_SLY_RESOURCE) {
-            Map<String, ExpressionNode> uriOptions = getFilterOptions(expression, SCHEME, DOMAIN, PATH, APPEND_PATH, PREPEND_PATH,
-                    SELECTORS, ADD_SELECTORS, REMOVE_SELECTORS, EXTENSION, SUFFIX, PREPEND_SUFFIX, APPEND_SUFFIX, FRAGMENT, QUERY,
-                    ADD_QUERY, REMOVE_QUERY);
-            if (uriOptions.size() > 0) {
-                ExpressionNode translation =
-                        new RuntimeCall(RuntimeCall.URI_MANIPULATION, expression.getRoot(), new MapLiteral(uriOptions));
-                return expression.withNode(translation);
-            }
+    protected Expression apply(Expression expression, Map<String, ExpressionNode> options) {
+        if (options.size() > 0) {
+            ExpressionNode translation =
+                    new RuntimeCall(RuntimeCall.URI_MANIPULATION, expression.getRoot(), new MapLiteral(options));
+            return expression.withNode(translation);
         }
         return expression;
     }
 
+    @Override
+    public Set<String> getOptions() {
+        return OPTIONS;
+    }
 
+    @Override
+    public Set<ExpressionContext> getApplicableContexts() {
+        return NON_PARAMETRIZABLE_CONTEXTS;
+    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/XSSFilter.java b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/XSSFilter.java
index f693102..2451d90 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/XSSFilter.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/XSSFilter.java
@@ -18,6 +18,9 @@
  ******************************************************************************/
 package org.apache.sling.scripting.sightly.impl.filter;
 
+import java.util.Map;
+import java.util.Set;
+
 import org.apache.sling.scripting.sightly.compiler.expression.Expression;
 import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.RuntimeCall;
@@ -33,9 +36,6 @@ public class XSSFilter extends AbstractFilter {
     }
 
     private XSSFilter() {
-        if (XSSFilterLoader.INSTANCE != null) {
-            throw new IllegalStateException("INSTANCE was already defined.");
-        }
         priority = 110;
     }
 
@@ -44,11 +44,7 @@ public class XSSFilter extends AbstractFilter {
     }
 
     @Override
-    public Expression apply(Expression expression, ExpressionContext expressionContext) {
-        if (expressionContext == ExpressionContext.PLUGIN_DATA_SLY_USE || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_TEMPLATE
-                || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_CALL) {
-            return expression;
-        }
+    protected Expression apply(Expression expression, Map<String, ExpressionNode> options) {
         ExpressionNode context = expression.removeOption(Syntax.CONTEXT_OPTION);
         if (context != null) {
             return expression.withNode(new RuntimeCall(RuntimeCall.XSS, expression.getRoot(), context));
@@ -56,4 +52,8 @@ public class XSSFilter extends AbstractFilter {
         return expression;
     }
 
+    @Override
+    public Set<ExpressionContext> getApplicableContexts() {
+        return NON_PARAMETRIZABLE_CONTEXTS;
+    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java b/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java
index c8c4966..314b29a 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java
@@ -78,7 +78,7 @@ public class MarkupHandler {
     public MarkupHandler(PushStream stream, Map<String, Plugin> pluginRegistry, List<Filter> filters) {
         this.stream = stream;
         this.pluginRegistry = pluginRegistry;
-        this.expressionWrapper = new ExpressionWrapper(filters);
+        this.expressionWrapper = new ExpressionWrapper(stream, filters);
         this.compilerContext = new CompilerContext(symbolGenerator, expressionWrapper, stream);
     }
 
diff --git a/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyCompilerTest.java b/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyCompilerTest.java
index 7d95e1d..044561e 100644
--- a/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyCompilerTest.java
+++ b/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyCompilerTest.java
@@ -51,7 +51,7 @@ public class SightlyCompilerTest {
 
     @Test
     public void testMissingExplicitContext() {
-        for (String s : new String[] {"", "-win", "-mac"}) {
+        for (String s : new String[]{"", "-win", "-mac"}) {
             String script = "/missing-explicit-context" + s + ".html";
             testMissingExplicitContext(script);
         }
@@ -74,7 +74,7 @@ public class SightlyCompilerTest {
         PowerMockito.mockStatic(System.class);
         PowerMockito.when(System.lineSeparator()).thenReturn("\r\n");
 
-        for (String s : new String[] {"", "-win", "-mac"}) {
+        for (String s : new String[]{"", "-win", "-mac"}) {
             String script = "/missing-explicit-context" + s + ".html";
             testMissingExplicitContext(script);
         }
@@ -85,7 +85,7 @@ public class SightlyCompilerTest {
         PowerMockito.mockStatic(System.class);
         PowerMockito.when(System.lineSeparator()).thenReturn("\r");
 
-        for (String s : new String[] {"", "-win", "-mac"}) {
+        for (String s : new String[]{"", "-win", "-mac"}) {
             String script = "/missing-explicit-context" + s + ".html";
             testMissingExplicitContext(script);
         }
@@ -129,8 +129,8 @@ public class SightlyCompilerTest {
         }
 
         // doubles
-        double  doubleTestRange = 20.00;
-        for (double i = -1.00 * doubleTestRange; i < doubleTestRange; i+= 0.1) {
+        double doubleTestRange = 20.00;
+        for (double i = -1.00 * doubleTestRange; i < doubleTestRange; i += 0.1) {
             assertEquals(0, compileSource("${" + i + "}").getErrors().size());
         }
 
@@ -150,6 +150,17 @@ public class SightlyCompilerTest {
         assertEquals(0, compileSource("${1e+2}").getErrors().size());
     }
 
+    @Test
+    public void testUnknownExpressionOptions() {
+        assertEquals(0, compileSource("<sly data-sly-use.a=\"${com.example.Pojo @ param1=1, param2=2}\"></sly>").getWarnings().size());
+        assertEquals(0, compileSource("<sly data-sly-call=\"${myTemplate @ param1=1, param2=2}\"></sly>").getWarnings().size());
+        assertEquals(0, compileSource("<template data-sly-template.myTemplate=\"${@ param1, param2}\"></template>").getWarnings().size());
+        assertEquals(1, compileSource("${currentPage.title @ contex = 'scriptString'}").getWarnings().size());
+        assertEquals(1, compileSource("${a @ unknownOption}").getWarnings().size());
+        assertEquals(2, compileSource("${a @ unknownOption1, unknownOption2}").getWarnings().size());
+        assertEquals(1, compileSource("<div data-sly-text='${\"text\" @ i18nn}'>Replaced</div>").getWarnings().size());
+    }
+
     private CompilationResult compileFile(final String file) {
         InputStream stream = this.getClass().getResourceAsStream(file);
         final Reader reader = new InputStreamReader(stream);
diff --git a/src/test/java/org/apache/sling/scripting/sightly/impl/frontend/ExpressionWrapperTest.java b/src/test/java/org/apache/sling/scripting/sightly/impl/frontend/ExpressionWrapperTest.java
index 4eea958..a713265 100644
--- a/src/test/java/org/apache/sling/scripting/sightly/impl/frontend/ExpressionWrapperTest.java
+++ b/src/test/java/org/apache/sling/scripting/sightly/impl/frontend/ExpressionWrapperTest.java
@@ -33,6 +33,7 @@ import org.apache.sling.scripting.sightly.compiler.expression.nodes.NullLiteral;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.NumericConstant;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.RuntimeCall;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.StringConstant;
+import org.apache.sling.scripting.sightly.impl.compiler.PushStream;
 import org.apache.sling.scripting.sightly.impl.compiler.frontend.ExpressionWrapper;
 import org.apache.sling.scripting.sightly.impl.compiler.frontend.Interpolation;
 import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext;
@@ -70,7 +71,7 @@ public class ExpressionWrapperTest {
         options.put(I18nFilter.LOCALE_OPTION, new StringConstant("de"));
         options.put(I18nFilter.I18N_OPTION, NullLiteral.INSTANCE);
         interpolation.addExpression(new Expression(new StringConstant("hello"), options));
-        ExpressionWrapper wrapper = new ExpressionWrapper(filters);
+        ExpressionWrapper wrapper = new ExpressionWrapper(new PushStream(), filters);
         Expression result = wrapper.transform(interpolation, MarkupContext.TEXT, ExpressionContext.TEXT);
         List<ExpressionNode> xssArguments = runOptionsAndXSSAssertions(result, 1);
         RuntimeCall i18n = (RuntimeCall) xssArguments.get(0);
@@ -86,7 +87,7 @@ public class ExpressionWrapperTest {
         formatArray.add(new StringConstant("Doe"));
         options.put(FormatFilter.FORMAT_OPTION, new ArrayLiteral(formatArray));
         interpolation.addExpression(new Expression(new StringConstant("Hello {0} {1}"), options));
-        ExpressionWrapper wrapper = new ExpressionWrapper(filters);
+        ExpressionWrapper wrapper = new ExpressionWrapper(new PushStream(), filters);
         Expression result = wrapper.transform(interpolation, MarkupContext.TEXT, ExpressionContext.TEXT);
         List<ExpressionNode> xssArguments = runOptionsAndXSSAssertions(result, 0);
         RuntimeCall format = (RuntimeCall) xssArguments.get(0);
@@ -102,7 +103,7 @@ public class ExpressionWrapperTest {
         array.add(new NumericConstant(0));
         array.add(new NumericConstant(1));
         interpolation.addExpression(new Expression(new ArrayLiteral(array), options));
-        ExpressionWrapper wrapper = new ExpressionWrapper(filters);
+        ExpressionWrapper wrapper = new ExpressionWrapper(new PushStream(), filters);
         Expression result = wrapper.transform(interpolation, MarkupContext.TEXT, ExpressionContext.TEXT);
         List<ExpressionNode> xssArguments = runOptionsAndXSSAssertions(result, 0);
         RuntimeCall join = (RuntimeCall) xssArguments.get(0);
@@ -138,7 +139,7 @@ public class ExpressionWrapperTest {
         interpolation.addExpression(
                 new Expression(new StringConstant("http://www.example.com/resource.selector.extension/suffix#fragment?param=value"),
                         options));
-        ExpressionWrapper wrapper = new ExpressionWrapper(filters);
+        ExpressionWrapper wrapper = new ExpressionWrapper(new PushStream(), filters);
         Expression result = wrapper.transform(interpolation, MarkupContext.TEXT, ExpressionContext.TEXT);
         List<ExpressionNode> xssArguments = runOptionsAndXSSAssertions(result, 0);
         RuntimeCall join = (RuntimeCall) xssArguments.get(0);