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/06/05 11:45:04 UTC

[sling-org-apache-sling-scripting-sightly-compiler] branch master 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 master
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/master by this push:
     new eb8891e  SLING-6779 - The HTL compiler and Maven Plugin should warn when using potentially invalid options
eb8891e is described below

commit eb8891e89caef2993f4cdab0fbe764a7695ec0e8
Author: Radu Cotescu <17...@users.noreply.github.com>
AuthorDate: Wed Jun 5 13:44:58 2019 +0200

    SLING-6779 - The HTL compiler and Maven Plugin should warn when using potentially invalid options
    
    * collected all known options from filters and plugins
    * 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
    * added the possibility to configure the compiler to ignore certain additional options
    * removed unused imports
    * removed unneeded powermock instrumentation for the SightlyCompiler since it messed up code
    coverage reports
---
 bnd.bnd                                            |  2 +
 pom.xml                                            | 61 ++++++----------------
 .../sightly/compiler/SightlyCompiler.java          | 55 ++++++++++++++++---
 .../scripting/sightly/compiler/package-info.java   |  2 +-
 .../sightly/impl/compiler/CompilerFrontend.java    | 33 ------------
 .../impl/compiler/frontend/CompilerContext.java    |  6 +--
 .../impl/compiler/frontend/ExpressionWrapper.java  | 21 ++++++--
 .../impl/compiler/frontend/SimpleFrontend.java     | 11 ++--
 .../sightly/impl/filter/AbstractFilter.java        | 50 +++++++++++++++++-
 .../sightly/impl/filter/ExpressionContext.java     | 49 +++++++++++------
 .../scripting/sightly/impl/filter/Filter.java      | 32 ++++++++++++
 .../sightly/impl/filter/FormatFilter.java          | 23 +++-----
 .../scripting/sightly/impl/filter/I18nFilter.java  | 21 ++++----
 .../scripting/sightly/impl/filter/JoinFilter.java  | 17 +++---
 .../sightly/impl/filter/URIManipulationFilter.java | 46 ++++++++--------
 .../scripting/sightly/impl/filter/XSSFilter.java   | 14 ++---
 .../sightly/impl/html/dom/MarkupHandler.java       |  4 +-
 .../sightly/impl/compiler/SightlyCompilerTest.java | 22 +++++---
 .../impl/frontend/ExpressionWrapperTest.java       |  9 ++--
 19 files changed, 280 insertions(+), 198 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..3e5f0d1 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>
 
@@ -37,8 +37,7 @@
         The versioning scheme defined here corresponds to SLING-7406 (<module_version>-<htl_specification_version>). Take care when
         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>
+    <version>1.2.0-1.4.0-SNAPSHOT</version>
 
     <name>Apache Sling Scripting HTL Compiler</name>
 
@@ -56,7 +55,7 @@
 
     <properties>
         <antlr.version>4.7.1</antlr.version>
-        <jacoco.maven.plugin.version>0.7.9</jacoco.maven.plugin.version>
+        <jacoco.maven.plugin.version>0.8.3</jacoco.maven.plugin.version>
     </properties>
 
     <!-- ======================================================================= -->
@@ -65,35 +64,6 @@
     <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.antlr</groupId>
                 <artifactId>antlr4-maven-plugin</artifactId>
                 <version>${antlr.version}</version>
@@ -126,9 +96,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>
@@ -249,6 +219,11 @@
             <scope>provided</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+        </dependency>
+
         <!-- testing -->
         <dependency>
             <groupId>junit</groupId>
@@ -257,20 +232,14 @@
         </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>
     </dependencies>
diff --git a/src/main/java/org/apache/sling/scripting/sightly/compiler/SightlyCompiler.java b/src/main/java/org/apache/sling/scripting/sightly/compiler/SightlyCompiler.java
index 3216b7c..bd16d85 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/compiler/SightlyCompiler.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/compiler/SightlyCompiler.java
@@ -21,16 +21,18 @@ package org.apache.sling.scripting.sightly.compiler;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.scripting.sightly.compiler.backend.BackendCompiler;
 import org.apache.sling.scripting.sightly.compiler.commands.CommandStream;
 import org.apache.sling.scripting.sightly.impl.compiler.CompilationResultImpl;
-import org.apache.sling.scripting.sightly.impl.compiler.CompilerFrontend;
 import org.apache.sling.scripting.sightly.impl.compiler.CompilerMessageImpl;
 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.compiler.debug.SanityChecker;
 import org.apache.sling.scripting.sightly.impl.compiler.frontend.SimpleFrontend;
 import org.apache.sling.scripting.sightly.impl.compiler.optimization.CoalescingWrites;
@@ -40,6 +42,7 @@ import org.apache.sling.scripting.sightly.impl.compiler.optimization.StreamTrans
 import org.apache.sling.scripting.sightly.impl.compiler.optimization.SyntheticMapRemoval;
 import org.apache.sling.scripting.sightly.impl.compiler.optimization.UnusedVariableRemoval;
 import org.apache.sling.scripting.sightly.impl.compiler.optimization.reduce.ConstantFolding;
+import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext;
 import org.apache.sling.scripting.sightly.impl.filter.Filter;
 import org.apache.sling.scripting.sightly.impl.filter.FormatFilter;
 import org.apache.sling.scripting.sightly.impl.filter.I18nFilter;
@@ -60,6 +63,7 @@ import org.apache.sling.scripting.sightly.impl.plugin.TestPlugin;
 import org.apache.sling.scripting.sightly.impl.plugin.TextPlugin;
 import org.apache.sling.scripting.sightly.impl.plugin.UnwrapPlugin;
 import org.apache.sling.scripting.sightly.impl.plugin.UsePlugin;
+import org.jetbrains.annotations.NotNull;
 import org.osgi.service.component.annotations.Component;
 
 /**
@@ -74,10 +78,17 @@ import org.osgi.service.component.annotations.Component;
 )
 public final class SightlyCompiler {
 
-    private StreamTransformer optimizer;
-    private CompilerFrontend frontend;
+    private final StreamTransformer optimizer;
+    private final SimpleFrontend frontend;
+    private final Set<String> knownExpressionOptions;
+    private final List<Plugin> plugins;
+    private final List<Filter> filters;
 
     public SightlyCompiler() {
+        this(Collections.emptySet());
+    }
+
+    private SightlyCompiler(Set<String> additionalExpresionOptions) {
         ArrayList<StreamTransformer> transformers = new ArrayList<>(5);
         transformers.add(ConstantFolding.transformer());
         transformers.add(DeadCodeRemoval.transformer());
@@ -87,7 +98,7 @@ public final class SightlyCompiler {
         optimizer = new SequenceStreamTransformer(transformers);
 
         // register plugins
-        final List<Plugin> plugins = new ArrayList<>(12);
+        plugins = new ArrayList<>(12);
         plugins.add(new AttributePlugin());
         plugins.add(new CallPlugin());
         plugins.add(new ElementPlugin());
@@ -104,15 +115,43 @@ public final class SightlyCompiler {
         Collections.sort(plugins);
 
         // register filters
-        final List<Filter> filters = new ArrayList<>(5);
+        filters = new ArrayList<>(5);
         filters.add(I18nFilter.getInstance());
         filters.add(FormatFilter.getInstance());
         filters.add(JoinFilter.getInstance());
         filters.add(URIManipulationFilter.getInstance());
         filters.add(XSSFilter.getInstance());
         Collections.sort(filters);
+        knownExpressionOptions = new HashSet<>(additionalExpresionOptions);
+        for (Filter filter : filters) {
+            knownExpressionOptions.addAll(filter.getOptions());
+        }
+        knownExpressionOptions.add(Syntax.CONTEXT_OPTION);
+        for (ExpressionContext context : ExpressionContext.values()) {
+            knownExpressionOptions.addAll(context.getOptions());
+        }
+        frontend = new SimpleFrontend(plugins, filters, knownExpressionOptions);
+    }
 
-        frontend = new SimpleFrontend(plugins, filters);
+    /**
+     * <p>
+     * Returns an instance of the {@code SightlyCompiler} with the provided {@code options} added to the list of known expression options.
+     * </p>
+     * <p>
+     * The compiler builds internally a set of allowed options from the options permitted by the expressions or plugins. As soon as an
+     * expression contains an unknown option the compiler logs a warning. Since the compiler works with dynamically registered
+     * {@link org.apache.sling.scripting.sightly.compiler.expression.nodes.RuntimeCall}s, some of them can work with additional expression
+     * options, not known to the compiler.
+     * </p>
+     * <p>
+     * <strong>NOTE</strong>: The {@code data-sly-template, data-sly-call, data-sly-use} plugins allow arbitrary options which define
+     * parameters and do not generate warnings.
+     * </p>
+     *
+     * @param options the options to add to the compiler's set of known expression options
+     */
+    public static SightlyCompiler withKnownExpressionOptions(@NotNull Set<String> options) {
+        return new SightlyCompiler(options);
     }
 
     /**
@@ -176,7 +215,7 @@ public final class SightlyCompiler {
                 String textBeforeError = documentFragment.substring(0, offendingInputIndex);
                 int line = lineOffset;
                 int lastNewLineIndex = 0;
-                for (String s : new String[] {"\r\n", "\r", "\n"}) {
+                for (String s : new String[]{"\r\n", "\r", "\n"}) {
                     int l = textBeforeError.split(s, -1).length - 1;
                     if (l + lineOffset > line) {
                         line = l + lineOffset;
@@ -188,7 +227,7 @@ public final class SightlyCompiler {
                 }
                 int column = textBeforeError.substring(lastNewLineIndex).length();
                 if (column != columnOffset) {
-                    column +=columnOffset;
+                    column += columnOffset;
                 }
                 return new ScriptError(line, column, longestContiguousOffendingSequence + ": " + message);
             }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/compiler/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/compiler/package-info.java
index 6ffe873..6272741 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/compiler/package-info.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/compiler/package-info.java
@@ -21,7 +21,7 @@
  * The {@code org.apache.sling.scripting.sightly.compiler} package defines the API exposed by the
  * {@link org.apache.sling.scripting.sightly.compiler.SightlyCompiler}.
  */
-@Version("1.0.1")
+@Version("1.1.0")
 package org.apache.sling.scripting.sightly.compiler;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/CompilerFrontend.java b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/CompilerFrontend.java
deleted file mode 100644
index d3cfd52..0000000
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/CompilerFrontend.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ******************************************************************************/
-package org.apache.sling.scripting.sightly.impl.compiler;
-
-/**
- * HTL compiler frontend.
- */
-public interface CompilerFrontend {
-
-    /**
-     * Compile the source code to a stream of commands
-     * @param stream the output stream
-     * @param source the source code
-     */
-    void compile(PushStream stream, String source);
-
-}
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..6096c20 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
@@ -19,7 +19,6 @@
 package org.apache.sling.scripting.sightly.impl.compiler.frontend;
 
 import org.apache.sling.scripting.sightly.impl.compiler.PushStream;
-import org.apache.sling.scripting.sightly.impl.compiler.Syntax;
 import org.apache.sling.scripting.sightly.compiler.expression.Expression;
 import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext;
 import org.apache.sling.scripting.sightly.compiler.expression.MarkupContext;
@@ -47,10 +46,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..78f03ff 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
@@ -21,16 +21,19 @@ package org.apache.sling.scripting.sightly.impl.compiler.frontend;
 import java.util.ArrayList;
 import java.util.HashMap;
 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.MarkupContext;
 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;
 
 /**
  * This object wraps expressions in filter applications depending on options.
@@ -38,9 +41,13 @@ 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, Set<String> knownExpressionOptions) {
+        this.stream = stream;
         this.filters = filters;
+        this.knownOptions = knownExpressionOptions;
     }
 
     public Expression transform(Interpolation interpolation, MarkupContext markupContext, ExpressionContext expressionContext) {
@@ -51,13 +58,21 @@ public class ExpressionWrapper {
                 nodes.add(new StringConstant(fragment.getText()));
             } else {
                 Expression expression = fragment.getExpression();
+                if (AbstractFilter.NON_PARAMETRIZABLE_CONTEXTS.contains(expressionContext)) {
+                    expression.getOptions().keySet().stream().filter(option -> !knownOptions.contains(option)).forEach(
+                        unknownOption ->
+                        stream.warn(
+                            new PushStream.StreamMessage(String.format("Unknown option '%s'.", unknownOption), 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/compiler/frontend/SimpleFrontend.java b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/SimpleFrontend.java
index 9de45da..6759893 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/SimpleFrontend.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/SimpleFrontend.java
@@ -21,8 +21,8 @@ package org.apache.sling.scripting.sightly.impl.compiler.frontend;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
-import org.apache.sling.scripting.sightly.impl.compiler.CompilerFrontend;
 import org.apache.sling.scripting.sightly.impl.compiler.PushStream;
 import org.apache.sling.scripting.sightly.impl.filter.Filter;
 import org.apache.sling.scripting.sightly.impl.html.dom.DocumentParser;
@@ -32,22 +32,23 @@ import org.apache.sling.scripting.sightly.impl.plugin.Plugin;
 /**
  * DOM-based compiler implementation
  */
-public class SimpleFrontend implements CompilerFrontend {
+public class SimpleFrontend {
 
     private final Map<String, Plugin> plugins;
     private final List<Filter> filters;
+    private final Set<String> knownExpressionOptions;
 
-    public SimpleFrontend(List<Plugin> plugins, List<Filter> filters) {
+    public SimpleFrontend(List<Plugin> plugins, List<Filter> filters, Set<String> knownExpressionOptions) {
         this.plugins = new HashMap<>();
         this.filters = filters;
+        this.knownExpressionOptions = knownExpressionOptions;
         for (Plugin plugin : plugins) {
             this.plugins.put(plugin.name(), plugin);
         }
     }
 
-    @Override
     public void compile(PushStream stream, String source) {
-        MarkupHandler markupHandler = new MarkupHandler(stream, plugins, filters);
+        MarkupHandler markupHandler = new MarkupHandler(stream, plugins, filters, knownExpressionOptions);
         DocumentParser.parse(source, markupHandler);
     }
 }
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 f72ded7..7bb94c7 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,8 +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 org.apache.sling.scripting.sightly.compiler.expression.Expression;
 import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
@@ -27,6 +31,24 @@ 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);
+    }
+
+    private final Set<ExpressionContext> applicableContexts;
+    private final Set<String> options;
+    private final Set<String> requiredOptions;
+
+    AbstractFilter(Set<ExpressionContext> applicableContexts, Set<String> options, Set<String> requiredOptions) {
+        this.applicableContexts = applicableContexts;
+        this.options = options;
+        this.requiredOptions = requiredOptions;
+    }
 
     @Override
     public int priority() {
@@ -43,6 +65,17 @@ public abstract class AbstractFilter implements Filter {
         return 1;
     }
 
+    @Override
+    public Expression apply(Expression expression, ExpressionContext expressionContext) {
+        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.
      *
@@ -50,7 +83,7 @@ public abstract class AbstractFilter implements Filter {
      * @param options    the options of interest for the {@link Filter}
      * @return a map with the retrieved options; the map can be empty if none of the options were found
      */
-    protected Map<String, ExpressionNode> getFilterOptions(Expression expression, String... options) {
+    private Map<String, ExpressionNode> getFilterOptions(Expression expression, Set<String> options) {
         Map<String, ExpressionNode> collector = new HashMap<>();
         for (String option : options) {
             ExpressionNode optionNode = expression.removeOption(option);
@@ -70,4 +103,19 @@ public abstract class AbstractFilter implements Filter {
     public boolean equals(Object obj) {
         return obj != null && this.getClass().equals(obj.getClass());
     }
+
+    @Override
+    public Set<String> getOptions() {
+        return options;
+    }
+
+    @Override
+    public Set<String> getRequiredOptions() {
+        return requiredOptions;
+    }
+
+    @Override
+    public Set<ExpressionContext> getApplicableContexts() {
+        return applicableContexts;
+    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/ExpressionContext.java b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/ExpressionContext.java
index 531e813..2c3e5fa 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/filter/ExpressionContext.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/filter/ExpressionContext.java
@@ -18,6 +18,13 @@
  ******************************************************************************/
 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;
+
 /**
  * Defines a context for the {@link Expression} that will be processed by a {@link Filter}. The context can then be used by filters to
  * further enhance the decision mechanism for their processing.
@@ -25,27 +32,34 @@ package org.apache.sling.scripting.sightly.impl.filter;
 public enum ExpressionContext {
 
     // Plugin contexts
-    PLUGIN_DATA_SLY_USE,
-    PLUGIN_DATA_SLY_TEXT,
-    PLUGIN_DATA_SLY_ATTRIBUTE,
-    PLUGIN_DATA_SLY_ELEMENT,
-    PLUGIN_DATA_SLY_TEST,
-    PLUGIN_DATA_SLY_SET,
-    PLUGIN_DATA_SLY_LIST,
-    PLUGIN_DATA_SLY_REPEAT,
-    PLUGIN_DATA_SLY_INCLUDE,
-    PLUGIN_DATA_SLY_RESOURCE,
-    PLUGIN_DATA_SLY_TEMPLATE,
-    PLUGIN_DATA_SLY_CALL,
-    PLUGIN_DATA_SLY_UNWRAP,
+    PLUGIN_DATA_SLY_USE(Collections.emptySet()),
+    PLUGIN_DATA_SLY_TEXT(Collections.emptySet()),
+    PLUGIN_DATA_SLY_ATTRIBUTE(Collections.emptySet()),
+    PLUGIN_DATA_SLY_ELEMENT(Collections.emptySet()),
+    PLUGIN_DATA_SLY_TEST(Collections.emptySet()),
+    PLUGIN_DATA_SLY_SET(Collections.emptySet()),
+    PLUGIN_DATA_SLY_LIST(new HashSet<>(Arrays.asList("begin", "step", "end"))),
+    PLUGIN_DATA_SLY_REPEAT(new HashSet<>(Arrays.asList("begin", "step", "end"))),
+    PLUGIN_DATA_SLY_INCLUDE(new HashSet<>(Arrays.asList("appendPath", "prependPath", "file"))),
+    PLUGIN_DATA_SLY_RESOURCE(new HashSet<>(Arrays.asList("appendPath", "prependPath", "file", "selectors", "addSelectors",
+            "removeSelectors", "resourceType"))),
+    PLUGIN_DATA_SLY_TEMPLATE(Collections.emptySet()),
+    PLUGIN_DATA_SLY_CALL(Collections.emptySet()),
+    PLUGIN_DATA_SLY_UNWRAP(Collections.emptySet()),
 
     // Markup contexts
-    ELEMENT,
-    TEXT,
-    ATTRIBUTE;
+    ELEMENT(Collections.emptySet()),
+    TEXT(Collections.emptySet()),
+    ATTRIBUTE(Collections.emptySet());
 
     private static final String PLUGIN_PREFIX = "PLUGIN_DATA_SLY_";
 
+    private final Set<String> options;
+
+    ExpressionContext(Set<String> options) {
+        this.options = options;
+    }
+
     /**
      * Retrieves the context for the plugin specified by {@code pluginName}.
      *
@@ -57,4 +71,7 @@ public enum ExpressionContext {
         return valueOf(PLUGIN_PREFIX + pluginName.toUpperCase());
     }
 
+    public Set<String> getOptions() {
+        return options;
+    }
 }
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 be239ef..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,6 +18,11 @@
  ******************************************************************************/
 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;
 
 /**
@@ -45,4 +50,31 @@ public interface Filter extends Comparable<Filter> {
      */
     int priority();
 
+    /**
+     * Provides the option names this {@code Filter} will process.
+     *
+     * @return a set of option names
+     */
+    default Set<String> getOptions() {
+        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 1962dc1..21ab92a 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
@@ -18,6 +18,11 @@
  ******************************************************************************/
 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 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.MapLiteral;
@@ -30,7 +35,6 @@ public class FormatFilter extends AbstractFilter {
 
     public static final String FORMAT_OPTION = "format";
     public static final String TYPE_OPTION = "type";
-    public static final String FORMAT_LOCALE_OPTION = "formatLocale";
     public static final String TIMEZONE_OPTION = "timezone";
 
     private static final class FormatFilterLoader {
@@ -38,6 +42,7 @@ public class FormatFilter extends AbstractFilter {
     }
 
     private FormatFilter() {
+        super(NON_PARAMETRIZABLE_CONTEXTS, new HashSet<>(Arrays.asList(FORMAT_OPTION, TYPE_OPTION, I18nFilter.LOCALE_OPTION, TIMEZONE_OPTION)), Collections.singleton(FORMAT_OPTION));
     }
 
     public static FormatFilter getInstance() {
@@ -45,21 +50,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
-        if (!expression.containsOption(FORMAT_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.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(options));
         return expression.withNode(translation);
     }
 }
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 5275b13..9b2134f 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
@@ -18,6 +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.Map;
 
 import org.apache.sling.scripting.sightly.compiler.expression.Expression;
@@ -40,9 +43,7 @@ public final class I18nFilter extends AbstractFilter {
     }
 
     private I18nFilter() {
-        if (I18nFilterLoader.INSTANCE != null) {
-            throw new IllegalStateException("INSTANCE was already defined.");
-        }
+        super(NON_PARAMETRIZABLE_CONTEXTS, new HashSet<>(Arrays.asList(I18N_OPTION, HINT_OPTION, LOCALE_OPTION, BASENAME_OPTION)), Collections.singleton(I18N_OPTION));
         priority = 90;
     }
 
@@ -51,16 +52,12 @@ public final class I18nFilter extends AbstractFilter {
     }
 
     @Override
-    public Expression apply(Expression expression, ExpressionContext expressionContext) {
-        if (!expression.containsOption(I18N_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.I18N, expression.getRoot(), new MapLiteral(options));
+        if (options.containsKey(LOCALE_OPTION)) {
+            // put back the locale option, in case it will be used by the FormatFilter
+            expression.getOptions().put(LOCALE_OPTION, options.get(LOCALE_OPTION));
         }
-        Map <String, ExpressionNode> options = getFilterOptions(expression, HINT_OPTION, LOCALE_OPTION, BASENAME_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);
     }
 }
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..b84c8a2 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,15 +32,14 @@ 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.");
-        }
+        super(NON_PARAMETRIZABLE_CONTEXTS, OPTIONS, OPTIONS);
     }
 
     public static JoinFilter getInstance() {
@@ -44,13 +47,9 @@ public class JoinFilter extends AbstractFilter {
     }
 
     @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));
+                new RuntimeCall(RuntimeCall.JOIN, expression.getRoot(), options.get(JOIN_OPTION));
         return expression.withNode(translation);
     }
 }
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..028d795 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,11 @@
  ******************************************************************************/
 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;
 
 import org.apache.sling.scripting.sightly.compiler.expression.Expression;
 import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
@@ -48,15 +52,26 @@ public class URIManipulationFilter extends AbstractFilter {
     public static final String ADD_QUERY = "addQuery";
     public static final String REMOVE_QUERY = "removeQuery";
 
+    private static final Set<ExpressionContext> APPLICABLE_CONTEXTS;
+
+    static {
+        Set<ExpressionContext> applicableContexts = new HashSet<>(Arrays.asList(ExpressionContext.values()));
+        applicableContexts.remove(ExpressionContext.PLUGIN_DATA_SLY_TEMPLATE);
+        applicableContexts.remove(ExpressionContext.PLUGIN_DATA_SLY_CALL);
+        applicableContexts.remove(ExpressionContext.PLUGIN_DATA_SLY_RESOURCE);
+        applicableContexts.remove(ExpressionContext.PLUGIN_DATA_SLY_INCLUDE);
+        APPLICABLE_CONTEXTS = Collections.unmodifiableSet(applicableContexts);
+    }
+
 
     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.");
-        }
+        super(APPLICABLE_CONTEXTS, 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)), Collections.emptySet());
     }
 
     public static URIManipulationFilter getInstance() {
@@ -64,27 +79,12 @@ public class URIManipulationFilter extends AbstractFilter {
     }
 
     @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;
     }
-
-
 }
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..a809300 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.Collections;
+import java.util.Map;
+
 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,7 @@ public class XSSFilter extends AbstractFilter {
     }
 
     private XSSFilter() {
-        if (XSSFilterLoader.INSTANCE != null) {
-            throw new IllegalStateException("INSTANCE was already defined.");
-        }
+        super(NON_PARAMETRIZABLE_CONTEXTS, Collections.emptySet(), Collections.emptySet());
         priority = 110;
     }
 
@@ -44,16 +45,11 @@ 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));
         }
         return expression;
     }
-
 }
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..2a58176 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
@@ -75,10 +75,10 @@ public class MarkupHandler {
     private static final Set<String> URI_ATTRIBUTES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("action", "cite",
             "data", "formaction", "href",  "manifest", "poster", "src")));
 
-    public MarkupHandler(PushStream stream, Map<String, Plugin> pluginRegistry, List<Filter> filters) {
+    public MarkupHandler(PushStream stream, Map<String, Plugin> pluginRegistry, List<Filter> filters, Set<String> knownExpressionOptions) {
         this.stream = stream;
         this.pluginRegistry = pluginRegistry;
-        this.expressionWrapper = new ExpressionWrapper(filters);
+        this.expressionWrapper = new ExpressionWrapper(stream, filters, knownExpressionOptions);
         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..902649c 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
@@ -38,7 +38,6 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(PowerMockRunner.class)
-@PrepareForTest(SightlyCompiler.class)
 public class SightlyCompilerTest {
 
     private SightlyCompiler compiler = new SightlyCompiler();
@@ -51,7 +50,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 +73,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 +84,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 +128,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 +149,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..ebeafa9 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, Collections.emptySet());
         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, Collections.emptySet());
         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, Collections.emptySet());
         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, Collections.emptySet());
         Expression result = wrapper.transform(interpolation, MarkupContext.TEXT, ExpressionContext.TEXT);
         List<ExpressionNode> xssArguments = runOptionsAndXSSAssertions(result, 0);
         RuntimeCall join = (RuntimeCall) xssArguments.get(0);