linter
diff --git a/build.xml b/build.xml
index 82af803..0e57eec 100644
--- a/build.xml
+++ b/build.xml
@@ -71,7 +71,7 @@
 
     <target name="swf" depends="sdk, javadoc, tests" description="Builds SWF compiler, builds Royale Javadoc, and runs SWF compiler tests."/>
 
-    <target name="main" depends="swf, jx, oem, debugger, formatter, anttasks, royaleunit.anttasks" description="Builds Royale SWF compiler, then Royale JS Transpiler"/>
+    <target name="main" depends="swf, jx, oem, debugger, formatter, linter, anttasks, royaleunit.anttasks" description="Builds Royale SWF compiler, then Royale JS Transpiler"/>
 
     <target name="jx" depends="compiler.jx, compiler.jx.tests" description="Builds Royale JS Transpiler" />
     
@@ -104,6 +104,10 @@
     <target name="formatter" description="Builds formatter JAR">
         <ant dir="formatter" target="main"/>
     </target>
+
+    <target name="linter" description="Builds linter JAR">
+        <ant dir="linter" target="main"/>
+    </target>
     
     <target name="compiler.jx" description="Builds Royale JS Transpiler.">
         <ant dir="compiler-jx" target="main"/>
@@ -147,6 +151,7 @@
         <ant dir="royaleunit-ant-tasks" target="clean"/>
         <ant dir="debugger" target="clean"/>
         <ant dir="formatter" target="clean"/>
+        <ant dir="linter" target="clean"/>
     </target>
 
     <target name="wipe" description="Wipes out everything that didn't come from Git.">
@@ -177,6 +182,7 @@
         <ant dir="royaleunit-ant-tasks" target="wipe"/>
         <ant dir="debugger" target="clean"/>
         <ant dir="formatter" target="clean"/>
+        <ant dir="linter" target="clean"/>
         <delete dir="${basedir}/out" failonerror="false" includeEmptyDirs="true"/>
         <delete dir="${basedir}/temp" failonerror="false" includeEmptyDirs="true"/>
         <!-- remove legacy folders if they exist -->
@@ -297,6 +303,9 @@
         <!-- formatter -->
         <antcall target="stage-formatter"/>
         
+        <!-- linter -->
+        <antcall target="stage-linter"/>
+        
         <!-- flex-compiler-oem and fdb -->
         <antcall target="stage-fb-integration"/>
         
@@ -470,6 +479,24 @@
         </copy>
     </target>
 
+    <target name="stage-linter">
+        <copy todir="${staging-dir}/linter" includeEmptyDirs="false">
+            <fileset dir="${basedir}/linter">
+                <include name="**"/>
+                <exclude name=".classpath" />
+                <exclude name=".project" />
+                <exclude name=".settings/**" />
+                <exclude name="in/**"/>
+                <exclude name="lib/**"/>
+                <exclude name="target/classes/**"/>
+                <exclude name="target/test-classes/**"/>
+                <exclude name="target/junit-temp/**"/>
+                <exclude name="target/junit-reports/**"/>
+                <exclude name="**/unittest.properties" />
+            </fileset>
+        </copy>
+    </target>
+
     <target name="stage-fb-integration">
         <copy todir="${staging-dir}/debugger" includeEmptyDirs="false">
             <fileset dir="${basedir}/debugger">
diff --git a/compiler/src/assembly/scripts/aslint b/compiler/src/assembly/scripts/aslint
new file mode 100644
index 0000000..b23185a
--- /dev/null
+++ b/compiler/src/assembly/scripts/aslint
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+################################################################################
+##
+##  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.
+##
+################################################################################
+
+#
+# fasc shell script to launch compiler-asc.jar on OSX, Unix, or Cygwin.
+# In Windows Command Prompt, use fasc.bat instead.
+#
+
+if [ "x${ROYALE_COMPILER_HOME}" = "x" ]
+then
+    SCRIPT_HOME=`dirname "$0"`
+    ROYALE_COMPILER_HOME=${SCRIPT_HOME}/..
+else
+    echo Using Royale codebase: $ROYALE_COMPILER_HOME
+fi
+
+case `uname` in
+		CYGWIN*)
+			OS="Windows"
+		;;
+		*)
+			OS=Unix
+esac
+
+D32=''
+
+if [ $OS = "Windows" ]; then
+
+	ROYALE_COMPILER_HOME=`cygpath -m $ROYALE_COMPILER_HOME`
+
+elif [ $OS = "Unix" ]; then
+
+    check64="`java -version 2>&1 | grep -i 64-Bit`"
+    isOSX="`uname | grep -i Darwin`"
+    javaVersion="`java -version 2>&1 | awk -F '[ ".]+' 'NR==1 {print $3 "." $4}'`"
+    
+    if [ "$isOSX" != "" -a "$HOSTTYPE" = "x86_64" -a "$check64" != "" -a "$javaVersion" = "1.6" ]; then
+        D32='-d32'
+    fi
+fi
+
+VMARGS="-Xmx384m -Dsun.io.useCanonCaches=false "
+
+java $VMARGS $D32 $SETUP_SH_VMARGS -jar "${ROYALE_COMPILER_HOME}/lib/linter.jar" "$@"
diff --git a/compiler/src/assembly/scripts/aslint.bat b/compiler/src/assembly/scripts/aslint.bat
new file mode 100644
index 0000000..5bc7dc9
--- /dev/null
+++ b/compiler/src/assembly/scripts/aslint.bat
@@ -0,0 +1,26 @@
+@echo off
+
+rem
+rem Licensed to the Apache Software Foundation (ASF) under one or more
+rem contributor license agreements.  See the NOTICE file distributed with
+rem this work for additional information regarding copyright ownership.
+rem The ASF licenses this file to You under the Apache License, Version 2.0
+rem (the "License"); you may not use this file except in compliance with
+rem the License.  You may obtain a copy of the License at
+rem
+rem     http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+rem
+
+rem
+rem fasc.bat script to launch compiler-asc.jar in Windows Command Prompt.
+rem On OSX, Unix, or Cygwin, use the fasc shell script instead.
+rem
+
+@java -Dsun.io.useCanonCaches=false -Dapplication.home="%~dp0.." -Xms32m -Xmx512m -jar "%~dp0..\lib\linter.jar" %*
+
diff --git a/linter/build.xml b/linter/build.xml
new file mode 100644
index 0000000..40ad821
--- /dev/null
+++ b/linter/build.xml
@@ -0,0 +1,145 @@
+<?xml version="1.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.
+
+-->
+
+<project name="linter" default="main" basedir=".">
+
+    <!-- 
+
+        PROPERTIES
+
+    -->
+
+    <!-- The 'linter' property is the absolute path, with forward slashes, -->
+    <!-- to the 'linter' directory that contains this file. -->
+    <!-- All input paths are expressed as absolute paths starting with ${linter}. -->
+    <pathconvert property="linter" dirsep="/">
+        <path location="${basedir}"/>
+    </pathconvert>
+    
+    <!-- The 'compiler' property is the absolute path, with forward slashes, -->
+    <!-- to the 'compiler' directory that is a sibling of the parent folder of this file. -->
+    <!-- All input paths are expressed as absolute paths starting with ${compiler}. -->
+    <pathconvert property="compiler" dirsep="/">
+        <path location="${basedir}/../compiler"/>
+    </pathconvert>
+
+    <property name="sdk" value="${compiler}/lib"/>
+
+    <!-- Properties can be overridden locally by loading a local.properties file -->
+    <!-- Java 8 users probably need javadoc.params=-Xdoclint:none -->
+    <property file="${linter}/local.properties"/>
+    <property file="${basedir}/../env.properties"/>
+    
+    <property name="src.depend" value="true"/>
+
+    <!-- Options for <javac> tasks -->
+    <property name="javac.debug" value="true"/>
+    <property name="javac.deprecation" value="false"/>
+    <property name="linter.javac.src" value="1.8"/>
+
+    <!-- JAR manifest entries -->
+    <property name="manifest.sealed" value="false"/>
+    <property name="manifest.Implementation-Title" value="Apache Royale ActionScript Linter"/>
+    <property name="manifest.Implementation-Version" value="${release.version}"/>
+    <property name="manifest.Implementation-Vendor" value="Apache Software Foundation"/>
+    
+    <!-- label is set by CruiseControl script based on P4 label incrementer -->
+    <condition property="build.number" value="${env.BUILD_NUMBER}">
+        <isset property="env.BUILD_NUMBER"/>
+    </condition>
+    
+    <!--
+
+        linter
+
+    -->
+    
+    <target name="compile">
+        <mkdir dir="${linter}/target/classes"/>
+        <javac debug="${javac.debug}" deprecation="${javac.deprecation}" destdir="${linter}/target/classes" includeAntRuntime="true" includes="**/*.java"
+            source="${linter.javac.src}" target="${linter.javac.src}">
+            <compilerarg value="-Xlint:all,-path,-fallthrough"/>
+            <src path="${linter}/src/main/java"/>
+            <classpath>
+                <fileset dir="${linter}/../compiler/lib" includes="compiler.jar"/>
+            </classpath>
+        </javac>
+    </target>
+    
+    <target name="prebuild">
+        <mkdir dir="${linter}/target/classes"/>
+        <mkdir dir="${linter}/target/classes/META-INF"/>
+    </target>
+
+    <target name="jar" depends="prebuild,compile">
+        <mkdir dir="${sdk}"/>
+        <copy file="${basedir}/../LICENSE.base" tofile="${linter}/target/classes/META-INF/LICENSE"/>
+        <copy file="${basedir}/../NOTICE.base" tofile="${linter}/target/classes/META-INF/NOTICE"/>
+        <jar file="${sdk}/linter.jar" basedir="${linter}/target/classes" includes="**/*.properties,org/apache/**/*,META-INF/**/*">
+            <include name="META-INF/LICENSE"/>
+            <include name="META-INF/NOTICE"/>
+            <manifest>
+                <attribute name="Sealed" value="${manifest.sealed}"/>
+                <attribute name="Implementation-Title" value="${manifest.Implementation-Title}"/>
+                <attribute name="Implementation-Version" value="${manifest.Implementation-Version}.${build.number}"/>
+                <attribute name="Implementation-Vendor" value="${manifest.Implementation-Vendor}"/>
+                <attribute name="Main-Class" value="org.apache.royale.linter.LINTER"/>
+                <attribute name="Class-Path" value="compiler.jar"/>
+            </manifest>
+        </jar>
+    </target>
+    
+    <target name="main" depends="jar">
+        <!-- <antcall target="test"/> -->
+        <antcall target="jar-test"/>
+    </target>
+
+    <target name="test">
+        <ant dir="src/test"/>
+    </target>
+    
+    <target name="jar-test">
+        <echo>using linter.jar from ${sdk}</echo>
+        <java jar="${sdk}/linter.jar" fork="true" resultproperty="linter.result"/>
+        <fail message="Starting Failed">
+            <condition>
+                <not>
+                    <equals arg1="${linter.result}" arg2="1"/>
+                </not>
+            </condition>
+        </fail>
+    </target>
+    
+    <!--
+
+        CLEANUP
+
+    -->
+
+    <target name="clean" description="clean">
+        <delete dir="${linter}/target/classes"/>
+    </target>
+
+    <target name="wipe" depends="clean" description="Wipes everything that didn't come from Git.">
+        <delete dir="${linter}/target"/>
+    </target>
+
+ </project>
diff --git a/linter/pom.xml b/linter/pom.xml
new file mode 100644
index 0000000..9cada6c
--- /dev/null
+++ b/linter/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.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.
+
+--><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.royale.compiler</groupId>
+    <artifactId>royale-compiler-parent</artifactId>
+    <version>0.9.10-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>linter</artifactId>
+  <version>0.9.10-SNAPSHOT</version>
+
+  <name>Apache Royale: Compiler: AS3/MXML Code Linter</name>
+  <description>The Apache Royale Compiler AS3/MXML Code Linter</description>
+
+  <build>
+    <plugins>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-jar-plugin</artifactId>
+            <executions>
+                <execution>
+                    <id>default-jar</id>
+                    <configuration>
+                        <archive>
+                            <manifest>
+                              <mainClass>org.apache.royale.linter.LINTER</mainClass>
+                            </manifest>
+                            <manifestEntries>
+                                <!-- These paths are all defined the way the layout will be in the distribution -->
+                                <Class-Path>compiler.jar</Class-Path>
+                            </manifestEntries>
+                        </archive>
+                    </configuration>
+                </execution>
+                <execution>
+                    <id>default-test-jar</id>
+                    <goals>
+                        <goal>test-jar</goal>
+                    </goals>
+                </execution>
+            </executions>
+        </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <includes>
+            <include>**/Test*.java</include>
+          </includes>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.royale.compiler</groupId>
+      <artifactId>compiler</artifactId>
+      <version>0.9.10-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.10</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/linter/src/main/java/org/apache/royale/linter/ASLinter.java b/linter/src/main/java/org/apache/royale/linter/ASLinter.java
new file mode 100644
index 0000000..17f403c
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/ASLinter.java
@@ -0,0 +1,217 @@
+
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.royale.compiler.internal.parsing.as.ASParser;
+import org.apache.royale.compiler.internal.parsing.as.ASToken;
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.internal.parsing.as.IncludeHandler;
+import org.apache.royale.compiler.internal.parsing.as.RepairingTokenBuffer;
+import org.apache.royale.compiler.internal.parsing.as.StreamingASTokenizer;
+import org.apache.royale.compiler.internal.semantics.PostProcessStep;
+import org.apache.royale.compiler.internal.tree.as.FileNode;
+import org.apache.royale.compiler.internal.workspaces.Workspace;
+import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.problems.UnexpectedExceptionProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.linter.internal.BaseLinter;
+
+public class ASLinter extends BaseLinter {
+	private static final String LINTER_TAG_OFF = "@linter:off";
+	private static final String LINTER_TAG_ON = "@linter:on";
+
+	public ASLinter(LinterSettings settings) {
+		super(settings);
+	}
+
+	public void lint(String filePath, String text, Collection<ICompilerProblem> allProblems) {
+		if (allProblems == null) {
+			allProblems = new ArrayList<ICompilerProblem>();
+		}
+
+		List<ICompilerProblem> fileProblems = new ArrayList<ICompilerProblem>();
+		try {
+			StringReader textReader = new StringReader(text);
+			StreamingASTokenizer tokenizer = null;
+			ASToken[] streamingTokens = null;
+			try {
+				tokenizer = StreamingASTokenizer.createForRepairingASTokenizer(textReader, filePath,
+						IncludeHandler.creatDefaultIncludeHandler());
+				tokenizer.setCollectComments(true);
+				tokenizer.setFollowIncludes(false);
+				streamingTokens = tokenizer.getTokens(textReader);
+			} finally {
+				IOUtils.closeQuietly(textReader);
+				IOUtils.closeQuietly(tokenizer);
+			}
+
+			if (tokenizer.hasTokenizationProblems()) {
+				fileProblems.addAll(tokenizer.getTokenizationProblems());
+			}
+
+			if (!settings.ignoreProblems && hasErrors(fileProblems)) {
+				return;
+			}
+
+			// temporarily remove the comments from the token list because ASParser
+			// doesn't know how to deal with them properly.
+			// we'll add them back at the same locations after the parser is done.
+			List<ASToken> comments = new ArrayList<ASToken>();
+			List<ASToken> streamingTokensList = new ArrayList<ASToken>();
+			for (ASToken token : streamingTokens) {
+				if (token.getType() == ASTokenTypes.HIDDEN_TOKEN_SINGLE_LINE_COMMENT
+						|| token.getType() == ASTokenTypes.HIDDEN_TOKEN_MULTI_LINE_COMMENT) {
+					comments.add(token);
+				} else {
+					streamingTokensList.add(token);
+				}
+			}
+
+			Workspace workspace = new Workspace();
+			RepairingTokenBuffer buffer = new RepairingTokenBuffer(streamingTokensList.toArray(new ASToken[0]));
+			ASParser parser = new ASParser(workspace, buffer);
+			FileNode node = new FileNode(workspace);
+			try {
+				parser.parseFile(node, EnumSet.of(PostProcessStep.CALCULATE_OFFSETS));
+			} catch (Exception e) {
+				parser = null;
+				fileProblems.add(new UnexpectedExceptionProblem(e));
+				return;
+			}
+
+			if (tokenizer.hasTokenizationProblems()) {
+				fileProblems.addAll(tokenizer.getTokenizationProblems());
+			}
+
+			if (parser.getSyntaxProblems().size() > 0) {
+				fileProblems.addAll(parser.getSyntaxProblems());
+			}
+
+			if (!settings.ignoreProblems && hasErrors(fileProblems)) {
+				return;
+			}
+
+			List<IASToken> repairedTokensList = new ArrayList<IASToken>(Arrays.asList(buffer.getTokens(true)));
+			// restore the comments that were removed before parsing
+			IASToken nextComment = null;
+			for (int i = 0; i < repairedTokensList.size(); i++) {
+				if (nextComment == null) {
+					if (comments.size() == 0) {
+						// no more comments to add
+						break;
+					}
+					nextComment = comments.get(0);
+				}
+				IASToken currentToken = repairedTokensList.get(i);
+				if (nextComment.getAbsoluteStart() <= currentToken.getAbsoluteStart()) {
+					repairedTokensList.add(i, nextComment);
+					nextComment = null;
+					comments.remove(0);
+				}
+			}
+			// there may be some comments left that didn't appear before any
+			// of the repaired tokens, so add them all at the end
+			repairedTokensList.addAll(comments);
+
+			IASToken[] allTokens = repairedTokensList.toArray(new IASToken[0]);
+			TokenQuery tokenQuery = new TokenQuery(allTokens);
+			visitNode(node, tokenQuery, fileProblems);
+			boolean skipLinting = false;
+			for (LinterRule rule : settings.rules) {
+				Map<Integer, TokenVisitor> tokenHandlers = rule.getTokenVisitors();
+				if (tokenHandlers != null) {
+					for (IASToken token : allTokens) {
+						int tokenType = token.getType();
+						if (tokenType == ASTokenTypes.HIDDEN_TOKEN_SINGLE_LINE_COMMENT
+								|| tokenType == ASTokenTypes.HIDDEN_TOKEN_MULTI_LINE_COMMENT) {
+							boolean isMultiline = tokenType == ASTokenTypes.HIDDEN_TOKEN_MULTI_LINE_COMMENT;
+							String tokenText = token.getText();
+							String trimmed = tokenText.substring(2, tokenText.length() - (isMultiline ? 2 : 0)).trim();
+							if (!skipLinting && LINTER_TAG_OFF.equals(trimmed)) {
+								skipLinting = true;
+							} else if (skipLinting && LINTER_TAG_ON.equals(trimmed)) {
+								skipLinting = false;
+								continue;
+							}
+						}
+						if (skipLinting) {
+							continue;
+						}
+						if (tokenHandlers.containsKey(tokenType)) {
+							tokenHandlers.get(tokenType).visit(token, tokenQuery, fileProblems);
+						}
+					}
+				}
+			}
+		} finally {
+			allProblems.addAll(fileProblems);
+		}
+	}
+
+	private void visitNode(IASNode node, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		ASTNodeID nodeID = node.getNodeID();
+		IASToken prevComment = tokenQuery.getPreviousComment(node);
+		while (prevComment != null) {
+			String commentText = null;
+			if (prevComment.getType() == ASTokenTypes.HIDDEN_TOKEN_SINGLE_LINE_COMMENT) {
+				commentText = prevComment.getText().substring(2).trim();
+			} else if (prevComment.getType() == ASTokenTypes.HIDDEN_TOKEN_MULTI_LINE_COMMENT) {
+				commentText = prevComment.getText();
+				commentText = commentText.substring(2, commentText.length() - 2).trim();
+			} else {
+				// not the type of comment that we care about
+				prevComment = tokenQuery.getPreviousComment(prevComment);
+				continue;
+			}
+			if (LINTER_TAG_ON.equals(commentText)) {
+				// linter is on
+				break;
+			}
+			if (LINTER_TAG_OFF.equals(commentText)) {
+				// linter is off
+				return;
+			}
+			prevComment = tokenQuery.getPreviousComment(prevComment);
+		}
+		for (LinterRule rule : settings.rules) {
+			Map<ASTNodeID, NodeVisitor> nodeHandlers = rule.getNodeVisitors();
+			if (nodeHandlers != null && nodeHandlers.containsKey(nodeID)) {
+				nodeHandlers.get(nodeID).visit(node, tokenQuery, problems);
+			}
+		}
+
+		for (int i = 0; i < node.getChildCount(); i++) {
+			IASNode child = node.getChild(i);
+			visitNode(child, tokenQuery, problems);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/LINTER.java b/linter/src/main/java/org/apache/royale/linter/LINTER.java
new file mode 100644
index 0000000..38b49f9
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/LINTER.java
@@ -0,0 +1,448 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.royale.compiler.clients.problems.CompilerProblemCategorizer;
+import org.apache.royale.compiler.clients.problems.ProblemFormatter;
+import org.apache.royale.compiler.clients.problems.ProblemPrinter;
+import org.apache.royale.compiler.clients.problems.ProblemQuery;
+import org.apache.royale.compiler.clients.problems.WorkspaceProblemFormatter;
+import org.apache.royale.compiler.common.VersionInfo;
+import org.apache.royale.compiler.exceptions.ConfigurationException;
+import org.apache.royale.compiler.filespecs.FileSpecification;
+import org.apache.royale.compiler.internal.config.localization.LocalizationManager;
+import org.apache.royale.compiler.internal.workspaces.Workspace;
+import org.apache.royale.compiler.problems.ConfigurationProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.problems.UnexpectedExceptionProblem;
+import org.apache.royale.linter.config.CommandLineConfigurator;
+import org.apache.royale.linter.config.Configuration;
+import org.apache.royale.linter.config.ConfigurationBuffer;
+import org.apache.royale.linter.config.ConfigurationValue;
+import org.apache.royale.linter.config.Configurator;
+import org.apache.royale.linter.config.ILinterSettingsConstants;
+import org.apache.royale.linter.config.LineCommentPosition;
+import org.apache.royale.linter.rules.AnyTypeRule;
+import org.apache.royale.linter.rules.BooleanEqualityRule;
+import org.apache.royale.linter.rules.ClassNameRule;
+import org.apache.royale.linter.rules.ConstantNameRule;
+import org.apache.royale.linter.rules.ConstructorDispatchEventRule;
+import org.apache.royale.linter.rules.ConstructorReturnTypeRule;
+import org.apache.royale.linter.rules.DynamicClassRule;
+import org.apache.royale.linter.rules.EmptyCommentRule;
+import org.apache.royale.linter.rules.EmptyFunctionBodyRule;
+import org.apache.royale.linter.rules.EmptyNestedBlockRule;
+import org.apache.royale.linter.rules.EmptyStatementRule;
+import org.apache.royale.linter.rules.FieldNameRule;
+import org.apache.royale.linter.rules.FunctionNameRule;
+import org.apache.royale.linter.rules.IfBooleanLiteralRule;
+import org.apache.royale.linter.rules.InterfaceNameRule;
+import org.apache.royale.linter.rules.LineCommentPositionRule;
+import org.apache.royale.linter.rules.LocalVarAndParameterNameRule;
+import org.apache.royale.linter.rules.LocalVarShadowsFieldRule;
+import org.apache.royale.linter.rules.MXMLEmptyAttributeRule;
+import org.apache.royale.linter.rules.MXMLIDRule;
+import org.apache.royale.linter.rules.MaxBlockDepthRule;
+import org.apache.royale.linter.rules.MaxParametersRule;
+import org.apache.royale.linter.rules.MissingASDocRule;
+import org.apache.royale.linter.rules.MissingNamespaceRule;
+import org.apache.royale.linter.rules.MissingSemicolonRule;
+import org.apache.royale.linter.rules.MissingTypeRule;
+import org.apache.royale.linter.rules.NumericLeadingZeroesRule;
+import org.apache.royale.linter.rules.OverrideContainsOnlySuperCallRule;
+import org.apache.royale.linter.rules.PackageNameRule;
+import org.apache.royale.linter.rules.StaticConstantsRule;
+import org.apache.royale.linter.rules.StrictEqualityRule;
+import org.apache.royale.linter.rules.StringEventNameRule;
+import org.apache.royale.linter.rules.SwitchWithoutDefaultRule;
+import org.apache.royale.linter.rules.ThisInClosureRule;
+import org.apache.royale.linter.rules.TraceRule;
+import org.apache.royale.linter.rules.WildcardImportRule;
+import org.apache.royale.linter.rules.WithRule;
+import org.apache.royale.utils.FilenameNormalization;
+
+/**
+ * Lints .as and .mxml source files.
+ */
+public class LINTER {
+	private static final String DEFAULT_VAR = "files";
+	private static final String L10N_CONFIG_PREFIX = "org.apache.royale.compiler.internal.config.configuration";
+
+	static enum ExitCode {
+		SUCCESS(0), PRINT_HELP(1), FAILED_WITH_ERRORS(2), FAILED_WITH_EXCEPTIONS(3), FAILED_WITH_CONFIG_PROBLEMS(4);
+
+		ExitCode(int code) {
+			this.code = code;
+		}
+
+		final int code;
+
+		int getCode() {
+			return code;
+		}
+	}
+
+	/**
+	 * Java program entry point.
+	 * 
+	 * @param args command line arguments
+	 */
+	public static void main(final String[] args) {
+		LINTER formatter = new LINTER();
+		int exitCode = formatter.execute(args);
+		System.exit(exitCode);
+	}
+
+	public LINTER() {
+
+	}
+
+	private ProblemQuery problemQuery;
+	private List<File> inputFiles = new ArrayList<File>();
+	private Configuration configuration;
+	private ConfigurationBuffer configBuffer;
+
+	private LinterSettings settings = new LinterSettings();
+
+	public int execute(String[] args) {
+		ExitCode exitCode = ExitCode.SUCCESS;
+		problemQuery = new ProblemQuery();
+		problemQuery.setShowWarnings(false);
+
+		try {
+			boolean continueLinting = configure(args, problemQuery);
+			if (continueLinting) {
+				for (File inputFile : inputFiles) {
+					String filePath = FilenameNormalization.normalize(inputFile.getAbsolutePath());
+					FileSpecification fileSpec = new FileSpecification(filePath);
+					String fileText = IOUtils.toString(fileSpec.createReader());
+					lintFileText(filePath, fileText, problemQuery.getProblems());
+				}
+			} else if (problemQuery.hasFilteredProblems()) {
+				exitCode = ExitCode.FAILED_WITH_CONFIG_PROBLEMS;
+			} else {
+				exitCode = ExitCode.PRINT_HELP;
+			}
+		} catch (Exception e) {
+			problemQuery.add(new UnexpectedExceptionProblem(e));
+			System.err.println(e.getMessage());
+			exitCode = ExitCode.FAILED_WITH_EXCEPTIONS;
+		} finally {
+			if (problemQuery.hasFilteredProblems()) {
+				final Workspace workspace = new Workspace();
+				final CompilerProblemCategorizer categorizer = new CompilerProblemCategorizer();
+				final ProblemFormatter formatter = new WorkspaceProblemFormatter(workspace, categorizer);
+				final ProblemPrinter printer = new ProblemPrinter(formatter);
+				printer.printProblems(problemQuery.getFilteredProblems());
+			}
+		}
+		return exitCode.getCode();
+	}
+
+	/**
+	 * Get the start up message that contains the program name with the copyright
+	 * notice.
+	 * 
+	 * @return The startup message.
+	 */
+	protected String getStartMessage() {
+		// This message should not be localized.
+		String message = "Apache Royale ActionScript Linter (aslint)\n" + VersionInfo.buildMessage()
+				+ "\n";
+		return message;
+	}
+
+	/**
+	 * Get my program name.
+	 * 
+	 * @return always "aslint".
+	 */
+	protected String getProgramName() {
+		return "aslint";
+	}
+
+	/**
+	 * Print detailed help information if -help is provided.
+	 */
+	private void processHelp(final List<ConfigurationValue> helpVar) {
+		final Set<String> keywords = new LinkedHashSet<String>();
+		if (helpVar != null) {
+			for (final ConfigurationValue val : helpVar) {
+				for (final Object element : val.getArgs()) {
+					String keyword = (String) element;
+					while (keyword.startsWith("-"))
+						keyword = keyword.substring(1);
+					keywords.add(keyword);
+				}
+			}
+		}
+
+		if (keywords.size() == 0)
+			keywords.add("help");
+
+		final String usages = CommandLineConfigurator.usage(getProgramName(), DEFAULT_VAR, configBuffer, keywords,
+				LocalizationManager.get(), L10N_CONFIG_PREFIX);
+		System.out.println(getStartMessage());
+		System.out.println(usages);
+	}
+
+	private boolean configure(String[] args, ProblemQuery problems) {
+		try {
+			if (args.length == 0) {
+				System.out.println(getStartMessage());
+				return false;
+			}
+			Configurator configurator = new Configurator();
+			configurator.setConfiguration(args, ILinterSettingsConstants.FILES);
+			configuration = configurator.getConfiguration();
+			configBuffer = configurator.getConfigurationBuffer();
+
+			problems.addAll(configurator.getConfigurationProblems());
+
+			if (configBuffer.getVar("version") != null) {
+				System.out.println(VersionInfo.buildMessage());
+				return false;
+			}
+
+			// // Print help if "-help" is present.
+			final List<ConfigurationValue> helpVar = configBuffer.getVar("help");
+			if (helpVar != null || args.length == 0) {
+				processHelp(helpVar);
+				return false;
+			}
+
+			if (problems.hasErrors()) {
+				return false;
+			}
+
+			settings = new LinterSettings();
+			settings.ignoreProblems = configuration.getIgnoreParsingProblems();
+
+			List<LinterRule> rules = new ArrayList<LinterRule>();
+			if (configuration.getAnyType()) {
+				rules.add(new AnyTypeRule());
+			}
+			if (configuration.getBooleanEquality()) {
+				rules.add(new BooleanEqualityRule());
+			}
+			if (configuration.getClassName()) {
+				rules.add(new ClassNameRule());
+			}
+			if (configuration.getConstantName()) {
+				rules.add(new ConstantNameRule());
+			}
+			if (configuration.getConstructorReturnType()) {
+				rules.add(new ConstructorReturnTypeRule());
+			}
+			if (configuration.getConstructorDispatchEvent()) {
+				rules.add(new ConstructorDispatchEventRule());
+			}
+			if (configuration.getDynamicClass()) {
+				rules.add(new DynamicClassRule());
+			}
+			if (configuration.getThisClosure()) {
+				rules.add(new ThisInClosureRule());
+			}
+			if (configuration.getEmptyFunctionBody()) {
+				rules.add(new EmptyFunctionBodyRule());
+			}
+			if (configuration.getEmptyNestedBlock()) {
+				rules.add(new EmptyNestedBlockRule());
+			}
+			if (configuration.getFunctionName()) {
+				rules.add(new FunctionNameRule());
+			}
+			if (configuration.getFieldName()) {
+				rules.add(new FieldNameRule());
+			}
+			if (configuration.getOverrideSuper()) {
+				rules.add(new OverrideContainsOnlySuperCallRule());
+			}
+			if (configuration.getEmptyComment()) {
+				rules.add(new EmptyCommentRule());
+			}
+			if (configuration.getEmptyStatement()) {
+				rules.add(new EmptyStatementRule());
+			}
+			if (configuration.getIfBoolean()) {
+				rules.add(new IfBooleanLiteralRule());
+			}
+			if (configuration.getInterfaceName()) {
+				rules.add(new InterfaceNameRule());
+			}
+			if (configuration.getLeadingZero()) {
+				rules.add(new NumericLeadingZeroesRule());
+			}
+			if (configuration.getLineCommentPosition() != null) {
+				LineCommentPositionRule rule = new LineCommentPositionRule();
+				rule.position = LineCommentPosition.valueOf(configuration.getLineCommentPosition().toUpperCase());
+				rules.add(rule);
+			}
+			if (configuration.getLocalVarParamName()) {
+				rules.add(new LocalVarAndParameterNameRule());
+			}
+			if (configuration.getLocalVarShadowsField()) {
+				rules.add(new LocalVarShadowsFieldRule());
+			}
+			if (configuration.getMaxParams() > 0) {
+				MaxParametersRule rule = new MaxParametersRule();
+				rule.maximum = configuration.getMaxParams();
+				rules.add(rule);
+			}
+			if (configuration.getMaxBlockDepth() > 0) {
+				MaxBlockDepthRule rule = new MaxBlockDepthRule();
+				rule.maximum = configuration.getMaxBlockDepth();
+				rules.add(rule);
+			}
+			if (configuration.getMissingAsdoc()) {
+				rules.add(new MissingASDocRule());
+			}
+			if (configuration.getMissingNamespace()) {
+				rules.add(new MissingNamespaceRule());
+			}
+			if (configuration.getMissingSemicolon()) {
+				rules.add(new MissingSemicolonRule());
+			}
+			if (configuration.getMissingType()) {
+				rules.add(new MissingTypeRule());
+			}
+			if (configuration.getMxmlId()) {
+				rules.add(new MXMLIDRule());
+			}
+			if (configuration.getMxmlEmptyAttr()) {
+				rules.add(new MXMLEmptyAttributeRule());
+			}
+			if (configuration.getPackageName()) {
+				rules.add(new PackageNameRule());
+			}
+			if (configuration.getStaticConstants()) {
+				rules.add(new StaticConstantsRule());
+			}
+			if (configuration.getStrictEquality()) {
+				rules.add(new StrictEqualityRule());
+			}
+			if (configuration.getStringEvent()) {
+				rules.add(new StringEventNameRule());
+			}
+			if (configuration.getSwitchDefault()) {
+				rules.add(new SwitchWithoutDefaultRule());
+			}
+			if (configuration.getTrace()) {
+				rules.add(new TraceRule());
+			}
+			if (configuration.getWildcardImport()) {
+				rules.add(new WildcardImportRule());
+			}
+			if (configuration.getWith()) {
+				rules.add(new WithRule());
+			}
+			settings.rules = rules;
+			if (rules.size() == 0) {
+				ICompilerProblem problem = new ConfigurationProblem(null, -1, -1, -1, -1, "No linter rules were specified");
+				problems.add(problem);
+				return false;
+			}
+
+			for (String filePath : configuration.getFiles()) {
+				File inputFile = new File(filePath);
+				if (!inputFile.exists()) {
+					ICompilerProblem problem = new ConfigurationProblem(null, -1, -1, -1, -1, "Input file does not exist: " + filePath);
+					problems.add(problem);
+					return false;
+				}
+				if (inputFile.isDirectory()) {
+					addDirectory(inputFile);
+				} else {
+					inputFiles.add(inputFile);
+				}
+			}
+			if (inputFiles.size() == 0) {
+				ICompilerProblem problem = new ConfigurationProblem(null, -1, -1, -1, -1, "Missing input file(s)");
+				problems.add(problem);
+				return false;
+			}
+			return true;
+		} catch (Exception e) {
+			e.printStackTrace(System.err);
+			final ICompilerProblem problem = new ConfigurationProblem(null, -1, -1, -1, -1, e.getMessage());
+			problems.add(problem);
+			return false;
+		}
+	}
+
+	private void addDirectory(File inputFile) {
+		for (File file : inputFile.listFiles()) {
+			String fileName = file.getName();
+			if (fileName.startsWith(".")) {
+				continue;
+			}
+			if (file.isDirectory()) {
+				addDirectory(file);
+			} else if (fileName.endsWith(".as") || fileName.endsWith(".mxml")) {
+				inputFiles.add(file);
+			}
+		}
+	}
+
+	public void lintFile(File file, Collection<ICompilerProblem> problems) throws IOException {
+		String filePath = FilenameNormalization.normalize(file.getAbsolutePath());
+		FileSpecification fileSpec = new FileSpecification(filePath);
+		String fileText = IOUtils.toString(fileSpec.createReader());
+		lintFileText(filePath, fileText, problems);
+	}
+
+	public void lintFileText(String filePath, String text, Collection<ICompilerProblem> problems) {
+		filePath = FilenameNormalization.normalize(filePath);
+		if (filePath.endsWith(".mxml")) {
+			lintMXMLTextInternal(filePath, text, problems);
+		} else {
+			lintAS3TextInternal(filePath, text, problems);
+		}
+	}
+
+	public void lintActionScriptText(String text, Collection<ICompilerProblem> problems) {
+		String filePath = FilenameNormalization.normalize("stdin.as");
+		lintAS3TextInternal(filePath, text, problems);
+	}
+
+	public void lintMXMLText(String text, Collection<ICompilerProblem> problems) {
+		String filePath = FilenameNormalization.normalize("stdin.mxml");
+		lintMXMLTextInternal(filePath, text, problems);
+	}
+
+	private void lintAS3TextInternal(String filePath, String text, Collection<ICompilerProblem> problems) {
+		ASLinter linter = new ASLinter(settings);
+		linter.lint(filePath, text, problems);
+	}
+
+	private void lintMXMLTextInternal(String filePath, String text, Collection<ICompilerProblem> problems) {
+		MXMLLinter linter = new MXMLLinter(settings);
+		linter.lint(filePath, text, problems);
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/LinterRule.java b/linter/src/main/java/org/apache/royale/linter/LinterRule.java
new file mode 100644
index 0000000..6f14203
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/LinterRule.java
@@ -0,0 +1,44 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.parsing.IMXMLToken;
+
+public abstract class LinterRule {
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		return null;
+	}
+
+	public Map<Integer /* ASTokenTypes */, TokenVisitor> getTokenVisitors() {
+		return null;
+	}
+
+	public Map<IMXMLToken.MXMLTokenKind, MXMLTokenVisitor> getMXMLTokenVisitors() {
+		return null;
+	}
+
+	public List<MXMLTagVisitor> getMXMLTagVisitors() {
+		return null;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/LinterSettings.java b/linter/src/main/java/org/apache/royale/linter/LinterSettings.java
new file mode 100644
index 0000000..104ad92
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/LinterSettings.java
@@ -0,0 +1,28 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LinterSettings {
+	public List<LinterRule> rules = new ArrayList<LinterRule>();
+	public boolean ignoreProblems = false;
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/MXMLLinter.java b/linter/src/main/java/org/apache/royale/linter/MXMLLinter.java
new file mode 100644
index 0000000..5f4784a
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/MXMLLinter.java
@@ -0,0 +1,182 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter;
+
+import java.io.StringReader;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.royale.compiler.common.PrefixMap;
+import org.apache.royale.compiler.internal.filespecs.StringFileSpecification;
+import org.apache.royale.compiler.internal.mxml.MXMLData;
+import org.apache.royale.compiler.internal.parsing.mxml.MXMLToken;
+import org.apache.royale.compiler.internal.parsing.mxml.MXMLTokenizer;
+import org.apache.royale.compiler.mxml.IMXMLLanguageConstants;
+import org.apache.royale.compiler.mxml.IMXMLTagData;
+import org.apache.royale.compiler.mxml.IMXMLUnitData;
+import org.apache.royale.compiler.parsing.IMXMLToken;
+import org.apache.royale.compiler.parsing.IMXMLToken.MXMLTokenKind;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.linter.internal.BaseLinter;
+
+public class MXMLLinter extends BaseLinter {
+	private static final String LINTER_TAG_OFF = "@linter:off";
+	private static final String LINTER_TAG_ON = "@linter:on";
+
+	public MXMLLinter(LinterSettings settings) {
+		super(settings);
+	}
+
+	public void lint(String filePath, String text, Collection<ICompilerProblem> allProblems) {
+		if (allProblems == null) {
+			allProblems = new ArrayList<ICompilerProblem>();
+		}
+		List<ICompilerProblem> fileProblems = new ArrayList<ICompilerProblem>();
+		try {
+			StringReader textReader = new StringReader(text);
+			MXMLTokenizer mxmlTokenizer = new MXMLTokenizer();
+			List<MXMLToken> originalTokens = null;
+			try {
+				originalTokens = mxmlTokenizer.parseTokens(textReader);
+			} finally {
+				IOUtils.closeQuietly(textReader);
+				IOUtils.closeQuietly(mxmlTokenizer);
+			}
+
+			if (mxmlTokenizer.hasTokenizationProblems()) {
+				fileProblems.addAll(mxmlTokenizer.getTokenizationProblems());
+			}
+
+			if (!settings.ignoreProblems && hasErrors(fileProblems)) {
+				return;
+			}
+			boolean skipLinting = false;
+			IMXMLToken[] allTokens = originalTokens.toArray(new IMXMLToken[0]);
+			MXMLTokenQuery tokenQuery = new MXMLTokenQuery(allTokens);
+			for (LinterRule rule : settings.rules) {
+				Map<MXMLTokenKind, MXMLTokenVisitor> tokenHandlers = rule.getMXMLTokenVisitors();
+				if (tokenHandlers != null) {
+					for (IMXMLToken token : originalTokens) {
+						MXMLTokenKind tokenKind = token.getMXMLTokenKind();
+						if (tokenKind == MXMLTokenKind.COMMENT) {
+							String tokenText = token.getText();
+							boolean isASDoc = tokenText.startsWith("<!---") && tokenText.length() > 7;
+							String trimmed = tokenText.substring(isASDoc ? 5 : 4, tokenText.length() - 3).trim();
+							if (!skipLinting && LINTER_TAG_OFF.equals(trimmed)) {
+								skipLinting = true;
+							} else if (skipLinting && LINTER_TAG_ON.equals(trimmed)) {
+								skipLinting = false;
+								continue;
+							}
+						}
+						if (skipLinting) {
+							continue;
+						}
+						if (tokenHandlers.containsKey(tokenKind)) {
+							tokenHandlers.get(tokenKind).visit(token, tokenQuery, fileProblems);
+						}
+					}
+				}
+			}
+
+			PrefixMap prefixMap = mxmlTokenizer.getPrefixMap();
+			MXMLData mxmlData = new MXMLData(originalTokens, prefixMap, new StringFileSpecification(filePath, text));
+			IMXMLTagData rootTag = mxmlData.getRootTag();
+			visitTag(rootTag, tokenQuery, fileProblems);
+			IMXMLUnitData current = rootTag;
+			int offset = 1;
+			String className = Paths.get(filePath).getFileName().toString();
+			int extensionIndex = className.indexOf('.');
+			if (extensionIndex != -1) {
+				className = className.substring(0, extensionIndex);
+			}
+			String componentName = mxmlData.getRootTag().getShortName();
+			StringBuilder builder = new StringBuilder();
+			builder.append("/* ");
+			builder.append(LINTER_TAG_OFF);
+			builder.append(" */");
+			builder.append("package{public class ");
+			builder.append(className);
+			builder.append(" extends ");
+			builder.append(componentName);
+			builder.append("{");
+			while (current != null) {
+				if (current instanceof IMXMLTagData) {
+					IMXMLTagData tag = (IMXMLTagData) current;
+					if ("Script".equals(tag.getShortName())) {
+						String prefix = tag.getPrefix();
+						String ns = prefixMap.getNamespaceForPrefix(prefix);
+						if (ns != null
+								&& (IMXMLLanguageConstants.NAMESPACE_MXML_2006.equals(ns)
+										|| IMXMLLanguageConstants.NAMESPACE_MXML_2009.equals(ns)
+										|| IMXMLLanguageConstants.NAMESPACE_MXML_2012.equals(ns))) {
+							if (tag.isOpenTag()) {
+								int line = tag.getLine();
+								String scriptText = tag.getCompilableText();
+								// wrap the script inside a class so that rules don't
+								// get confused. adds new lines to ensure the line
+								// numbers match the original file
+								for (int i = offset; i < line; i++) {
+									builder.append("\n");
+								}
+								builder.append("/* ");
+								builder.append(LINTER_TAG_ON);
+								builder.append(" */\n");
+								builder.append(scriptText);
+								builder.append("/* ");
+								builder.append(LINTER_TAG_OFF);
+								builder.append(" */");
+							} else {
+								offset = tag.getLine() + 1;
+							}
+						}
+					}
+				}
+				current = current.getNext();
+			}
+			builder.append("}}\n");
+			ASLinter asLinter = new ASLinter(settings);
+			asLinter.lint(filePath, builder.toString(), fileProblems);
+		} finally {
+			allProblems.addAll(fileProblems);
+		}
+	}
+
+	private void visitTag(IMXMLTagData tag, MXMLTokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		for (LinterRule rule : settings.rules) {
+			List<MXMLTagVisitor> tagVisitors = rule.getMXMLTagVisitors();
+			if (tagVisitors != null) {
+				for (int i = 0; i < tagVisitors.size(); i++) {
+					tagVisitors.get(i).visit(tag, tokenQuery, problems);
+				}
+			}
+		}
+
+		IMXMLTagData current = tag.getFirstChild(true);
+		while (current != null) {
+			visitTag(current, tokenQuery, problems);
+			current = current.getNextSibling(true);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/MXMLTagVisitor.java b/linter/src/main/java/org/apache/royale/linter/MXMLTagVisitor.java
new file mode 100644
index 0000000..e327d23
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/MXMLTagVisitor.java
@@ -0,0 +1,29 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter;
+
+import java.util.Collection;
+
+import org.apache.royale.compiler.mxml.IMXMLTagData;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+
+public interface MXMLTagVisitor {
+	void visit(IMXMLTagData tag, MXMLTokenQuery tokenQuery, Collection<ICompilerProblem> problems);
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/MXMLTokenQuery.java b/linter/src/main/java/org/apache/royale/linter/MXMLTokenQuery.java
new file mode 100644
index 0000000..82fb830
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/MXMLTokenQuery.java
@@ -0,0 +1,96 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter;
+
+import java.util.List;
+
+import org.apache.royale.compiler.common.ISourceLocation;
+import org.apache.royale.compiler.parsing.IMXMLToken;
+
+public class MXMLTokenQuery {
+	public MXMLTokenQuery(IMXMLToken[] tokens) {
+		allTokens = tokens;
+	}
+
+	private IMXMLToken[] allTokens;
+
+	/**
+	 * Returns all tokens in the file.
+	 */
+	public IMXMLToken[] getTokens() {
+		return allTokens;
+	}
+
+	/**
+	 * Returns the token immediately before a source location.
+	 */
+	public IMXMLToken getTokenBefore(ISourceLocation sourceLocation) {
+		return getTokenBefore(sourceLocation, false);
+	}
+
+	/**
+	 * Returns the token immediately before a source location, with the option
+	 * to skip comment tokens.
+	 */
+	public IMXMLToken getTokenBefore(ISourceLocation sourceLocation, boolean skipComments) {
+		IMXMLToken result = null;
+		for (IMXMLToken otherToken : allTokens) {
+			if (skipComments && isComment(otherToken)) {
+				continue;
+			}
+			if (otherToken.getStart() >= sourceLocation.getAbsoluteStart()) {
+				return result;
+			}
+			result = otherToken;
+		}
+		return null;
+	}
+
+	/**
+	 * Returns the token immediately after a source location.
+	 */
+	public IMXMLToken getTokenAfter(ISourceLocation sourceLocation) {
+		return getTokenAfter(sourceLocation, false);
+	}
+
+	/**
+	 * Returns the token immediately after a source location, with the option to
+	 * skip comment tokens.
+	 */
+	public IMXMLToken getTokenAfter(ISourceLocation sourceLocation, boolean skipComments) {
+		for (IMXMLToken token : allTokens) {
+			if (skipComments && isComment(token)) {
+				continue;
+			}
+			if (token.getStart() >= sourceLocation.getAbsoluteEnd()) {
+				return token;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Checks if a token is comment.
+	 */
+	public boolean isComment(IMXMLToken token) {
+		return token.getMXMLTokenKind() == IMXMLToken.MXMLTokenKind.COMMENT;
+	}
+	
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/MXMLTokenVisitor.java b/linter/src/main/java/org/apache/royale/linter/MXMLTokenVisitor.java
new file mode 100644
index 0000000..520a3a6
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/MXMLTokenVisitor.java
@@ -0,0 +1,29 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter;
+
+import java.util.Collection;
+
+import org.apache.royale.compiler.parsing.IMXMLToken;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+
+public interface MXMLTokenVisitor {
+	void visit(IMXMLToken token, MXMLTokenQuery tokenQuery, Collection<ICompilerProblem> problems);
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/NodeVisitor.java b/linter/src/main/java/org/apache/royale/linter/NodeVisitor.java
new file mode 100644
index 0000000..6e723ec
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/NodeVisitor.java
@@ -0,0 +1,29 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter;
+
+import java.util.Collection;
+
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.as.IASNode;
+
+public interface NodeVisitor {
+	void visit(IASNode node, TokenQuery tokenQuery, Collection<ICompilerProblem> problems);
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/TokenQuery.java b/linter/src/main/java/org/apache/royale/linter/TokenQuery.java
new file mode 100644
index 0000000..99b69c6
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/TokenQuery.java
@@ -0,0 +1,203 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.royale.compiler.common.ISourceLocation;
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.tree.as.IASNode;
+
+public class TokenQuery {
+	public TokenQuery(IASToken[] tokens) {
+		allTokens = tokens;
+	}
+
+	private IASToken[] allTokens;
+
+	/**
+	 * Returns all tokens in the file.
+	 */
+	public IASToken[] getTokens() {
+		return allTokens;
+	}
+
+	/**
+	 * Returns all tokens inside of a particular node.
+	 */
+	public IASToken[] getTokens(IASNode node) {
+		List<IASToken> result = new ArrayList<>();
+		for (IASToken token : allTokens) {
+			if (token.getAbsoluteStart() < node.getAbsoluteStart()) {
+				continue;
+			}
+			if (token.getAbsoluteStart() >= node.getAbsoluteEnd()) {
+				break;
+			}
+			result.add(token);
+		}
+		return result.toArray(new IASToken[0]);
+	}
+
+	/**
+	 * Returns the token immediately before a source location.
+	 */
+	public IASToken getTokenBefore(ISourceLocation sourceLocation) {
+		return getTokenBefore(sourceLocation, false);
+	}
+
+	/**
+	 * Returns the token immediately before a source location, with the option
+	 * to skip comment tokens.
+	 */
+	public IASToken getTokenBefore(ISourceLocation sourceLocation, boolean skipComments) {
+		IASToken result = null;
+		for (IASToken otherToken : allTokens) {
+			if (skipComments && isComment(otherToken)) {
+				continue;
+			}
+			if (otherToken.getAbsoluteStart() >= sourceLocation.getAbsoluteStart()) {
+				return result;
+			}
+			result = otherToken;
+		}
+		return null;
+	}
+
+	/**
+	 * Returns the token immediately after a source location.
+	 */
+	public IASToken getTokenAfter(ISourceLocation sourceLocation) {
+		return getTokenAfter(sourceLocation, false);
+	}
+
+	/**
+	 * Returns the token immediately after a source location, with the option to
+	 * skip comment tokens.
+	 */
+	public IASToken getTokenAfter(ISourceLocation sourceLocation, boolean skipComments) {
+		for (IASToken token : allTokens) {
+			if (skipComments && isComment(token)) {
+				continue;
+			}
+			if (token.getAbsoluteStart() >= sourceLocation.getAbsoluteEnd()) {
+				return token;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Returns the first token inside a node.
+	 */
+	public IASToken getFirstToken(IASNode node) {
+		for (IASToken token : allTokens) {
+			if (token.getAbsoluteStart() >= node.getAbsoluteStart()) {
+				return token;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Returns the last token inside a node.
+	 */
+	public IASToken getLastToken(IASNode node) {
+		IASToken result = null;
+		for (IASToken token : allTokens) {
+			if (token.getAbsoluteStart() >= node.getAbsoluteStart()) {
+				result = token;
+			} else if (result != null) {
+				break;
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * Returns all comment tokens inside a node.
+	 */
+	public IASToken[] getCommentsInside(IASNode node) {
+		List<IASToken> result = new ArrayList<>();
+		IASToken[] tokensInside = getTokens(node);
+		for (IASToken token : tokensInside) {
+			if (isComment(token)) {
+				result.add(token);
+			}
+		}
+		return result.toArray(new IASToken[0]);
+	}
+
+	/**
+	 * Checks if a token is a comment.
+	 */
+	public boolean isComment(IASToken token) {
+		return token.getType() == ASTokenTypes.HIDDEN_TOKEN_COMMENT
+				|| token.getType() == ASTokenTypes.HIDDEN_TOKEN_SINGLE_LINE_COMMENT
+				|| token.getType() == ASTokenTypes.HIDDEN_TOKEN_MULTI_LINE_COMMENT
+				|| token.getType() == ASTokenTypes.TOKEN_ASDOC_COMMENT;
+	}
+
+	public IASToken getPreviousTokenOfType(ISourceLocation before, int type) {
+		IASToken result = null;
+		for (IASToken token : allTokens) {
+			if (token.getAbsoluteStart() >= before.getAbsoluteStart()) {
+				return result;
+			}
+			if (token.getType() == type) {
+				result = token;
+			}
+		}
+		return null;
+	}
+
+	public IASToken getNextTokenOfType(ISourceLocation after, int type) {
+		for (IASToken token : allTokens) {
+			if (token.getType() == type && token.getAbsoluteStart() >= after.getAbsoluteEnd()) {
+				return token;
+			}
+		}
+		return null;
+	}
+
+	public IASToken getPreviousComment(ISourceLocation before) {
+		IASToken result = null;
+		for (IASToken token : allTokens) {
+			if (token.getAbsoluteStart() >= before.getAbsoluteStart()) {
+				return result;
+			}
+			if (isComment(token)) {
+				result = token;
+			}
+		}
+		return null;
+	}
+
+	public IASToken getNextComment(ISourceLocation after) {
+		for (IASToken token : allTokens) {
+			if (token.getAbsoluteStart() >= after.getAbsoluteEnd() && isComment(token)) {
+				return token;
+			}
+		}
+		return null;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/TokenVisitor.java b/linter/src/main/java/org/apache/royale/linter/TokenVisitor.java
new file mode 100644
index 0000000..cfc931a
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/TokenVisitor.java
@@ -0,0 +1,29 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter;
+
+import java.util.Collection;
+
+import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+
+public interface TokenVisitor {
+	void visit(IASToken token, TokenQuery tokenQuery, Collection<ICompilerProblem> problems);
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/config/CommandLineConfigurator.java b/linter/src/main/java/org/apache/royale/linter/config/CommandLineConfigurator.java
new file mode 100644
index 0000000..f547fa0
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/config/CommandLineConfigurator.java
@@ -0,0 +1,607 @@
+/*
+ *
+ *  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.royale.linter.config;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.TreeSet;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.HashMap;
+import java.io.File;
+
+import com.google.common.base.Joiner;
+
+import org.apache.royale.compiler.Messages;
+import org.apache.royale.compiler.exceptions.ConfigurationException;
+import org.apache.royale.compiler.internal.config.localization.LocalizationManager;
+
+/**
+ * A utility class, which is used to parse an array of command line args and
+ * populate a ConfigurationBuffer. It also contains some associated methods like
+ * brief() and usage(). A counterpart of FileConfigurator and
+ * SystemPropertyConfigurator.
+ */
+public class CommandLineConfigurator
+{
+    public static final String SOURCE_COMMAND_LINE = "command line";
+
+    /**
+     * parse - buffer up configuration vals from the command line
+     * 
+     * @param buffer the configuration buffer to hold the results
+     * @param defaultvar the variable name where the trailing loose args go
+     * @param args the command line
+     */
+    public static void parse(final ConfigurationBuffer buffer,
+                              final String defaultvar,
+                              final String[] args)
+            throws ConfigurationException
+    {
+        // "no-default-arg" means the application does not have a default var.
+        assert defaultvar == null || buffer.isValidVar(defaultvar) || "no-default-arg".equals(defaultvar) : "coding error: config must provide default var " + defaultvar;
+
+        Map<String, String> aliases = getAliases(buffer);
+        final int START = 1;
+        final int ARGS = 2;
+        final int EXEC = 3;
+        final int DONE = 4;
+
+        int i = 0, iStart = 0, iEnd = 0;
+        String var = null;
+        int varArgCount = -2;
+        List<String> argList = new LinkedList<String>();
+        Set<String> vars = new HashSet<String>();
+        boolean append = false;
+        boolean dash = true;
+
+        int mode = START;
+
+        while (mode != DONE)
+        {
+            switch (mode)
+            {
+                case START:
+                {
+                    iStart = i;
+
+                    if (args.length == i)
+                    {
+                        mode = DONE;
+                        break;
+                    }
+                    // expect -var, --, or the beginning of default args
+
+                    mode = ARGS;
+                    varArgCount = -2;
+
+                    if (args[i].equals("--"))
+                    {
+                        dash = false;
+                        if (defaultvar != null)
+                            var = defaultvar;
+                        else
+                            mode = START;
+                        ++i;
+                    }
+                    else if (dash && args[i].startsWith("+"))
+                    {
+                        String token = null;
+                        int c = (args[i].length() > 1 && args[i].charAt(1) == '+') ? 2 : 1; // gnu-style?
+
+                        int equals = args[i].indexOf('=');
+                        String rest = null;
+                        if (equals != -1)
+                        {
+                            rest = args[i].substring(equals + 1);
+                            token = args[i++].substring(c, equals);
+                        }
+                        else
+                        {
+                            token = args[i++].substring(c);
+                        }
+                        if (equals != -1)
+                        {
+                            iEnd = i;
+                            buffer.setToken(token, rest);
+                            buffer.addPosition(token, iStart, iEnd);
+                        }
+                        else
+                        {
+                            if (i == args.length)
+                            {
+                                throw new ConfigurationException.Token(ConfigurationException.Token.INSUFFICIENT_ARGS,
+                                                                        token, var, source, -1);
+                            }
+                            rest = args[i++];
+                            iEnd = i;
+                            buffer.setToken(token, rest);
+                            buffer.addPosition(token, iStart, iEnd);
+                        }
+                        mode = START;
+                        break;
+                    }
+                    else if (dash && isAnArgument(args[i]))
+                    {
+                        int c = (args[i].length() > 1 && args[i].charAt(1) == '-') ? 2 : 1; // gnu-style?
+
+                        int plusequals = args[i].indexOf("+=");
+                        int equals = args[i].indexOf('=');
+                        String rest = null;
+                        if (plusequals != -1)
+                        {
+                            rest = args[i].substring(plusequals + 2);
+                            var = args[i++].substring(c, plusequals);
+                            append = true;
+                        }
+                        else if (equals != -1)
+                        {
+                            rest = args[i].substring(equals + 1);
+                            var = args[i++].substring(c, equals);
+                        }
+                        else
+                        {
+                            var = args[i++].substring(c);
+                        }
+
+                        if (aliases.containsKey(var))
+                            var = aliases.get(var);
+
+                        if (!buffer.isValidVar(var))
+                        {
+                            throw new ConfigurationException.UnknownVariable(var, source, -1);
+                        }
+
+                        if (equals != -1)
+                        {
+                            if ((rest == null) || (rest.length() == 0))
+                            {
+                                varArgCount = -1;
+                                mode = EXEC;
+                            }
+                            else
+                            {
+                                String seps = null;
+                                if (buffer.getInfo(var).isPath())
+                                {
+                                    seps = "[," + File.pathSeparatorChar + "]";
+                                }
+                                else
+                                {
+                                    seps = ",";
+                                }
+
+                                String[] tokens = rest.split(seps);
+                                argList.addAll(Arrays.asList(tokens));
+                                varArgCount = buffer.getVarArgCount(var);
+                                mode = EXEC;
+                            }
+                        }
+
+                    }
+                    else
+                    {
+                        // asdoc sets default var as no-default-arg - it has no default vars
+                        if (defaultvar != null && !defaultvar.equals("no-default-arg"))
+                        {
+                            // don't increment i, let ARGS pick it up.
+                            var = defaultvar;
+                        }
+                        else
+                        {
+                            throw new ConfigurationException.UnexpectedDefaults(null, null, -1);
+                        }
+                    }
+                    break;
+                }
+                case ARGS:
+                {
+                    if (varArgCount == -2)
+                    {
+                        if (isBoolean(buffer, var))
+                        {
+                            varArgCount = 0;
+                            mode = EXEC;
+                            break;
+                        }
+                        else
+                        {
+                            varArgCount = buffer.getVarArgCount(var);
+                        }
+                    }
+                    assert varArgCount >= -1; // just in case the getVarArgCount author was insane.
+
+                    if (args.length == i)
+                    {
+                        mode = EXEC;
+                        break;
+                    }
+
+                    boolean greedy = buffer.getInfo(var).isGreedy();
+
+                    // accumulating non-command arguments...
+
+                    // check for a terminator on our accumulated parameter list
+                    if (!greedy && dash && isAnArgument(args[i]))
+                    {
+                        if (varArgCount == -1)
+                        {
+                            // we were accumulating an unlimited set of args, a new var terminates that.
+                            mode = EXEC;
+                            break;
+                        }
+                        throw new ConfigurationException.IncorrectArgumentCount(varArgCount, argList.size(), var, source, -1);
+                    }
+
+                    argList.add(args[i++]);
+                    if (argList.size() == varArgCount)
+                    {
+                        mode = EXEC;
+                    }
+
+                    break;
+                }
+                case EXEC:
+                {
+                    if ((varArgCount != -1) && (argList.size() != varArgCount))
+                    {
+                        throw new ConfigurationException.IncorrectArgumentCount(varArgCount, argList.size(), var, source, -1);
+                    }
+                    if (varArgCount == 0) // boolean flag fakery...
+                        argList.add("true");
+
+                    if (vars.contains(var))
+                    {
+                        if ((defaultvar != null) && var.equals(defaultvar))
+                        {
+                            // we could perhaps accumulate the defaults spread out through
+                            // the rest of the flags, but for now we'll call this illegal.
+                            throw new ConfigurationException.InterspersedDefaults(var, source, -1);
+                        }
+                    }
+                    iEnd = i;
+                    buffer.setVar(var, new LinkedList<String>(argList), source, -1, null, append);
+                    buffer.addPosition(var, iStart, iEnd);
+                    append = false;
+                    vars.add(var);
+                    argList.clear();
+                    mode = START;
+                    break;
+                }
+                case DONE:
+                {
+                    assert false;
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Given a string like "-foo" or "-5" or "-123.mxml", this determines
+     * whether the string is an argument or... not an argument (e.g. numeral)
+     */
+    private static boolean isAnArgument(final String arg)
+    {
+        return (arg.startsWith("-") &&
+                // if the first character after a dash is numeric, this is not
+                // an argument, it is a parameter (and therefore non-terminating)
+                (arg.length() > 1) && !Character.isDigit(arg.charAt(1)));
+    }
+
+    private static Map<String, String> getAliases(ConfigurationBuffer buffer)
+    {
+        Map<String, String> aliases = new HashMap<String, String>();
+        aliases.putAll(buffer.getAliases());
+        for (final String varname : buffer.getVars())
+        {
+            if (varname.indexOf('.') == -1)
+                continue;
+
+            String leafname = varname.substring(varname.lastIndexOf('.') + 1);
+            if (aliases.containsKey(leafname))
+                continue;
+            aliases.put(leafname, varname);
+        }
+
+        return aliases;
+    }
+
+    private static boolean isBoolean(ConfigurationBuffer buffer, String var)
+    {
+        ConfigurationInfo info = buffer.getInfo(var);
+
+        if (info.getArgCount() > 1)
+            return false;
+
+        Class<?> c = info.getArgType(0);
+
+        return ((c == boolean.class) || (c == Boolean.class));
+    }
+
+    public static String brief(String program, String defaultvar, LocalizationManager l10n, String l10nPrefix)
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("defaultVar", defaultvar);
+        params.put("program", program);
+        return l10n.getLocalizedTextString(l10nPrefix + ".Brief", params);
+    }
+
+    public static String usage(String program, String defaultVar, ConfigurationBuffer cfgbuf, Set<String> keywords, LocalizationManager lmgr, String l10nPrefix)
+    {
+        boolean isCompc = program.contains("compc");
+        Map<String, String> aliases = getAliases(cfgbuf);
+        Map<String, String> sesaila = new HashMap<String, String>();
+        for (Iterator<Map.Entry<String, String>> it = aliases.entrySet().iterator(); it.hasNext();)
+        {
+            Map.Entry<String, String> e = it.next();
+            sesaila.put(e.getValue(), e.getKey());
+        }
+
+        TreeSet<String> printSet = new TreeSet<String>();
+
+        boolean all = false;
+        boolean advanced = false;
+        boolean details = false;
+        boolean syntax = false;
+        boolean printaliases = false;
+
+        // figure out behavior..
+        Set<String> newSet = new HashSet<String>();
+        for (Iterator<String> kit = keywords.iterator(); kit.hasNext();)
+        {
+            String keyword = kit.next();
+
+            if (keyword.equals("list"))
+            {
+                all = true;
+                newSet.add("*");
+            }
+            else if (keyword.equals("advanced"))
+            {
+                advanced = true;
+                if (keywords.size() == 1)
+                {
+                    all = true;
+                    newSet.add("*");
+                }
+            }
+            else if (keyword.equals("details"))
+            {
+                details = true;
+            }
+            else if (keyword.equals("syntax"))
+            {
+                syntax = true;
+            }
+            else if (keyword.equals("aliases"))
+            {
+                printaliases = true;
+            }
+            else
+            {
+                details = true;
+                newSet.add(keyword);
+            }
+        }
+        if (syntax)
+        {
+            final List<String> lines = ConfigurationBuffer.formatText(getSyntaxDescription(program, defaultVar, advanced, lmgr, l10nPrefix), 78);
+            return Joiner.on("\n").join(lines);
+        }
+        keywords = newSet;
+
+        // accumulate set to print
+        for (Iterator<String> kit = keywords.iterator(); kit.hasNext();)
+        {
+            String keyword = kit.next().toLowerCase();
+
+            for (final String var : cfgbuf.getVars())
+            {
+                ConfigurationInfo info = cfgbuf.getInfo(var);
+                
+                // If the client is not "compc", skip "compc-only" options.
+                if (info.isCompcOnly && !isCompc)
+                    continue;
+                
+                String description = getDescription(cfgbuf, var, lmgr, l10nPrefix);
+
+                if ((all
+                        || (var.indexOf(keyword) != -1)
+                        || ((description != null) && (description.toLowerCase().indexOf(keyword) != -1))
+                        || (keyword.matches(var))
+                        || ((sesaila.get(var) != null) && (sesaila.get(var)).indexOf(keyword) != -1))
+                     && (!info.isHidden())
+                     && (!info.isRemoved())
+                     && (advanced || !info.isAdvanced()))
+                {
+                    if (printaliases && sesaila.containsKey(var))
+                        printSet.add(sesaila.get(var));
+                    else
+                        printSet.add(var);
+                }
+                else
+                {
+                    /*
+                     * for (int i = 0; i < info.getAliases().length; ++i) {
+                     * String alias = info.getAliases()[i]; if (alias.indexOf(
+                     * keyword ) != -1) { printSet.add( var ); } }
+                     */
+                }
+            }
+        }
+
+        StringBuilder output = new StringBuilder(1024);
+
+        if (printSet.size() == 0)
+        {
+            String nkm = lmgr.getLocalizedTextString(l10nPrefix + ".NoKeywordsMatched");
+            output.append(nkm);
+            output.append("\n");
+        }
+        else
+            for (Iterator<String> it = printSet.iterator(); it.hasNext();)
+            {
+                String avar = it.next();
+                String var = avar;
+                if (aliases.containsKey(avar))
+                    var = aliases.get(avar);
+
+                ConfigurationInfo info = cfgbuf.getInfo(var);
+                assert info != null;
+
+                output.append("-");
+                output.append(avar);
+
+                int count = cfgbuf.getVarArgCount(var);
+                if ((count >= 1) && (!isBoolean(cfgbuf, var)))
+                {
+                    for (int i = 0; i < count; ++i)
+                    {
+                        output.append(" <");
+                        output.append(cfgbuf.getVarArgName(var, i));
+                        output.append(">");
+                    }
+                }
+                else if (count == -1)
+                {
+                    String last = "";
+                    for (int i = 0; i < 5; ++i)
+                    {
+                        String argname = cfgbuf.getVarArgName(var, i);
+                        if (!argname.equals(last))
+                        {
+                            output.append(" [");
+                            output.append(argname);
+                            output.append("]");
+                            last = argname;
+                        }
+                        else
+                        {
+                            output.append(" [...]");
+                            break;
+                        }
+                    }
+                }
+
+                output.append("\n");
+
+                if (details)
+                {
+                    StringBuilder description = new StringBuilder(160);
+                    if (printaliases)
+                    {
+                        if (aliases.containsKey(avar))
+                        {
+                            String fullname = lmgr.getLocalizedTextString(l10nPrefix + ".FullName");
+                            description.append(fullname);
+                            description.append(" -");
+                            description.append(aliases.get(avar));
+                            description.append("\n");
+                        }
+                    }
+                    else if (sesaila.containsKey(var))
+                    {
+                        String alias = lmgr.getLocalizedTextString(l10nPrefix + ".Alias");
+                        description.append(alias);
+                        description.append(" -");
+                        description.append(sesaila.get(var));
+                        description.append("\n");
+                    }
+
+                    String d = getDescription(cfgbuf, var, lmgr, l10nPrefix);
+                    if (var.equals("help") && (printSet.size() > 2))
+                    {
+                        String helpKeywords = lmgr.getLocalizedTextString(l10nPrefix + ".HelpKeywords");
+                        description.append(helpKeywords);
+                    }
+                    else if (d != null)
+                        description.append(d);
+
+                    String flags = "";
+                    if (info.isAdvanced())
+                    {
+                        String advancedString = lmgr.getLocalizedTextString(l10nPrefix + ".Advanced");
+                        flags += (((flags.length() == 0) ? " (" : ", ") + advancedString);
+                    }
+                    if (info.allowMultiple())
+                    {
+                        String repeatableString = lmgr.getLocalizedTextString(l10nPrefix + ".Repeatable");
+                        flags += (((flags.length() == 0) ? " (" : ", ") + repeatableString);
+                    }
+                    if ((defaultVar != null) && var.equals(defaultVar))
+                    {
+                        String defaultString = lmgr.getLocalizedTextString(l10nPrefix + ".Default");
+                        flags += (((flags.length() == 0) ? " (" : ", ") + defaultString);
+                    }
+                    if (info.isRoyaleOnly())
+                    {
+                        String royaleOnlylString = Messages.getString("RoyaleOnly");
+                        flags += (((flags.length() == 0) ? " (" : ", ") + royaleOnlylString);
+                    }
+                    if (flags.length() != 0)
+                    {
+                        flags += ")";
+                    }
+                    description.append(flags);
+
+                    List<String> descriptionLines = ConfigurationBuffer.formatText(description.toString(), 70);
+
+                    for (final String next : descriptionLines)
+                    {
+                        output.append("    ");
+                        output.append(next);
+                        output.append("\n");
+                    }
+                }
+            }
+        return output.toString();
+    }
+
+    public static String getDescription(ConfigurationBuffer buffer, String var, LocalizationManager l10n, String l10nPrefix)
+    {
+        String key = (l10nPrefix == null) ? var : (l10nPrefix + "." + var);
+        String description = l10n.getLocalizedTextString(key, null);
+
+        return description;
+    }
+
+    public static String getSyntaxDescription(String program, String defaultVar, boolean advanced, LocalizationManager l10n, String l10nPrefix)
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("defaultVar", defaultVar);
+        params.put("program", program);
+
+        String key = l10nPrefix + "." + (advanced ? "AdvancedSyntax" : "Syntax");
+        String text = l10n.getLocalizedTextString(key, params);
+
+        if (text == null)
+        {
+            text = "No syntax help available, try '-help list' to list available configuration variables.";
+            assert false : "Localized text for syntax description not found!";
+        }
+        return text;
+    }
+
+    public static final String source = SOURCE_COMMAND_LINE;
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/config/Configuration.java b/linter/src/main/java/org/apache/royale/linter/config/Configuration.java
new file mode 100644
index 0000000..32b82cf
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/config/Configuration.java
@@ -0,0 +1,764 @@
+/*
+ *
+ *  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.royale.linter.config;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.royale.compiler.exceptions.ConfigurationException;
+import org.apache.royale.compiler.internal.config.annotations.Arguments;
+import org.apache.royale.compiler.internal.config.annotations.Config;
+import org.apache.royale.compiler.internal.config.annotations.InfiniteArguments;
+import org.apache.royale.compiler.internal.config.annotations.Mapping;
+import org.apache.royale.compiler.problems.DeprecatedConfigurationOptionProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.problems.RemovedConfigurationOptionProblem;
+
+public class Configuration {
+
+    private static Map<String, String> aliases = null;
+
+    public static Map<String, String> getAliases() {
+        if (aliases == null) {
+            aliases = new HashMap<String, String>();
+
+            // aliases.put("s", "short-option");
+        }
+        return aliases;
+    }
+
+    /**
+     * Collection of fatal and non-fatal configuration problems.
+     */
+    private Collection<ICompilerProblem> configurationProblems = new ArrayList<ICompilerProblem>();
+
+    /**
+     * Get the configuration problems. This should be called after the configuration
+     * has been processed.
+     * 
+     * @return a collection of fatal and non-fatal configuration problems.
+     */
+    public Collection<ICompilerProblem> getConfigurationProblems() {
+        return configurationProblems;
+    }
+
+    /**
+     * Validate configuration options values.
+     * 
+     * @param configurationBuffer Configuration buffer.
+     * @throws ConfigurationException Error.
+     */
+    public void validate(ConfigurationBuffer configurationBuffer) throws ConfigurationException {
+        // process the merged configuration buffer. right, can't just process the args.
+        processDeprecatedAndRemovedOptions(configurationBuffer);
+    }
+
+    private void processDeprecatedAndRemovedOptions(ConfigurationBuffer configurationBuffer) {
+        for (final String var : configurationBuffer.getVars()) {
+            ConfigurationInfo info = configurationBuffer.getInfo(var);
+            List<ConfigurationValue> values = configurationBuffer.getVar(var);
+            if (values != null) {
+                for (final ConfigurationValue cv : values) {
+                    if (info.isRemoved()) {
+                        addRemovedConfigurationOptionProblem(cv);
+                    } else if (info.isDeprecated() && configurationBuffer.getVar(var) != null) {
+                        String replacement = info.getDeprecatedReplacement();
+                        String since = info.getDeprecatedSince();
+                        DeprecatedConfigurationOptionProblem problem = new DeprecatedConfigurationOptionProblem(var,
+                                replacement, since, cv.getSource(), cv.getLine());
+                        configurationProblems.add(problem);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Add a RemovedConfigurationOptionProblem to the list of configuration
+     * problems.
+     * 
+     * @param cv
+     */
+    private void addRemovedConfigurationOptionProblem(ConfigurationValue cv) {
+        RemovedConfigurationOptionProblem problem = new RemovedConfigurationOptionProblem(cv.getVar(), cv.getSource(),
+                cv.getLine());
+        configurationProblems.add(problem);
+    }
+
+    //
+    // 'help' option from CommandLineConfiguration
+    //
+
+    /**
+     * dummy, just a trigger for help text
+     */
+    @Config(displayed = false, greedy = true)
+    @Arguments("keyword")
+    @InfiniteArguments
+    public void setHelp(ConfigurationValue cv, String[] keywords) {
+
+    }
+
+    //
+    // 'version' option from CommandLineConfiguration
+    //
+
+    /**
+     * Dummy option. Just a trigger for version info.
+     */
+    @Config
+    public void setVersion(ConfigurationValue cv, boolean value) {
+    }
+
+    //
+    // 'files' option
+    //
+
+    private List<String> files = new ArrayList<String>();
+
+    /**
+     * @return A list of filespecs. It's the default variable for command line.
+     */
+    public List<String> getFiles() {
+        return files;
+    }
+
+    @Config(allowMultiple = true, hidden = true)
+    @Mapping("files")
+    @Arguments(Arguments.PATH_ELEMENT)
+    @InfiniteArguments
+    public void setFiles(ConfigurationValue cv, List<String> args) throws ConfigurationException {
+        this.files.addAll(args);
+    }
+
+    //
+    // 'ignore-parsing-problems' option
+    //
+
+    private boolean ignoreParsingProblems = false;
+
+    public boolean getIgnoreParsingProblems() {
+        return ignoreParsingProblems;
+    }
+
+    @Config(advanced = true)
+    @Mapping("ignore-parsing-problems")
+    public void setIgnoreParsingProblems(ConfigurationValue cv, boolean b) {
+        this.ignoreParsingProblems = b;
+    }
+
+    //
+    // 'any-type' option
+    //
+
+    private boolean anyType = false;
+
+    public boolean getAnyType() {
+        return anyType;
+    }
+
+    @Config
+    @Mapping("any-type")
+    public void setAnyType(ConfigurationValue cv, boolean b) {
+        this.anyType = b;
+    }
+
+    //
+    // 'boolean-equality' option
+    //
+
+    private boolean booleanEquality = false;
+
+    public boolean getBooleanEquality() {
+        return booleanEquality;
+    }
+
+    @Config
+    @Mapping("boolean-equality")
+    public void setBooleanEquality(ConfigurationValue cv, boolean b) {
+        this.booleanEquality = b;
+    }
+
+    //
+    // 'class-name' option
+    //
+
+    private boolean className = false;
+
+    public boolean getClassName() {
+        return className;
+    }
+
+    @Config
+    @Mapping("class-name")
+    public void setClassName(ConfigurationValue cv, boolean b) {
+        this.className = b;
+    }
+
+    //
+    // 'constant-name' option
+    //
+
+    private boolean constantName = false;
+
+    public boolean getConstantName() {
+        return constantName;
+    }
+
+    @Config
+    @Mapping("constant-name")
+    public void setConstantName(ConfigurationValue cv, boolean b) {
+        this.constantName = b;
+    }
+
+    //
+    // 'constructor-dispatch-event' option
+    //
+
+    private boolean constructorDispatchEvent = false;
+
+    public boolean getConstructorDispatchEvent() {
+        return constructorDispatchEvent;
+    }
+
+    @Config
+    @Mapping("constructor-dispatch-event")
+    public void setConstructorDispatchEvent(ConfigurationValue cv, boolean b) {
+        this.constructorDispatchEvent = b;
+    }
+
+    //
+    // 'constructor-return-type' option
+    //
+
+    private boolean constructorReturnType = false;
+
+    public boolean getConstructorReturnType() {
+        return constructorReturnType;
+    }
+
+    @Config
+    @Mapping("constructor-return-type")
+    public void setConstructorReturnType(ConfigurationValue cv, boolean b) {
+        this.constructorReturnType = b;
+    }
+
+    //
+    // 'dynamic-class' option
+    //
+
+    private boolean dynamicClass = false;
+
+    public boolean getDynamicClass() {
+        return dynamicClass;
+    }
+
+    @Config
+    @Mapping("dynamic-class")
+    public void setDynamicClass(ConfigurationValue cv, boolean b) {
+        this.dynamicClass = b;
+    }
+
+    //
+    // 'empty-comment' option
+    //
+
+    private boolean emptyComment = false;
+
+    public boolean getEmptyComment() {
+        return emptyComment;
+    }
+
+    @Config
+    @Mapping("empty-comment")
+    public void setEmptyComment(ConfigurationValue cv, boolean b) {
+        this.emptyComment = b;
+    }
+
+    //
+    // 'empty-function-body' option
+    //
+
+    private boolean emptyFunctionBody = false;
+
+    public boolean getEmptyFunctionBody() {
+        return emptyFunctionBody;
+    }
+
+    @Config
+    @Mapping("empty-function-body")
+    public void setEmptyFunctionBody(ConfigurationValue cv, boolean b) {
+        this.emptyFunctionBody = b;
+    }
+
+    //
+    // 'empty-nested-block' option
+    //
+
+    private boolean emptyNestedBlock = false;
+
+    public boolean getEmptyNestedBlock() {
+        return emptyNestedBlock;
+    }
+
+    @Config
+    @Mapping("empty-nested-block")
+    public void setEmptyNestedBlock(ConfigurationValue cv, boolean b) {
+        this.emptyNestedBlock = b;
+    }
+
+    //
+    // 'empty-statement' option
+    //
+
+    private boolean emptyStatement = false;
+
+    public boolean getEmptyStatement() {
+        return emptyStatement;
+    }
+
+    @Config
+    @Mapping("empty-statement")
+    public void setEmptyStatement(ConfigurationValue cv, boolean b) {
+        this.emptyStatement = b;
+    }
+
+    //
+    // 'field-name' option
+    //
+
+    private boolean fieldName = false;
+
+    public boolean getFieldName() {
+        return fieldName;
+    }
+
+    @Config
+    @Mapping("field-name")
+    public void setFieldName(ConfigurationValue cv, boolean b) {
+        this.fieldName = b;
+    }
+
+    //
+    // 'function-name' option
+    //
+
+    private boolean functionName = false;
+
+    public boolean getFunctionName() {
+        return functionName;
+    }
+
+    @Config
+    @Mapping("function-name")
+    public void setFunctionName(ConfigurationValue cv, boolean b) {
+        this.functionName = b;
+    }
+
+    //
+    // 'if-boolean' option
+    //
+
+    private boolean ifBoolean = false;
+
+    public boolean getIfBoolean() {
+        return ifBoolean;
+    }
+
+    @Config
+    @Mapping("if-boolean")
+    public void setIfBoolean(ConfigurationValue cv, boolean b) {
+        this.ifBoolean = b;
+    }
+
+    //
+    // 'interface-name' option
+    //
+
+    private boolean interfaceName = false;
+
+    public boolean getInterfaceName() {
+        return interfaceName;
+    }
+
+    @Config
+    @Mapping("interface-name")
+    public void setInterfaceName(ConfigurationValue cv, boolean b) {
+        this.interfaceName = b;
+    }
+
+    //
+    // 'line-comment-position' option
+    //
+
+    private LineCommentPosition lineCommentPosition = null;
+
+    public String getLineCommentPosition() {
+        if (lineCommentPosition == null) {
+            return null;
+        }
+        return lineCommentPosition.getPosition();
+    }
+
+    @Config
+    @Mapping("line-comment-position")
+    public void setLineCommentPosition(ConfigurationValue cv, String s) {
+        this.lineCommentPosition = LineCommentPosition.valueOf(s.toUpperCase());
+    }
+
+    //
+    // 'local-var-param-name' option
+    //
+
+    private boolean localVarParamName = false;
+
+    public boolean getLocalVarParamName() {
+        return localVarParamName;
+    }
+
+    @Config
+    @Mapping("local-var-param-name")
+    public void setLocalVarParamName(ConfigurationValue cv, boolean b) {
+        this.localVarParamName = b;
+    }
+
+    //
+    // 'local-var-shadows-field' option
+    //
+
+    private boolean localVarShadowsField = false;
+
+    public boolean getLocalVarShadowsField() {
+        return localVarShadowsField;
+    }
+
+    @Config
+    @Mapping("local-var-shadows-field")
+    public void setLocalVarShadowsField(ConfigurationValue cv, boolean b) {
+        this.localVarShadowsField = b;
+    }
+
+    //
+    // 'max-block-depth' option
+    //
+
+    private int maxBlockDepth = 0;
+
+    public int getMaxBlockDepth() {
+        return maxBlockDepth;
+    }
+
+    @Config
+    @Mapping("max-block-depth")
+    public void setMaxBlockDepth(ConfigurationValue cv, int i) {
+        this.maxBlockDepth = i;
+    }
+
+    //
+    // 'max-params' option
+    //
+
+    private int maxParams = 0;
+
+    public int getMaxParams() {
+        return maxParams;
+    }
+
+    @Config
+    @Mapping("max-params")
+    public void setMaxParams(ConfigurationValue cv, int i) {
+        this.maxParams = i;
+    }
+
+    //
+    // 'missing-asdoc' option
+    //
+
+    private boolean missingAsdoc = false;
+
+    public boolean getMissingAsdoc() {
+        return missingAsdoc;
+    }
+
+    @Config
+    @Mapping("missing-asdoc")
+    public void setMissingAsdoc(ConfigurationValue cv, boolean b) {
+        this.missingAsdoc = b;
+    }
+
+    //
+    // 'missing-namespace' option
+    //
+
+    private boolean missingNamespace = false;
+
+    public boolean getMissingNamespace() {
+        return missingNamespace;
+    }
+
+    @Config
+    @Mapping("missing-namespace")
+    public void setMissingNamespace(ConfigurationValue cv, boolean b) {
+        this.missingNamespace = b;
+    }
+
+    //
+    // 'missing-semicolon' option
+    //
+
+    private boolean missingSemicolon = false;
+
+    public boolean getMissingSemicolon() {
+        return missingSemicolon;
+    }
+
+    @Config
+    @Mapping("missing-semicolon")
+    public void setMissingSemicolon(ConfigurationValue cv, boolean b) {
+        this.missingSemicolon = b;
+    }
+
+    //
+    // 'missing-type' option
+    //
+
+    private boolean missingType = false;
+
+    public boolean getMissingType() {
+        return missingType;
+    }
+
+    @Config
+    @Mapping("missing-type")
+    public void setMissingType(ConfigurationValue cv, boolean b) {
+        this.missingType = b;
+    }
+
+    //
+    // 'mxml-empty-attr' option
+    //
+
+    private boolean mxmlEmptyAttr = false;
+
+    public boolean getMxmlEmptyAttr() {
+        return mxmlEmptyAttr;
+    }
+
+    @Config
+    @Mapping("mxml-empty-attr")
+    public void setMxmlEmptyAttr(ConfigurationValue cv, boolean b) {
+        this.mxmlEmptyAttr = b;
+    }
+
+    //
+    // 'mxml-id' option
+    //
+
+    private boolean mxmlId = false;
+
+    public boolean getMxmlId() {
+        return mxmlId;
+    }
+
+    @Config
+    @Mapping("mxml-id")
+    public void setMxmlId(ConfigurationValue cv, boolean b) {
+        this.mxmlId = b;
+    }
+
+    //
+    // 'leading-zero' option
+    //
+
+    private boolean leadingZero = false;
+
+    public boolean getLeadingZero() {
+        return leadingZero;
+    }
+
+    @Config
+    @Mapping("leading-zero")
+    public void setLeadingZero(ConfigurationValue cv, boolean b) {
+        this.leadingZero = b;
+    }
+
+    //
+    // 'override-super' option
+    //
+
+    private boolean overrideSuper = false;
+
+    public boolean getOverrideSuper() {
+        return overrideSuper;
+    }
+
+    @Config
+    @Mapping("override-super")
+    public void setOverrideSuper(ConfigurationValue cv, boolean b) {
+        this.overrideSuper = b;
+    }
+
+    //
+    // 'package-name' option
+    //
+
+    private boolean packageName = false;
+
+    public boolean getPackageName() {
+        return packageName;
+    }
+
+    @Config
+    @Mapping("package-name")
+    public void setPackageName(ConfigurationValue cv, boolean b) {
+        this.packageName = b;
+    }
+
+    //
+    // 'static-constants' option
+    //
+
+    private boolean staticConstants = false;
+
+    public boolean getStaticConstants() {
+        return staticConstants;
+    }
+
+    @Config
+    @Mapping("static-constants")
+    public void setStaticConstants(ConfigurationValue cv, boolean b) {
+        this.staticConstants = b;
+    }
+
+    //
+    // 'strict-equality' option
+    //
+
+    private boolean strictEquality = false;
+
+    public boolean getStrictEquality() {
+        return strictEquality;
+    }
+
+    @Config
+    @Mapping("strict-equality")
+    public void setStrictEquality(ConfigurationValue cv, boolean b) {
+        this.strictEquality = b;
+    }
+
+    //
+    // 'string-event' option
+    //
+
+    private boolean stringEvent = false;
+
+    public boolean getStringEvent() {
+        return stringEvent;
+    }
+
+    @Config
+    @Mapping("string-event")
+    public void setStringEvent(ConfigurationValue cv, boolean b) {
+        this.stringEvent = b;
+    }
+
+    //
+    // 'switch-default' option
+    //
+
+    private boolean switchDefault = false;
+
+    public boolean getSwitchDefault() {
+        return switchDefault;
+    }
+
+    @Config
+    @Mapping("switch-default")
+    public void setSwitchDefault(ConfigurationValue cv, boolean b) {
+        this.switchDefault = b;
+    }
+
+    //
+    // 'this-closure' option
+    //
+
+    private boolean thisClosure = false;
+
+    public boolean getThisClosure() {
+        return thisClosure;
+    }
+
+    @Config
+    @Mapping("this-closure")
+    public void setThisClosure(ConfigurationValue cv, boolean b) {
+        this.thisClosure = b;
+    }
+
+    //
+    // 'trace' option
+    //
+
+    private boolean trace = false;
+
+    public boolean getTrace() {
+        return trace;
+    }
+
+    @Config
+    @Mapping("trace")
+    public void setTrace(ConfigurationValue cv, boolean b) {
+        this.trace = b;
+    }
+
+    //
+    // 'wildcard-import' option
+    //
+
+    private boolean wildcardImport = false;
+
+    public boolean getWildcardImport() {
+        return wildcardImport;
+    }
+
+    @Config
+    @Mapping("wildcard-import")
+    public void setWildcardImport(ConfigurationValue cv, boolean b) {
+        this.wildcardImport = b;
+    }
+
+    //
+    // 'with' option
+    //
+
+    private boolean with = false;
+
+    public boolean getWith() {
+        return with;
+    }
+
+    @Config
+    @Mapping("with")
+    public void setWith(ConfigurationValue cv, boolean b) {
+        this.with = b;
+    }
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/config/ConfigurationBuffer.java b/linter/src/main/java/org/apache/royale/linter/config/ConfigurationBuffer.java
new file mode 100644
index 0000000..fd1dda2
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/config/ConfigurationBuffer.java
@@ -0,0 +1,1346 @@
+/*
+ *
+ *  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.royale.linter.config;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.royale.compiler.exceptions.ConfigurationException;
+import org.apache.royale.compiler.internal.config.IConfigurationFilter;
+import org.apache.royale.compiler.internal.config.annotations.ArgumentNameGenerator;
+import org.apache.royale.compiler.internal.config.annotations.Arguments;
+import org.apache.royale.compiler.internal.config.annotations.Config;
+import org.apache.royale.compiler.internal.config.annotations.RoyaleOnly;
+import org.apache.royale.compiler.internal.config.annotations.InfiniteArguments;
+import org.apache.royale.compiler.internal.config.annotations.Mapping;
+import org.apache.royale.compiler.internal.config.annotations.SoftPrerequisites;
+import org.apache.royale.compiler.problems.ConfigurationProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.utils.Trace;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * The basic idea here is to let you keep all your configuration knowledge in
+ * your configuration object, and to automate as much as possible. Reflection is
+ * used to convert public fields and setters on your configuration object into
+ * settable vars. There are a few key concepts:
+ * <p>
+ * - You should be able to configure absolutely any object.<br>
+ * - Child configuration variables in your config become a dotted hierarchy of
+ * varnames<br>
+ * - All sources of configuration data are buffered and merged (as string
+ * var/vals) before committing to the final configuration. This class acts as
+ * the buffer.<br>
+ * - Hyphenated variables (i.e. "some-var") are automatically configured by
+ * calling your matching setter (i.e. setSomeVar)<br>
+ * - Implementing an getSomeVarInfo() method on your class lets you set up more
+ * complicated config objects<br>
+ * - You can make variables depend on other variables having been set first.
+ * This lets you set a root directory in one var and then use its value in
+ * another.<br>
+ * - Per-variable validation can be performed in setters. Overall validation
+ * should take place as a post-process step.<br>
+ * - You can keep ConfigurationBuffers around and merge multiple buffers
+ * together before committing. Most recent definitions always win.<br>
+ * <p>
+ * The contract with your configuration class:
+ * <p>
+ * - You must provide a method with the signature
+ * "void setYourVar(ConfigurationValue val)" to set your config var. Your setter
+ * method should accept either a single arg of type List or String[], or else an
+ * arglist of simple types. For example
+ * "void myvar(int a, boolean b, String c")".<br>
+ * - You can implement a function with the signature "int yourvar_argcount()" to
+ * require a different number of arguments. This limit will be enforced by
+ * configurators (command line, file, etc.)<br>
+ * - If you provide a setter and explicit parameters (i.e. not List or String[])
+ * the number of arguments will be automatically determined.<br>
+ * - Each argument to your configuration variable is assumed to have a
+ * (potentially non-unique) name. The default is the simple type of the argument
+ * (boolean, int, string). If the var takes an undetermined number of args via
+ * List or String[], the argname defaults to string.<br>
+ * - You can implement a function with the signature
+ * "String yourvar_argnames(int)" to provide names for each of the parameters.
+ * The integer passed in is the argument number. Return the same name (i.e.
+ * "item") for infinite lists.<br>
+ * - You can implement a function with the signature "String[] yourvar_deps()"
+ * to provide a list of other prerequisites for this var. You will be guaranteed
+ * that the deps are committed before your var, or else a configurationexception
+ * will be thrown if a prerequsite was unset. (Note that infinite cycles are not
+ * checked, so be careful.)<br>
+ */
+public final class ConfigurationBuffer
+{
+    public ConfigurationBuffer(Class<? extends Configuration> configClass)
+    {
+        this(configClass, new HashMap<String, String>());
+    }
+
+    public ConfigurationBuffer(Class<? extends Configuration> configClass, Map<String, String> aliases)
+    {
+        this(configClass, aliases, null);
+    }
+
+    /**
+     * Create a configuration buffer with an optional filter. The filter can be
+     * used to remove unwanted options from a super class.
+     * 
+     * @param filter if null there is no filter, otherwise the set of
+     * configuration options is filtered.
+     */
+    public ConfigurationBuffer(Class<? extends Configuration> configClass, Map<String, String> aliases, IConfigurationFilter filter)
+    {
+        this.configClass = configClass;
+        this.varMap = new HashMap<String, List<ConfigurationValue>>();
+        this.committed = new HashSet<String>();
+
+        loadCache(configClass, filter);
+        assert (varCache.size() > 0) : "coding error: nothing was configurable in the provided object!";
+        for (Map.Entry<String, String> e : aliases.entrySet())
+        {
+            addAlias(e.getKey(), e.getValue());
+        }
+    }
+
+    public ConfigurationBuffer(ConfigurationBuffer copyFrom, boolean copyCommitted)
+    {
+        this.configClass = copyFrom.configClass;
+        this.varMap = new HashMap<String, List<ConfigurationValue>>(copyFrom.varMap);
+        this.committed = copyCommitted ? new HashSet<String>(copyFrom.committed) : new HashSet<String>();
+        this.varCache = copyFrom.varCache; // doesn't change after creation
+        this.varList = copyFrom.varList; // doesn't change after creation
+        this.tokens = new HashMap<String, String>(copyFrom.tokens);
+    }
+
+    public final List<String> dump()
+    {
+        final List<String> dump = new ArrayList<String>(varCache.size());
+        for (final Map.Entry<String, ConfigurationInfo> entry : varCache.entrySet())
+        {
+            dump.add(entry.getKey() + "," + entry.getValue().toString());
+        }
+        Collections.sort(dump);
+        return dump;
+    }
+
+    public void setVar(String var, String val, String source, int line) throws ConfigurationException
+    {
+        List<String> list = new LinkedList<String>();
+        list.add(val);
+        setVar(var, list, source, line, null, false);
+    }
+
+    public void setVar(String var, List<String> vals, String source, int line) throws ConfigurationException
+    {
+        setVar(var, vals, source, line, null, false);
+    }
+
+    public void setVar(String avar, List<String> vals, String source, int line, String contextPath, boolean append) throws ConfigurationException
+    {
+        String var = unalias(avar);
+        if (!isValidVar(var))
+            throw new ConfigurationException.UnknownVariable(var, source, line);
+
+        int argCount = getVarArgCount(var);
+
+        // -1 means unspecified length, its up to the receiving setter to validate.
+        if (argCount != -1)
+        {
+            addAnyDefaultArgValues(var, argCount, vals);
+
+            if (vals.size() != argCount)
+            {
+                throw new ConfigurationException.IncorrectArgumentCount(argCount, // expected
+                        vals.size(), //passed
+                var, source, line);
+            }
+        }
+
+        ConfigurationValue val = new ConfigurationValue(this, var,
+                                                         vals, //processValues( var, vals, source, line ),
+                                                         source, line, contextPath);
+        storeValue(var, val, append);
+        committed.remove(var);
+    }
+
+    public void clearVar(String avar, String source, int line) throws ConfigurationException
+    {
+        String var = unalias(avar);
+        if (!isValidVar(var))
+            throw new ConfigurationException.UnknownVariable(var, source, line);
+        varMap.remove(var);
+        committed.remove(var);
+    }
+
+    /**
+     * Remove the configuration values came from the given source.
+     * 
+     * @param source source name
+     * @see CommandLineConfigurator#SOURCE_COMMAND_LINE
+     */
+    public void clearSourceVars(String source)
+    {
+        List<String> remove = new LinkedList<String>();
+        for (Map.Entry<String, List<ConfigurationValue>> e : varMap.entrySet())
+        {
+            String var = e.getKey();
+            List<ConfigurationValue> vals = e.getValue();
+
+            List<ConfigurationValue> newvals = new LinkedList<ConfigurationValue>();
+            for (ConfigurationValue val : vals)
+            {
+                if (!val.getSource().equals(source))
+                {
+                    newvals.add(val);
+                }
+            }
+            if (newvals.size() > 0)
+                varMap.put(var, newvals);
+            else
+                remove.add(var);
+        }
+        for (Iterator<String> it = remove.iterator(); it.hasNext();)
+        {
+            varMap.remove(it.next());
+        }
+    }
+
+    public List<String> processValues(String var, List<String> args, String source, int line) throws ConfigurationException
+    {
+        List<String> newArgs = new LinkedList<String>();
+        for (Iterator<String> it = args.iterator(); it.hasNext();)
+        {
+            String arg = it.next();
+
+            int depth = 100;
+            while (depth-- > 0)
+            {
+                int o = arg.indexOf("${");
+                if (o == -1)
+                    break;
+
+                int c = arg.indexOf("}", o);
+
+                if (c == -1)
+                {
+                    throw new ConfigurationException.Token(ConfigurationException.Token.MISSING_DELIMITER,
+                                                           null, var, source, line);
+                }
+                String token = arg.substring(o + 2, c);
+                String value = getToken(token);
+
+                if (value == null)
+                {
+                    if (value == null)
+
+                    {
+                        throw new ConfigurationException.Token(ConfigurationException.Token.UNKNOWN_TOKEN,
+                                                                token, var, source, line);
+                    }
+
+                }
+                arg = arg.substring(0, o) + value + arg.substring(c + 1);
+
+            }
+            if (depth == 0)
+            {
+                throw new ConfigurationException.Token(ConfigurationException.Token.RECURSION_LIMIT,
+                                                        null, var, source, line);
+            }
+
+            newArgs.add(arg);
+        }
+        return newArgs;
+    }
+
+    public void setToken(String token, String value)
+    {
+        tokens.put(token, value);
+    }
+
+    public String getToken(String token)
+    {
+        if (tokens.containsKey(token))
+            return tokens.get(token);
+        else
+        {
+            try
+            {
+                return System.getProperty(token);
+            }
+            catch (SecurityException se)
+            {
+                return null;
+            }
+        }
+    }
+
+    private void storeValue(String avar, ConfigurationValue val, boolean append) throws ConfigurationException
+    {
+        String var = unalias(avar);
+        ConfigurationInfo info = getInfo(var);
+
+        List<ConfigurationValue> vals;
+        if (varMap.containsKey(var))
+        {
+            vals = varMap.get(var);
+            assert (vals.size() > 0);
+            ConfigurationValue first = vals.get(0);
+            if (!append && !first.getSource().equals(val.getSource()))
+                vals.clear();
+            else if (!info.allowMultiple())
+                throw new ConfigurationException.IllegalMultipleSet(
+                                                  var,
+                                                  val.getSource(), val.getLine());
+        }
+        else
+        {
+            vals = new LinkedList<ConfigurationValue>();
+            varMap.put(var, vals);
+        }
+        vals.add(val);
+    }
+
+    public List<ConfigurationValue> getVar(String avar)
+    {
+        String var = unalias(avar);
+        return varMap.get(var);
+    }
+
+    public Set<String> getVars()
+    {
+        return varCache.keySet();
+    }
+
+    public void merge(ConfigurationBuffer other)
+    {
+        assert (configClass == other.configClass);
+        varMap.putAll(other.varMap);
+        committed.addAll(other.committed);
+    }
+
+    private final Map<String, List<ConfigurationValue>> varMap; // list of vars that have been set
+    private final Set<String> committed; // set of vars committed to backing config
+    private final Class<? extends Configuration> configClass; // configuration class
+    private Map<String, ConfigurationInfo> varCache // info cache
+    = new HashMap<String, ConfigurationInfo>();
+    private List<String> requiredList = new LinkedList<String>(); // required vars
+    private List<String> varList = new LinkedList<String>(); // list of vars in order they should be set
+    private Map<String, String> aliases = new HashMap<String, String>(); // variable name aliases
+    private Map<String, String> tokens = new HashMap<String, String>(); // tokens for replacement
+    private List<Object[]> positions = new ArrayList<Object[]>();
+
+    private static final String SET_PREFIX = "cfg";
+    private static final String GET_PREFIX = "get";
+    private static final String INFO_SUFFIX = "Info";
+
+    //-----------------------------------------------
+    //
+
+    /**
+     * WORKAROUND FOR BUG CMP-396
+     * 
+     * <p>
+     * {@link #c2h(String)} generates option names based on cfgXXX names in
+     * {@code Configuration}. Since we collapsed all the sub-configurations into
+     * one class, there's no longer a "base name" like "compiler.*" or
+     * "compiler.fonts.*". In order to preserve the dotted naming convention, we
+     * need to know which "-" separated names are actually dotted names. The
+     * {@link #CONVERT_FROM} and {@link #CONVERT_TO} is an <b>ordered</b> lookup
+     * table for option group base names. It's order makes sure that the longest
+     * possible replacement is done.
+     */
+    private static final ImmutableList<String> CONVERT_FROM =
+            ImmutableList.of(
+                    "compiler-fonts-languages-",
+                    "compiler-fonts-",
+                    "compiler-namespaces-",
+                    "compiler-mxml-",
+                    "compiler-",
+                    "metadata-",
+                    "licenses-",
+                    "frames-",
+                    "runtime-shared-library-settings-");
+
+    private static final ImmutableList<String> CONVERT_TO =
+            ImmutableList.of(
+                    "compiler.fonts.languages.",
+                    "compiler.fonts.",
+                    "compiler.namespaces.",
+                    "compiler.mxml.",
+                    "compiler.",
+                    "metadata.",
+                    "licenses.",
+                    "frames.",
+                    "runtime-shared-library-settings.");
+
+    /**
+     * convert StudlyCaps or camelCase to hyphenated
+     * 
+     * @param camel someVar or SomeVar
+     * @return hyphen some-var
+     */
+    protected static String c2h(String camel)
+    {
+        StringBuilder b = new StringBuilder(camel.length() + 5);
+        for (int i = 0; i < camel.length(); ++i)
+        {
+            char c = camel.charAt(i);
+            if (Character.isUpperCase(c))
+            {
+                if (i != 0)
+                    b.append('-');
+                b.append(Character.toLowerCase(c));
+            }
+            else
+            {
+                b.append(camel.charAt(i));
+            }
+        }
+        final String combined = b.toString();
+
+        for (int i = 0; i < CONVERT_FROM.size(); i++)
+        {
+            if (combined.startsWith(CONVERT_FROM.get(i)))
+            {
+                return combined.replaceFirst(CONVERT_FROM.get(i), CONVERT_TO.get(i));
+            }
+        }
+        return combined;
+    }
+
+    /**
+     * convert hyphenated to StudlyCaps or camelCase
+     * 
+     * @param hyphenated some-var
+     * @return result
+     */
+    protected static String h2c(String hyphenated, boolean studly)
+    {
+        StringBuilder b = new StringBuilder(hyphenated.length());
+        boolean capNext = studly;
+        for (int i = 0; i < hyphenated.length(); ++i)
+        {
+            char c = hyphenated.charAt(i);
+            if (c == '-')
+                capNext = true;
+            else
+            {
+                b.append(capNext ? Character.toUpperCase(c) : c);
+                capNext = false;
+            }
+        }
+        return b.toString();
+    }
+
+    public static String varname(String membername, String basename)
+    {
+        return ((basename == null) ? membername : (basename + "." + membername));
+    }
+
+    private static ConfigurationInfo createInfo(Method setterMethod)
+    {
+        ConfigurationInfo info = null;
+
+        String infoMethodName = GET_PREFIX + setterMethod.getName().substring(SET_PREFIX.length()) + INFO_SUFFIX;
+        String getterMethodName = GET_PREFIX + setterMethod.getName().substring(SET_PREFIX.length());
+        @SuppressWarnings("unchecked")
+        Class<? extends Configuration> cfgClass = (Class<? extends Configuration>)setterMethod.getDeclaringClass();
+
+        Method infoMethod = null, getterMethod = null;
+        if (!setterMethod.isAnnotationPresent(Config.class))
+        {
+            try
+            {
+                infoMethod = cfgClass.getMethod(infoMethodName);
+    
+                if (!Modifier.isStatic(infoMethod.getModifiers()))
+                {
+                    assert false : ("coding error: " + cfgClass.getName() + "." + infoMethodName + " needs to be static!");
+                    infoMethod = null;
+                }
+    
+                info = (ConfigurationInfo)infoMethod.invoke(null, (Object[])null);
+    
+            }
+            catch (SecurityException e)
+            {
+                e.printStackTrace();
+            }
+            catch (NoSuchMethodException e)
+            {
+            }
+            catch (IllegalArgumentException e)
+            {
+                e.printStackTrace();
+            }
+            catch (IllegalAccessException e)
+            {
+                e.printStackTrace();
+            }
+            catch (InvocationTargetException e)
+            {
+                e.printStackTrace();
+            }
+        }
+        
+        if (info == null)
+            info = new ConfigurationInfo();
+
+        try
+        {
+            getterMethod = cfgClass.getMethod(getterMethodName, (Class[])null);
+        }
+        catch (SecurityException e)
+        {
+            e.printStackTrace();
+        }
+        catch (NoSuchMethodException e)
+        {
+        }
+        info.setSetterMethod(setterMethod);
+        info.setGetterMethod(getterMethod);
+
+        return info;
+    }
+
+    /**
+     * load - prefetch all the interesting names into a dictionary so that we
+     * can find them again more easily. At the end of this call, we will have a
+     * list of every variable and their associated method.
+     * 
+     * @param filter if null there is no filter, otherwise the set of
+     * configuration options is filtered.
+     */
+    private boolean loadCache(Class<? extends Configuration> cfg, IConfigurationFilter filter)
+    {
+        int count = 0;
+
+        // First, find all vars at this level.
+        for (final Method method : cfg.getMethods())
+        {
+            if (method.getName().startsWith(SET_PREFIX) ||
+                method.isAnnotationPresent(Config.class))
+            {
+                String configName = null;
+
+                final Class<?>[] pt = method.getParameterTypes();
+                assert pt.length > 1 : "Expected at least one parameters on setter.";
+
+                // Collect configuration info from getXXXInfo() static methods.
+                final ConfigurationInfo info = createInfo(method);
+
+                // Collect configuration info from annotations.
+                final Config config = method.getAnnotation(Config.class);
+                if (config != null)
+                {
+                    info.isAdvanced = config.advanced();
+                    info.isHidden = config.hidden();
+                    info.isRemoved = config.removed();
+                    info.allowMultiple = config.allowMultiple();
+                    info.isPath = config.isPath();
+                    info.isDisplayed = config.displayed();
+                    info.isCompcOnly = config.compcOnly();
+                    info.isRequired = config.isRequired();
+                    
+                    // Argument name generator class
+                    final ArgumentNameGenerator argumentNameGeneratorClass = 
+                        method.getAnnotation(ArgumentNameGenerator.class);
+                    if (argumentNameGeneratorClass != null)
+                    {
+                        info.argNameGeneratorClass = argumentNameGeneratorClass.value();
+                    }
+                    else
+                    {
+                        // Argument names
+                        final Arguments arguments = method.getAnnotation(Arguments.class);
+                        if (arguments != null)
+                            info.argnames = arguments.value();
+                    }
+                    
+                    // Argument count
+                    final InfiniteArguments infinite = method.getAnnotation(InfiniteArguments.class);
+                    if (infinite != null)
+                        info.argcount = ConfigurationInfo.INFINITE_ARGS;
+
+                    // Soft Prerequisites
+                    final SoftPrerequisites softPre = method.getAnnotation(SoftPrerequisites.class);
+                    if (softPre != null)
+                        info.softPrerequisites = softPre.value();
+                    
+                    // XML element name for configuration
+                    final Mapping mapping = method.getAnnotation(Mapping.class);
+                    if (mapping != null)
+                        configName = Joiner.on(".").skipNulls().join(mapping.value());
+                    
+                    // Is this a Flex only option?
+                    final RoyaleOnly royaleOnly = method.getAnnotation(RoyaleOnly.class);
+                    if (royaleOnly != null)
+                        info.isRoyaleOnly = true;
+                }
+
+                // Fall back to naming convention for configuration names.
+                if (configName == null)
+                    configName = c2h(method.getName().substring(SET_PREFIX.length()));
+
+                if( filter == null || filter.select(configName) )
+                {
+                    varCache.put(configName, info);
+                    varList.add(configName);
+                    if (info.isRequired())
+                    {
+                        requiredList.add(configName);
+                    }
+                    ++count;
+                }
+            }
+        }
+
+        assert (count > 0 || filter != null) : "coding error: config class " + cfg.getName() + " did not define any setters or child configs";
+        return (count > 0);
+    }
+
+    String classToArgName(Class<?> c)
+    {
+        // we only support builtin classnames!
+
+        String className = c.getName();
+        if (className.startsWith("java.lang."))
+            className = className.substring("java.lang.".length());
+
+        return className.toLowerCase();
+    }
+
+    public ConfigurationInfo getInfo(String avar)
+    {
+        String var = unalias(avar);
+        return varCache.get(var);
+    }
+
+    public String getVarArgName(String avar, int argnum)
+    {
+        String var = unalias(avar);
+        ConfigurationInfo info = getInfo(var);
+
+        if (info == null)
+        {
+            assert false : ("must call isValid to check vars!");
+        }
+
+        return info.getArgName(argnum);
+    }
+
+    public boolean isValidVar(String avar)
+    {
+        String var = unalias(avar);
+        ConfigurationInfo info = getInfo(var);
+        return (info != null);
+    }
+
+    public int getVarArgCount(String avar)
+    {
+        ConfigurationInfo info = getInfo(avar);
+        assert (info != null);
+        return info.getArgCount();
+    }
+
+    /**
+     * Add any default values to an argument, if the user did not specify them
+     * on the command line.
+     * 
+     * @param avar the argument variable
+     * @param argCount the number of argument values specified
+     * @param vals Values to add any default values to
+     */
+    private void addAnyDefaultArgValues(String avar, int argCount, List<String> vals)
+    {
+        ConfigurationInfo info = getInfo(avar);
+        final int missingArgsCount = argCount - vals.size();
+        if (missingArgsCount == 0 || info.getDefaultArgValues() == null)
+            return;
+
+        final String[] defaultArgValues = info.getDefaultArgValues();
+        final int defaultArgsCount = defaultArgValues.length;
+        final int defaultArgsStart = defaultArgsCount - missingArgsCount;
+        for (int i = defaultArgsStart; i < defaultArgsCount; i++)
+        {
+            vals.add(defaultArgValues[i]);
+        }
+    }
+
+    /**
+     * commit - bake the resolved map to the configuration
+     * 
+     * @param config The configuration to set the buffer variables into.
+     * @param problems A collection where configuration problems are reported.
+     * 
+     * @return true if successful, false otherwise.
+     */
+    public boolean commit(Object config, Collection<ICompilerProblem> problems)
+    {
+        assert (config.getClass() == configClass) : 
+            ("coding error: configuration " + config.getClass() + " != template " + configClass);
+        Set<String> done = new HashSet<String>();
+        boolean success = true;
+        
+        for (Iterator<String> vars = varList.iterator(); vars.hasNext();)
+        {
+            String var = vars.next();
+            if (varMap.containsKey(var))
+            {
+                try
+                {
+                    commitVariable(config, var, done);
+                }
+                catch (ConfigurationException e)
+                {
+                    problems.add(new ConfigurationProblem(e));
+                    success = false;
+                }
+            }
+        }
+
+        for (Iterator<String> reqs = requiredList.iterator(); reqs.hasNext();)
+        {
+            String req = reqs.next();
+
+            if (!committed.contains(req))
+            {
+                ConfigurationException e = new ConfigurationException.MissingRequirement(req, null, null, -1);
+                problems.add(new ConfigurationProblem(
+                        null,
+                        -1,
+                        -1,
+                        -1,
+                        -1,
+                        e.getMessage()));
+                success = false;
+            }
+        }
+        
+        return success;
+    }
+
+    /**
+     * commitVariable - copy a variable out of a state into the final config.
+     * This should only be called on variables that are known to exist in the
+     * state!
+     * 
+     * @param var variable name to lookup
+     * @param done set of variable names that have been completed so far (for
+     * recursion)
+     */
+    private void commitVariable(Object config, String var, Set<String> done) throws ConfigurationException
+    {
+        ConfigurationInfo info = getInfo(var);
+
+        setPrerequisites(info.getPrerequisites(), var, done, config, true);
+        setPrerequisites(info.getSoftPrerequisites(), var, done, config, false);
+
+        if (committed.contains(var))
+            return;
+
+        committed.add(var);
+        done.add(var);
+
+        assert (varMap.containsKey(var));
+        List<ConfigurationValue> vals = varMap.get(var);
+
+        if (vals.size() > 1)
+        {
+            assert (info.allowMultiple()); // assumed to have been previously checked
+        }
+        for (ConfigurationValue val : vals)
+        {
+            try
+            {
+                Object[] args = buildArgList(info, val);
+
+                info.getSetterMethod().invoke(config, args);
+            }
+            catch (Exception e)
+            {
+                Throwable t = e;
+
+                if (e instanceof InvocationTargetException)
+                {
+                    t = ((InvocationTargetException)e).getTargetException();
+                }
+
+                if (Trace.error)
+                    t.printStackTrace();
+
+                if (t instanceof ConfigurationException)
+                {
+                    throw (ConfigurationException)t;
+                }
+                else
+                {
+                    throw new ConfigurationException.OtherThrowable(t, var, val.getSource(), val.getLine());
+                }
+            }
+        }
+
+    }
+
+    private void setPrerequisites(String[] prerequisites, String var, Set<String> done, Object config, boolean required)
+            throws ConfigurationException
+    {
+        if (prerequisites != null)
+        {
+            for (int p = 0; p < prerequisites.length; ++p)
+            {
+                String depvar = prerequisites[p];
+
+                // Dependencies can only go downward.
+                int dot = var.lastIndexOf('.');
+
+                if (dot >= 0)
+                {
+                    String car = var.substring(0, dot);
+                    //String cdr = var.substring( dot + 1 );
+
+                    String newDepvar = car + "." + depvar;
+
+                    // Since in royale we have collapsed sub-configurations into one
+                    // configuration, some options that were in sub-configurations now
+                    // have prerequisites on options in the same configuration. We
+                    // need to keep the old configuration mappings so old configurations
+                    // options will still work. So a simple thing we can do is if the 
+                    // dependency variable is invalid (presumably because the 
+                    // dependency is really on a parent configuration option), 
+                    // then use the dependency as is depvar) and see if it is 
+                    // valid. If depvar ends up not being valid then set depvar
+                    // to newDepvar so error reporting isn't changed by the new
+                    // fall-back behavior.
+                    if (isValidVar(newDepvar) || !isValidVar(depvar))
+                        depvar = newDepvar;
+
+                }
+
+                if (!done.contains(depvar))
+                {
+                    if (!isValidVar(depvar))
+                    {
+                        assert false : ("invalid " + var + " dependency " + depvar);
+                        continue;
+                    }
+                    if (varMap.containsKey(depvar))
+                    {
+                        commitVariable(config, depvar, done);
+                    }
+                    else if (required && !committed.contains(depvar))
+                    {
+                        // TODO - can we get source/line for this?
+                        throw new ConfigurationException.MissingRequirement(depvar, var, null, -1);
+                    }
+                }
+            }
+        }
+    }
+
+    private String[] constructStringArray(List<String> args)
+    {
+        String[] sa = new String[args.size()];
+
+        int i = 0;
+        for (Iterator<String> it = args.iterator(); it.hasNext();)
+            sa[i++] = it.next();
+
+        return sa;
+    }
+
+    private Object constructValueObject(ConfigurationInfo info, ConfigurationValue cv) throws ConfigurationException
+    {
+        try
+        {
+            Class<?>[] pt = info.getSetterMethod().getParameterTypes();
+            assert (pt.length == 2); // assumed to be checked upstream
+
+            Object o = pt[1].newInstance();
+
+            Field[] fields = pt[1].getFields();
+
+            assert (fields.length == cv.getArgs().size()); // assumed to be checked upstream
+
+            Iterator<String> argsit = cv.getArgs().iterator();
+            for (int f = 0; f < fields.length; ++f)
+            {
+                String val = (String)argsit.next();
+                Object valobj = null;
+                Class<?> fc = fields[f].getType();
+
+                assert (info.getArgType(f) == fc);
+                assert (info.getArgName(f).equals(ConfigurationBuffer.c2h(fields[f].getName())));
+
+                if (fc == String.class)
+                {
+                    valobj = val;
+                }
+                else if ((fc == Boolean.class) || (fc == boolean.class))
+                {
+                    // TODO - Boolean.valueOf is pretty lax.  Maybe we should restrict to true/false?
+                    valobj = Boolean.valueOf(val);
+                }
+                else if ((fc == Integer.class) || (fc == int.class))
+                {
+                    valobj = Integer.decode(val);
+                }
+                else if ((fc == Long.class) || (fc == long.class))
+                {
+                    valobj = Long.decode(val);
+                }
+                else
+                {
+                    assert false; // should have checked any other condition upstream!
+                }
+                fields[f].set(o, valobj);
+            }
+
+            return o;
+        }
+        catch (InstantiationException e)
+        {
+            assert false : ("coding error: unable to instantiate value object when trying to set var " + cv.getVar());
+            throw new ConfigurationException.OtherThrowable(e, cv.getVar(), cv.getSource(), cv.getLine());
+
+        }
+        catch (IllegalAccessException e)
+        {
+            assert false : ("coding error: " + e + " when trying to set var " + cv.getVar());
+            throw new ConfigurationException.OtherThrowable(e, cv.getVar(), cv.getSource(), cv.getLine());
+        }
+    }
+
+    protected static boolean isSupportedSimpleType(Class<?> c)
+    {
+        return ((c == String.class)
+                || (c == Integer.class) || (c == int.class)
+                || (c == Long.class) || (c == long.class)
+                || (c == Boolean.class) || (c == boolean.class));
+    }
+
+    protected static boolean isSupportedListType(Class<?> c)
+    {
+        return ((c == List.class) || (c == String[].class));
+    }
+
+    protected static boolean isSupportedValueType(Class<?> c)
+    {
+        if (isSupportedSimpleType(c))
+            return false;
+
+        Field[] fields = c.getFields();
+
+        for (int f = 0; f < fields.length; ++f)
+        {
+            if (!isSupportedSimpleType(fields[f].getType()))
+                return false;
+        }
+        return true;
+    }
+
+    private Object[] buildArgList(ConfigurationInfo info, ConfigurationValue val) throws ConfigurationException
+    {
+        Method setter = info.getSetterMethod();
+
+        Class<?>[] pt = setter.getParameterTypes();
+
+        List<String> args = processValues(val.getVar(), val.getArgs(), val.getSource(), val.getLine());
+
+        if (info.getArgCount() == -1)
+        {
+            if (pt.length != 2)
+            {
+                assert false : ("coding error: unlimited length setter " + val.getVar() + " must take a single argument of type List or String[]");
+                return null;
+            }
+            else if (List.class.isAssignableFrom(pt[1]))
+            {
+                return new Object[] {val, args};
+            }
+            else if (String[].class.isAssignableFrom(pt[1]))
+            {
+                return new Object[] {val, constructStringArray(args)};
+            }
+            else
+            {
+                assert false : ("coding error: unlimited length setter " + val.getVar() + " must take a single argument of type List or String[]");
+                return null;
+            }
+        }
+        else
+        {
+            assert (pt.length > 1) : ("coding error: config setter " + val.getVar() + " must accept at least one argument");
+            // ok, we first check to see if the signature of their setter accepts a list.
+
+            if (pt.length == 2)
+            {
+                // a variety of specialty setters here...
+
+                if (List.class.isAssignableFrom(pt[1]))
+                {
+                    return new Object[] {val, args};
+                }
+                else if (String[].class == pt[1])
+                {
+                    return new Object[] {val, constructStringArray(args)};
+                }
+                else if (isSupportedValueType(pt[1]))
+                {
+                    return new Object[] {val, constructValueObject(info, val)};
+                }
+            }
+
+            // otherwise, they must have a matching size parm list as the number of args passed in.
+
+            assert (pt.length == (args.size() + 1)) : ("coding error: config setter " + val.getVar() + " does not have " + args.size() + " parameters!");
+
+            Object[] pa = new Object[pt.length];
+
+            pa[0] = val;
+
+            for (int p = 1; p < pt.length; ++p)
+            {
+                String arg = args.get(p - 1);
+                if (pt[p].isAssignableFrom(String.class))
+                {
+                    pa[p] = arg;
+                }
+                else if ((pt[p] == int.class) || (pt[p] == Integer.class))
+                {
+                    try
+                    {
+                        pa[p] = Integer.decode(arg);
+
+                    }
+                    catch (Exception e)
+                    {
+                        throw new ConfigurationException.TypeMismatch(ConfigurationException.TypeMismatch.INTEGER,
+                                                                       arg, val.getVar(), val.getSource(), val.getLine());
+                    }
+                }
+                else if ((pt[p] == long.class) || (pt[p] == Long.class))
+                {
+                    try
+                    {
+                        pa[p] = Long.decode(arg);
+
+                    }
+                    catch (Exception e)
+                    {
+                        throw new ConfigurationException.TypeMismatch(
+                                ConfigurationException.TypeMismatch.LONG,
+                                arg, val.getVar(), val.getSource(), val.getLine());
+                    }
+                }
+                else if ((pt[p] == boolean.class) || (pt[p] == Boolean.class))
+                {
+                    try
+                    {
+                        arg = arg.trim().toLowerCase();
+                        if (arg.equals("true") || arg.equals("false"))
+                        {
+                            pa[p] = Boolean.valueOf(arg);
+                        }
+                        else
+                        {
+                            throw new ConfigurationException.TypeMismatch(
+                                    ConfigurationException.TypeMismatch.BOOLEAN, arg, val.getVar(), val.getSource(), val.getLine());
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        throw new ConfigurationException.TypeMismatch(
+                                ConfigurationException.TypeMismatch.BOOLEAN, arg, val.getVar(), val.getSource(), val.getLine());
+                    }
+                }
+                else
+                {
+                    assert false : ("coding error: " + val.getVar() + " setter argument " + p + " is not a supported type");
+                }
+            }
+
+            return pa;
+        }
+    }
+
+    public void addAlias(String alias, String var)
+    {
+        if (!isValidVar(var))
+        {
+            assert false : ("coding error: can't bind alias " + alias + " to nonexistent var " + var);
+            return;
+        }
+        if (aliases.containsKey(alias))
+        {
+            assert false : ("coding error: alias " + alias + " already defined as " + aliases.get(alias));
+            return;
+        }
+        if (varCache.containsKey(alias))
+        {
+            assert false : ("coding error: can't define alias " + alias + ", it already exists as a var");
+            return;
+        }
+
+        aliases.put(alias, var);
+    }
+
+    public Map<String, String> getAliases()
+    {
+        return aliases;
+    }
+
+    public String unalias(String var)
+    {
+        String realvar = aliases.get(var);
+        return (realvar == null) ? var : realvar;
+    }
+
+    public String peekSimpleConfigurationVar(String avar) throws ConfigurationException
+    {
+        String val = null;
+        List<ConfigurationValue> valList = getVar(avar);
+        if (valList != null)
+        {
+            ConfigurationValue cv = (ConfigurationValue)valList.get(0);
+            List<String> args = processValues(avar, cv.getArgs(), cv.getSource(), cv.getLine());
+            val = args.get(0);
+        }
+        return val;
+    }
+
+    public List<ConfigurationValue> peekConfigurationVar(String avar) throws ConfigurationException
+    {
+        List<ConfigurationValue> srcList = getVar(avar);
+        if (srcList == null)
+            return null;
+
+        List<ConfigurationValue> dstList = new LinkedList<ConfigurationValue>();
+        for (ConfigurationValue srcVal : srcList)
+        {
+            List<String> args = processValues(avar, srcVal.getArgs(), srcVal.getSource(), srcVal.getLine());
+
+            ConfigurationValue dstVal = new ConfigurationValue(srcVal.getBuffer(), avar, args, srcVal.getSource(), srcVal.getLine(), srcVal.getContext());
+            dstList.add(dstVal);
+        }
+        return dstList;
+    }
+
+    public void addPosition(String var, int iStart, int iEnd)
+    {
+        positions.add(new Object[] {var, new Integer(iStart), new Integer(iEnd)});
+    }
+
+    public List<Object[]> getPositions()
+    {
+        return positions;
+    }
+
+    public static List<String> formatText(String input, int columns)
+    {
+        ArrayList<String> lines = new ArrayList<String>();
+
+        if ((input == null) || (input.length() == 0))
+            return lines;
+
+        int current = 0;
+        int lineStart = -1;
+        int lineEnd = -1;
+        int wordStart = -1;
+        int wordEnd = -1;
+        boolean start = true;
+        boolean preserve = true;
+
+        while (true)
+        {
+            if (current < input.length())
+            {
+                boolean newline = input.charAt(current) == '\n';
+                boolean printable = (preserve && !newline) || !Character.isWhitespace(input.charAt(current));
+
+                if (start) // find a word
+                {
+                    if (printable)
+                    {
+                        if (lineStart == -1)
+                        {
+                            lineStart = current;
+                        }
+                        wordStart = current;
+                        start = false;
+                    }
+                    else
+                    {
+                        if (newline && lineStart != -1)
+                        {
+                            lines.add(input.substring(lineStart, current));
+                            lineStart = -1;
+                        }
+                        else if (newline)
+                        {
+                            lines.add("");
+                        }
+                        ++current;
+                    }
+                }
+                else
+                // have a word
+                {
+                    preserve = false;
+                    if (printable)
+                    {
+                        ++current;
+                    }
+                    else
+                    {
+                        wordEnd = current;
+                        if (lineEnd == -1)
+                        {
+                            lineEnd = current;
+                        }
+
+                        // two possibilities; if the new word fits in the current line length
+                        // without being too many columns, leave on current line.
+                        // otherwise, set it as the start of a new line.
+
+                        if (wordEnd - lineStart < columns)
+                        {
+                            if (newline)
+                            {
+                                lines.add(input.substring(lineStart, current));
+                                lineStart = -1;
+                                lineEnd = -1;
+                                wordStart = -1;
+                                start = true;
+                                preserve = true;
+                                ++current;
+                            }
+                            else
+                            {
+                                // we have room to add the current word to this line, find new word
+                                start = true;
+                                lineEnd = current;
+                            }
+                        }
+                        else
+                        {
+                            // current word pushes things beyond the requested column limit,
+                            // dump current text
+                            lines.add(input.substring(lineStart, lineEnd));
+                            lineStart = wordStart;
+                            lineEnd = -1;
+                            wordStart = -1;
+                            start = true;
+                            if (newline)
+                                preserve = true;
+                        }
+                    }
+                }
+            }
+            else
+            // we're done
+            {
+                // a) no line yet, so don't do anything
+                // b) have line and new word would push over edge, need two lines
+                // c) have line and current word fits, need one line
+                // d) only one word and its too long anyway, need one line
+
+                if (lineStart != -1) // we have a line in progress
+                {
+                    wordEnd = current;
+                    if (lineEnd == -1)
+                        lineEnd = current;
+
+                    if (((wordEnd - lineStart) < columns) // current word fits
+                        || (wordEnd == lineEnd)) // or one long word
+                    {
+                        lineEnd = wordEnd;
+                        lines.add(input.substring(lineStart, wordEnd));
+                    }
+                    else
+                    // didn't fit, multiple words
+                    {
+                        lines.add(input.substring(lineStart, lineEnd));
+                        lines.add(input.substring(wordStart, wordEnd));
+                    }
+                }
+                break;
+            }
+        }
+        return lines;
+    }
+
+    /**
+     * For debugging only.
+     * <p>
+     * Produces an alphabetized list of this buffer's configuration options and their values.
+     * An option such as
+     * <pre>
+     * -foo=aaa,bbb -foo+=ccc
+     * </pre>
+     * will appear as
+     * <pre>
+     * foo=aaa,bbb;ccc
+     * </pre>
+     */
+    @Override
+    public String toString()
+    {
+        StringBuffer sb = new StringBuffer();
+        
+        String[] variables = varMap.keySet().toArray(new String[0]);
+        Arrays.sort(variables);
+        
+        for (String var : variables)
+        {
+            sb.append(var);
+            sb.append("=");
+
+            ArrayList<String> commaSeparatedValues = new ArrayList<String>();
+            for (ConfigurationValue cv : varMap.get(var))
+            {
+                List<String> args = cv.getArgs();
+                String joinedArgs = Joiner.on(',').join(args);
+                commaSeparatedValues.add(joinedArgs);
+            }
+            String rhs = Joiner.on(';').join(commaSeparatedValues);
+            sb.append(rhs);
+            
+            sb.append('\n');
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/config/ConfigurationInfo.java b/linter/src/main/java/org/apache/royale/linter/config/ConfigurationInfo.java
new file mode 100644
index 0000000..87588fb
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/config/ConfigurationInfo.java
@@ -0,0 +1,473 @@
+/*
+ *
+ *  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.royale.linter.config;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import org.apache.royale.compiler.internal.config.annotations.DefaultArgumentValue;
+import com.google.common.base.Joiner;
+import com.google.common.base.MoreObjects;
+
+/**
+ * Meta information for each configuration options. It is created by
+ * {@link ConfigurationBuffer#loadCache} from either annotations or
+ * {@code public static ConfigurationInfo getFooInfo();} methods in
+ * {@link Configuration} class.
+ */
+public class ConfigurationInfo
+{
+    public static final int NOT_SET = -2;
+    public static final int INFINITE_ARGS = -1;
+
+    /**
+     * This ctor is used when everything can be introspected off the setter
+     * method, or else when the names/types are provided by method overrides
+     * rather than ctor arguments
+     */
+    public ConfigurationInfo()
+    {
+        this.argcount = NOT_SET;
+        this.argnames = null;
+    }
+
+    /**
+     * Simple ctor for restricting the number of arguments.
+     * 
+     * @param argcount number of args, -1 for an infinite list
+     */
+    public ConfigurationInfo(int argcount)
+    {
+        this.argcount = argcount;
+        this.argnames = null;
+    }
+
+    /**
+     * Simple ctor for naming the arguments.
+     * 
+     * @param argnames list of argnames, argcount will default to # of elements
+     */
+    public ConfigurationInfo(String argnames[])
+    {
+        this.argcount = argnames.length;
+        this.argnames = argnames;
+    }
+
+    /**
+     * Use this ctor when you want to set a single list of some number of
+     * identically named args
+     * 
+     * @param argcount number of arguments (-1 for infinite)
+     * @param argname name of each argument
+     */
+    public ConfigurationInfo(int argcount, String argname)
+    {
+        this.argcount = argcount;
+        this.argnames = new String[] {argname};
+    }
+
+    /**
+     * More unusual ctor, this would let you have the first few args named one
+     * thing, the rest named something else. It is far more likely that you want
+     * a constrained list of names or else an arbitrary list of identical names.
+     * 
+     * @param argcount number of arguments
+     * @param argnames array of argument names
+     */
+    public ConfigurationInfo(int argcount, String argnames[])
+    {
+        this.argcount = argcount;
+        this.argnames = argnames;
+    }
+
+    public final int getArgCount()
+    {
+        return argcount;
+    }
+
+    protected int argcount = NOT_SET;
+
+    protected String[] defaultArgValues = null;
+
+    /**
+     * Get any default values for an argument
+     * 
+     * @return an array of default argument values.  May be null
+     */
+    public final String[] getDefaultArgValues()
+    {
+        return defaultArgValues;
+    }
+
+    private static String classToArgName(Class<?> c)
+    {
+        // we only support builtin classnames!
+
+        String className = c.getName();
+        if (className.startsWith("java.lang."))
+            className = className.substring("java.lang.".length());
+
+        return className.toLowerCase();
+    }
+
+    /**
+     * Return the name of each parameter. The default implementation is usually
+     * sufficient for simple cases, but one could do wacky things here like
+     * support an infinite list of alternating arg names.
+     * 
+     * @param argnum The argument number.
+     * @return name of argument
+     */
+    public String getArgName(int argnum)
+    {
+        if (argNameGeneratorClass != null)
+        {
+            Method getArgNameMethod;
+            try
+            {
+                getArgNameMethod = argNameGeneratorClass.getMethod("getArgumentName", int.class);
+                return (String)getArgNameMethod.invoke(null, argnum);
+            }
+            catch (Exception e)
+            {
+                // TODO: connect these exception to our problem logging subsystem.
+                e.printStackTrace();
+            }
+
+            return "";
+        }
+
+        if ((argnames == null) || (argnames.length == 0))
+        {
+            return classToArgName(getArgType(argnum));
+        }
+        else if (argnum >= argnames.length)
+        {
+            return argnames[argnames.length - 1];
+        }
+        else
+        {
+            return argnames[argnum];
+        }
+    }
+
+    /**
+     * Return the type of each parameter. This is computed based on your setter,
+     * and cannot be overridden
+     * 
+     * @param argnum The argument number.
+     */
+    public final Class<?> getArgType(int argnum)
+    {
+        if (argnum >= argtypes.length)
+        {
+            return argtypes[argtypes.length - 1];
+        }
+        else
+        {
+            return argtypes[argnum];
+        }
+    }
+
+    protected Class<?> argNameGeneratorClass;
+    protected String[] argnames;
+    protected Class<?>[] argtypes;
+
+    protected String[] prerequisites = null;
+
+    /**
+     * Return variable names that should be set before this one. The buffer is
+     * always set such that it tries to set all variables at a given level
+     * before setting child values, but you could override by using this. Its
+     * probably a bad idea to depend on children, though. It is unnecessary to
+     * set parent vars as prerequisites, since they are implicitly set first
+     */
+    public String[] getPrerequisites()
+    {
+        return prerequisites;
+    }
+
+    protected String[] softPrerequisites = null;
+
+    /**
+     * Prerequisites which should be set before this one if they exist
+     */
+    public String[] getSoftPrerequisites()
+    {
+        return softPrerequisites;
+    }
+
+    protected boolean allowMultiple = false;
+
+    /**
+     * Variables are generally only allowed to be set once in a given
+     * file/cmdline. It is sometimes useful to allow the same set multiple times
+     * in order to aggregate values.
+     * 
+     * @return true if the setter can be called multiple times
+     */
+    public boolean allowMultiple()
+    {
+        return allowMultiple;
+    }
+
+    protected String[] aliases = null;
+
+    /**
+     * Return an array of other names for this variable.
+     */
+    public String[] getAliases()
+    {
+        return aliases;
+    }
+
+    protected boolean isAdvanced = false;
+
+    /**
+     * Override to make a variable hidden by default (i.e. you need -advanced on
+     * the cmdline)
+     */
+    public boolean isAdvanced()
+    {
+        return isAdvanced;
+    }
+
+    protected boolean isHidden = false;
+
+    /**
+     * Override to make a variable completely hidden
+     */
+    public boolean isHidden()
+    {
+        return isHidden;
+    }
+
+    protected boolean isDisplayed = true;
+
+    /**
+     * Override to prevent printing when dumping configuration
+     */
+    public boolean isDisplayed()
+    {
+        return isDisplayed;
+    }
+
+    /**
+     * If a variable -must- be set, override this
+     */
+    public boolean isRequired()
+    {
+        return isRequired;
+    }
+
+    protected boolean isRequired = false;
+
+    /**
+     * Magic used by the command line configurator only at the moment to decide
+     * whether this variable should eat all subsequent arguments. Useful for
+     * -help...
+     */
+    public boolean isGreedy()
+    {
+        return isGreedy;
+    }
+
+    protected boolean isGreedy = false;
+
+    public boolean isPath()
+    {
+        return isPath;
+    }
+
+    protected boolean isPath = false;
+
+    public boolean doChecksum()
+    {
+        return true;
+    }
+
+    public String getDeprecatedMessage()
+    {
+        return deprecatedMessage;
+    }
+
+    protected String deprecatedMessage = null;
+
+    public boolean isDeprecated()
+    {
+        return isDeprecated;
+    }
+
+    protected boolean isDeprecated = false;
+
+    public String getDeprecatedReplacement()
+    {
+        return deprecatedReplacement;
+    }
+
+    protected String deprecatedReplacement;
+
+    public String getDeprecatedSince()
+    {
+        return deprecatedSince;
+    }
+
+    protected String deprecatedSince;
+    
+    /**
+     * @return True indicates that the option is no longer 
+     * supported and will not have any affect.
+     */
+    public boolean isRemoved() 
+    {
+        return isRemoved;
+    }
+
+    protected boolean isRemoved = false;
+    
+    /**
+     * @return True the option requires Flex in order to be useful. 
+     */
+    public boolean isRoyaleOnly() 
+    {
+        return isRoyaleOnly;
+    }
+
+    protected boolean isRoyaleOnly = false;
+    
+    protected final void setSetterMethod(Method setter)
+    {
+        Class<?>[] pt = setter.getParameterTypes();
+
+        assert (pt.length >= 2) : ("coding error: config setter must take at least 2 args!");
+
+        this.setter = setter;
+
+        if (pt.length == 2)
+        {
+            Class<?> c = pt[1];
+
+            if (ConfigurationBuffer.isSupportedListType(c))
+            {
+                if (argcount == NOT_SET)
+                    argcount = -1; // infinite list
+
+                argtypes = new Class[] {String.class};
+                return;
+            }
+            else if (ConfigurationBuffer.isSupportedValueType(c))
+            {
+                assert (argcount == NOT_SET) : ("coding error: value object setter cannot override argcount");
+                assert (argnames == null) : ("coding error: value object setter cannot override argnames");
+
+                Field[] fields = c.getFields();
+
+                argcount = fields.length;
+
+                assert (argcount > 0) : ("coding error: " + setter + " value object " + c.getName() + " must contain at least one public field");
+
+                argnames = new String[fields.length];
+                argtypes = new Class[fields.length];
+
+                for (int f = 0; f < fields.length; ++f)
+                {
+                    argnames[f] = ConfigurationBuffer.c2h(fields[f].getName());
+                    argtypes[f] = fields[f].getType();
+                }
+                return;
+            }
+        }
+
+        assert ((argcount == NOT_SET) || (argcount == pt.length - 1)) : ("coding error: the argument count must match the number of setter arguments");
+        // We've taken care of lists and value objects, from here on out, it must match the parameter list.
+
+        argcount = pt.length - 1;
+
+        DefaultArgumentValue defaultArgValuesAnno = setter.getAnnotation(DefaultArgumentValue.class);
+        if (defaultArgValuesAnno != null)
+            defaultArgValues = defaultArgValuesAnno.value();
+
+        argtypes = new Class[pt.length - 1];
+        for (int i = 1; i < pt.length; ++i)
+        {
+            assert (ConfigurationBuffer.isSupportedSimpleType(pt[i])) : ("coding error: " + setter.getClass().getName() + "." + setter.getName() + " parameter " + i + " is not a supported type!");
+            argtypes[i - 1] = pt[i];
+        }
+    }
+
+    protected final Method getSetterMethod()
+    {
+        return setter;
+    }
+
+    private Method setter;
+    private Method getter;
+
+    protected final void setGetterMethod(Method getter)
+    {
+        this.getter = getter;
+    }
+
+    protected final Method getGetterMethod()
+    {
+        return getter;
+    }
+
+    @Override
+    public String toString()
+    {
+        return MoreObjects.toStringHelper("")
+                .add("alias", arrayAsString(getAliases()))
+                .add("argcount", getArgCount())
+                .add("argnames", arrayAsString(argnames))
+                .add("argtypes", arrayAsString(argtypes))
+                .add("deprecated", isDeprecated())
+                .add("deprecatedMessage", getDeprecatedMessage())
+                .add("deprecatedReplacement", getDeprecatedReplacement())
+                .add("deprecatedSince", getDeprecatedSince())
+                .add("getter", getGetterMethod() == null ? "null" : getGetterMethod().getName())
+                .add("setter", getSetterMethod() == null ? "null" : getSetterMethod().getName())
+                .add("required", isRequired())
+                .add("Prerequisites", arrayAsString(getPrerequisites()))
+                .add("softPrerequisites", arrayAsString(getSoftPrerequisites()))
+                .add("advanced", isAdvanced())
+                .add("allow multiple", allowMultiple())
+                //.add("doChecksum", doChecksum())
+                .add("displayed", isDisplayed())
+                .add("greedy", isGreedy())
+                .add("hidden", isHidden())
+                .add("removed", isRemoved())
+                .add("path", isPath())
+                .toString();
+    }
+
+    private String arrayAsString(Object[] array)
+    {
+        if (array == null)
+            return "";
+        else
+            return "[" + Joiner.on(",").join(array) + "]";
+    }
+
+    /**
+     * True if only {@code compc} client can use this option.
+     */
+    public boolean isCompcOnly = false;
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/config/ConfigurationValue.java b/linter/src/main/java/org/apache/royale/linter/config/ConfigurationValue.java
new file mode 100644
index 0000000..5eed4f5
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/config/ConfigurationValue.java
@@ -0,0 +1,109 @@
+/*
+ *
+ *  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.royale.linter.config;
+
+import java.util.List;
+import java.util.LinkedList;
+
+
+/**
+ * This class represents an instance of a configuration option.  For
+ * example, "-debug=true".
+ */
+public class ConfigurationValue
+{
+    protected ConfigurationValue( ConfigurationBuffer buffer, String var, List<String> args, String source, int line, String context )
+    {
+        this.buffer = buffer;
+        this.var = var;
+        this.args = new LinkedList<String>( args );
+        this.source = source;
+        this.line = line;
+        this.context = context;
+    }
+
+    /**
+     * getArgs
+     *
+     * @return list of values provided, in schema order
+     */
+    public final List<String> getArgs()
+    {
+        return args;
+    }
+
+    /**
+     * getBuffer
+     *
+     * @return a handle to the associated buffer holding this value
+     */
+    public final ConfigurationBuffer getBuffer()
+    {
+        return buffer;
+    }
+
+    /**
+     * getSource
+     *
+     * @return a string representing the origin of this value, or null if unknown
+     */
+    public final String getSource()
+    {
+        return source;
+    }
+
+    /**
+     * getLine
+     *
+     * @return the line number of the origin of this value, or -1 if unknown
+     */
+    public final int getLine()
+    {
+        return line;
+    }
+
+    /**
+     * getVar
+     *
+     * @return the full name of this configuration variable in the hierarchy
+     */
+    public final String getVar()
+    {
+        return var;
+    }
+
+    /**
+     * getContext
+     *
+     * @return the path of the enclosing context where the variable was set
+     * (i.e. the directory where the config file was found)
+     */
+    public final String getContext()
+    {
+        return context;
+    }
+
+    private final ConfigurationBuffer buffer;
+    private final String var;
+    private final List<String> args;
+    private final String source;
+    private final int line;
+    private final String context;
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/config/Configurator.java b/linter/src/main/java/org/apache/royale/linter/config/Configurator.java
new file mode 100644
index 0000000..7af4f1a
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/config/Configurator.java
@@ -0,0 +1,683 @@
+/*
+ *
+ *  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.royale.linter.config;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.royale.compiler.exceptions.ConfigurationException;
+import org.apache.royale.compiler.internal.config.localization.LocalizationManager;
+import org.apache.royale.compiler.internal.config.localization.ResourceBundleLocalizer;
+import org.apache.royale.compiler.problems.ConfigurationProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.utils.FilenameNormalization;
+import org.apache.royale.utils.Trace;
+
+/**
+ * A class that allows a client change compiler settings and to 
+ * configure projects and targets from those settings.
+ */
+public class Configurator implements Cloneable
+{
+    /**
+     * Convert file path strings to {@code File} objects. Null values are
+     * discarded.
+     * 
+     * @param paths List of file paths
+     * @return List of File objects. No null values will be returned.
+     */
+    public static List<File> toFiles(final List<String> paths)
+    {
+        final List<File> result = new ArrayList<File>();
+        for (final String path : paths)
+        {
+            if (path != null)
+                result.add(new File(path));
+        }
+        return result;
+    }
+
+    /**
+     * Convert file path strings to {@code File} objects. Null values are
+     * discarded.
+     * 
+     * @param paths List of file paths
+     * @return Array of File objects. No null values will be returned.
+     */
+    public static List<File> toFileList(final List<String> paths)
+    {
+        final List<File> result = new ArrayList<File>();
+        for (final String path : paths)
+        {
+            if (path != null)
+                result.add(FilenameNormalization.normalize(new File(path)));
+        }
+        return result;
+    }
+
+    /**
+     * Convert {@code File} objects to {@code String}, where each {@code String} is 
+     * the absolute file path of the file. Null values are discarded.
+     * 
+     * @param files file specifications
+     * @return Array of File objects. No null values will be returned.
+     */
+    public static String[] toPaths(File[] files)
+    {
+        final List<String> result = new ArrayList<String>();
+        for (final File file : files)
+        {
+            if (file != null)
+                result.add(file.getAbsolutePath());
+        }
+        return result.toArray(new String[0]);
+    }
+
+    // Used to generate the command line
+    private static final String EQUALS_STRING = "=";
+    private static final String PLUS_EQUALS_STRING = "+=";
+    private static final String COMMA_STRING =  ",";
+    private static final String PLUS_STRING =  "+";
+    /**
+     * Constructor
+     */
+    public Configurator()
+    {
+        this(Configuration.class);
+    }
+    
+    /**
+     * Constructor
+     */
+    public Configurator(Class<? extends Configuration> configurationClass)
+    {
+        this.configurationClass = configurationClass;
+        args = new LinkedHashMap<String, Object>();
+        more = new LinkedHashMap<String, Object>();
+        tokens = new TreeMap<String, String>();
+        
+        isConfigurationDirty = true;
+        configurationDefaultVariable = ILinterSettingsConstants.FILES; // the default variable of the configuration.
+        configurationProblems = new ArrayList<ICompilerProblem>();
+
+        // initialize the localization manager.
+        LocalizationManager.get().addLocalizer(new ResourceBundleLocalizer());
+    }
+
+    private ConfigurationBuffer cfgbuf;
+    protected Configuration configuration;
+    private Class<? extends Configuration> configurationClass;
+    
+    private Map<String, Object> args, more;
+    private String[] extras;
+    private String configurationDefaultVariable;
+    
+    private Map<String, String> tokens;
+    
+    private boolean isConfigurationDirty;
+    private boolean configurationSuccess;
+    protected Collection<ICompilerProblem> configurationProblems;
+
+    // 
+    // IConfigurator related methods
+    //
+
+    public Collection<ICompilerProblem> validateConfiguration(String[] args)
+    {
+        if (args == null)
+            throw new NullPointerException("args may not be null");
+        
+        List<ICompilerProblem> problems = new ArrayList<ICompilerProblem>();
+        ConfigurationBuffer configurationBuffer = createConfigurationBuffer(configurationClass);
+        
+        try
+        {
+            CommandLineConfigurator.parse(configurationBuffer, null, args);
+        }
+        catch (ConfigurationException e)
+        {
+            final ICompilerProblem problem = new ConfigurationProblem(e);
+            problems.add(problem);
+        }
+        
+        return problems;
+    }
+    
+    public Collection<ICompilerProblem> getConfigurationProblems()
+    {
+        assert configuration != null : 
+            "Get the configuration problems after calling getConfiguration()";
+        
+        List<ICompilerProblem> problems = new ArrayList<ICompilerProblem>(configurationProblems.size() +
+                                            configuration.getConfigurationProblems().size());
+        problems.addAll(configurationProblems);
+        problems.addAll(configuration.getConfigurationProblems()); 
+        return problems;
+    }
+
+    public Configuration getConfiguration()
+    {
+        processConfiguration();
+        return configuration;
+    }
+    
+    public ConfigurationBuffer getConfigurationBuffer()
+    {
+        return cfgbuf;
+    }
+
+    /**
+     * Create a new configuration instance. The Configurator will need to 
+     * create a new configuration for each new configuration. For example,
+     * creating a new Configurator and getting the target settings will create
+     * a new configuration. If later on, the configuration is modified by calling
+     * any of the setter methods on the Configurator, then a new configuration 
+     * will be created the next time applyToProject() or getTargetSettings() is called.
+     * 
+     * The method may be overriden to allow for greater control when creating a 
+     * custom configuration that extends the built-in configuration.
+     * 
+     * @return a new configuration instance. If the custom configuration class
+     * cannot be created, the default configuration class will be created instead.
+     */
+    protected Configuration createConfiguration()
+    {
+        try
+        {
+            return configurationClass.newInstance();
+        }
+        catch (Exception e)
+        {
+            // If there is a problem initializing the configuration, then
+            // throw a ConfigurationException.
+            reportConfigurationException(new ConfigurationException.CouldNotInstantiate(configurationClass.getName()));
+
+            // Create the default configuration so we can report configuration
+            // problems.
+            try
+            {
+                return Configuration.class.newInstance();                
+            }
+            catch (Exception e2)
+            {
+                // this should never fail
+                assert(false);
+                return null;
+            }
+        }
+    }
+    
+    /**
+     * Initialize the configuration and the configuration buffer.
+     */
+    protected void initializeConfiguration()
+    {
+        // Create a clean configuration and configuration buffer
+        configuration = createConfiguration();
+        cfgbuf = createConfigurationBuffer(configuration.getClass());
+    }
+
+    /**
+     * Create a configuration buffer.
+     * @param configurationClass    The Configuration object
+     * @return          the configuration buffer to use
+     */
+    protected ConfigurationBuffer createConfigurationBuffer(
+            Class<? extends Configuration> configurationClass)
+    {
+        return new ConfigurationBuffer(
+                configurationClass, Configuration.getAliases());
+    }
+
+    /**
+     * Wrapper around the real processConfiguration.
+     * 
+     * @return true if success, false otherwise.
+     */
+    protected boolean processConfiguration()
+    {
+        boolean success = true;
+        
+        if (isConfigurationDirty)
+        {
+            configurationProblems.clear();
+            
+            try
+            {
+                success = processConfiguration(getOptions(args, more, processExtras(extras)));
+            }
+            catch (ConfigurationException e)
+            {
+                reportConfigurationException(e);
+                success = false;
+            }
+            catch (Exception e)
+            {
+            	e.printStackTrace();
+            }
+        }
+        else
+        {
+            success = configurationSuccess;
+        }
+        
+        isConfigurationDirty = false;
+        configurationSuccess = success;
+        return success;
+    }
+
+    /**
+     * Does all the work to set the command line arguments info the 
+     * configuration object.
+     *  
+     * @param argsArray - command line arguments
+     * 
+     * @return true if successful, false otherwise.
+     */
+    protected boolean  processConfiguration(String[] argsArray)
+    {
+        initializeConfiguration();
+        
+        boolean success = true;
+
+        try
+        {   
+            SystemPropertyConfigurator.load(cfgbuf, "royale");
+
+            // Parse the command line a first time, to peak at stuff like
+            // "royalelib" and "load-config".  The first parse is thrown
+            // away after that and we intentionally parse a second time
+            // below.  See note below.
+            CommandLineConfigurator.parse(cfgbuf, configurationDefaultVariable, argsArray);
+
+            overrideDefaults();
+
+            // Return if "-version" is present so the command line can print the 
+            // version.
+            if (cfgbuf.getVar("version") != null)
+                return false;
+
+            // Return so the command line can print help if "-help" is present.
+            final List<ConfigurationValue> helpVar = cfgbuf.getVar("help");
+            if (helpVar != null)
+                return false;
+            
+            // The command line needs to take precedence over all defaults and config files.
+            // By simply re-merging the command line back on top,
+            // we will get the behavior we want.
+            cfgbuf.clearSourceVars(CommandLineConfigurator.source);
+            CommandLineConfigurator.parse(cfgbuf, configurationDefaultVariable, argsArray);
+
+            // commit() reports problems instead of throwing an exception. This 
+            // allows us to process all the options in a configuration that
+            // are correct in the hopes that it will be enough to configure a
+            // project.
+            if (!cfgbuf.commit(configuration, configurationProblems))
+                success = false;
+            
+            configuration.validate(cfgbuf);
+        }
+        catch (ConfigurationException e)
+        {
+            reportConfigurationException(e);
+            success = false;
+        }
+        
+        return success;
+    }
+
+    /**
+     * Override default values.
+     */
+    protected void overrideDefaults() throws ConfigurationException
+    {
+    }
+
+    /**
+     * Convert conifguration exceptions to problems and collect them for 
+     * reporting.
+     * 
+     * @param e
+     */
+    protected void reportConfigurationException(ConfigurationException e)
+    {
+        final ICompilerProblem problem = new ConfigurationProblem(e);
+        configurationProblems.add(problem);
+    }
+    
+    //
+    // Configuration related methods
+    //
+    protected String[] getOptions(Map<String, Object> args, Map<String, Object> more, 
+		String[] extras)
+    {
+        ArrayList<String> buffer = new ArrayList<String>();
+        
+        for (Map.Entry<String, String> tokenEntry : tokens.entrySet())
+        {
+            buffer.add(PLUS_STRING + tokenEntry.getKey() + EQUALS_STRING + tokenEntry.getValue());
+        }
+        
+        for (Map.Entry<String, Object> arg : args.entrySet())
+        {
+            String key = arg.getKey();
+            Object value = arg.getValue();
+
+            if (value instanceof Boolean)
+            {
+                buffer.add(key + EQUALS_STRING + value);
+            }
+            else if (value instanceof Number)
+            {
+                buffer.add(key);
+                buffer.add(value.toString());
+            }
+            else if (value instanceof String)
+            {               
+                if (!"".equals(value))
+                {
+                    buffer.add(key);
+                    buffer.add((String)value);
+                }
+                else
+                {
+                    buffer.add(key + EQUALS_STRING);
+                }
+            }
+            else if (value instanceof File)
+            {
+                String p = ((File) value).getPath();
+                if (!"".equals(p))
+                {
+                    buffer.add(key);
+                    buffer.add(p);
+                }
+                else
+                {
+                    buffer.add(key + EQUALS_STRING);
+                }
+            }
+            else if (value instanceof java.util.Date)
+            {
+                buffer.add(key);
+                buffer.add(value.toString());
+            }
+            else if (value instanceof Map)
+            {
+                @SuppressWarnings("unchecked")
+                Map<String, ?> m = (Map<String, ?>) value;
+                for (Map.Entry<String, ?>entry : m.entrySet())
+                {
+                    String k = entry.getKey();
+                    Object v = entry.getValue();
+                    
+                    if (v instanceof String)
+                    {
+                        buffer.add(key);
+                        buffer.add(k);
+                        buffer.add((String)v);
+                    }
+                    else if (v instanceof File)
+                    {
+                        buffer.add(key);
+                        buffer.add(k);
+                        buffer.add(((File) v).getPath());
+                    }
+                    else if (v instanceof Collection)
+                    {
+                        buffer.add(key);
+                        buffer.add(k);
+                        Collection<?> list = (Collection<?>)v;
+                        for (Object next : list)
+                        {
+                            if (next != null)
+                                buffer.add(next.toString());
+                        }
+                    }
+                    else if (v != null)
+                    {
+                        assert false;
+                    }
+                }
+            }
+            else if (value instanceof int[])
+            {
+                int[] a = (int[]) value;
+                buffer.add(key);
+                buffer.add(String.valueOf(a[0]));
+                buffer.add(String.valueOf(a[1]));
+            }
+            else if (value instanceof Collection)
+            {
+                Collection<Object> list = new LinkedList<Object>((Collection<?>)args.get(key));
+                
+                int length = list.size();
+                if (length > 0)
+                {
+                    buffer.add(key);
+                }
+                for (Object obj : list)
+                {
+                    if (obj instanceof String)
+                    {
+                        buffer.add((String)obj);
+                    }
+                    else if (obj instanceof File)
+                    {
+                        buffer.add(((File)obj).getPath());
+                    }
+                }
+            }
+            else if (value != null)
+            {
+                assert false;
+            }
+            else
+            {
+                // System.err.println("unprocessed compiler options: " + key + EQUALS_STRING + value);
+            }
+        }
+        
+        for (Map.Entry<String, Object> moreEntry : more.entrySet())
+        {
+            String key = moreEntry.getKey();
+            Object value = moreEntry.getValue();
+
+            if (value instanceof Collection)
+            {
+                buffer.add(key + PLUS_EQUALS_STRING + toCommaSeparatedString((Collection<?>)value));
+            }
+            else if (value instanceof Map)
+            {
+                @SuppressWarnings("unchecked")
+                Map<String, ?> m = (Map<String, ?>) value;
+                for (Map.Entry<String, ?>entry : m.entrySet())
+                {
+                    String k = entry.getKey();
+                    Object v = entry.getValue();
+                    
+                    if (v instanceof Collection)
+                    {
+                        buffer.add(key + PLUS_EQUALS_STRING + k + COMMA_STRING +
+                                toCommaSeparatedString((Collection<?>)v));
+                    }
+                    else if (v != null)
+                    {
+                        assert false;
+                    }
+                }
+            }
+            else if (value != null)
+            {
+                assert false;
+            }
+            else
+            {
+                // System.err.println("unprocessed compiler options: " + key + EQUALS_STRING + value);
+            }
+        }
+
+        // Append extra command line args to the buffer.
+        if (extras != null && extras.length > 0)
+        {
+            for (int i = 0, length = extras == null ? 0 : extras.length; i < length; i++)
+            {
+                if (extras[i] != null)
+                {
+                    buffer.add(extras[i]);
+                }
+            }            
+        }
+        
+        String[] options = new String[buffer.size()];
+        buffer.toArray(options);
+    
+        if (Trace.config)
+            Trace.trace("Configurator: options = " + buffer.toString());
+        
+        return options;
+    }
+
+	protected String[] processExtras(String[] extraOptions) throws ConfigurationException
+    {
+		return extraOptions;
+	}
+
+    /**
+     * Sets the configuration parameters. The input should be valid <code>mxmlc/compc</code> command-line arguments.<p>
+     * 
+     * @param args <code>mxmlc/compc</code> command-line arguments
+     * @param defaultVariable the default variable of the configuration.
+     */
+    public void setConfiguration(String[] args, String defaultVariable)
+    {
+        extras = args;
+        configurationDefaultVariable = defaultVariable;
+        isConfigurationDirty = true;
+    }
+    
+    /**
+     * Defines a token. mxmlc and compc support token substitutions. For example,
+     * 
+     * <pre>
+     * mxmlc +royalelib=path1 +foo=bar --var=${foo}
+     * </pre>
+     * 
+     * <code>var=bar</code> after the substitution of <code>${foo}</code>.
+     * 
+     * @param name The name of the token.
+     * @param value The value of the token.
+     */
+    public void setToken(String name, String value)
+    {
+        tokens.put(name, value);
+        
+        isConfigurationDirty = true;
+    }
+
+    /**
+     * 
+     */
+    @Override
+    public String toString()
+    {
+        String[] options;
+        try
+        {
+            options = getOptions(args, more, processExtras(extras));
+        }
+        catch (ConfigurationException e)
+        {
+            options = new String[0];
+        }
+        
+        StringBuilder b = new StringBuilder();
+        for (int i = 0; i < options.length; i++)
+        {
+            b.append(options[i]);
+            b.append(' ');
+        }
+        return b.toString();
+    }
+    
+    private String toCommaSeparatedString(Collection<?> values)
+    {
+        StringBuilder b = new StringBuilder();
+        int length = values.size();
+        int i = 0;
+        for (Object value : values)
+        {
+            String valueString = null;
+            
+            if (value instanceof String)
+            {
+                valueString = (String)value;
+            }
+            else if (value instanceof File)
+            {
+                valueString = ((File)value).getPath();
+            }
+            
+            if (valueString != null)
+                b.append(valueString);
+            
+            if (i++ < length - 1)
+            {
+                b.append(COMMA_STRING);
+            }
+        }
+        return b.toString();
+    }
+    
+    @Override
+    public Configurator clone()
+    {
+        Configurator cloneConfig;
+        
+        try
+        {
+            cloneConfig = (Configurator) super.clone();
+        }
+        catch ( CloneNotSupportedException e )
+        {
+            throw new RuntimeException(e);//wont happen
+        }
+
+        cloneConfig.args = new LinkedHashMap<String, Object>(args);
+        cloneConfig.more = new LinkedHashMap<String, Object>(more);
+        cloneConfig.tokens = new LinkedHashMap<String, String>(tokens);
+        cloneConfig.configurationClass = configurationClass;
+        cloneConfig.isConfigurationDirty = true;
+        
+        if (extras != null)
+        {
+            cloneConfig.extras = new String[extras.length];
+            System.arraycopy(extras, 0, cloneConfig.extras, 0, extras.length);
+        }
+
+        return cloneConfig;
+    }
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/config/ILinterSettingsConstants.java b/linter/src/main/java/org/apache/royale/linter/config/ILinterSettingsConstants.java
new file mode 100644
index 0000000..9fb238f
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/config/ILinterSettingsConstants.java
@@ -0,0 +1,24 @@
+/*
+ *
+ *  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.royale.linter.config;
+
+public interface ILinterSettingsConstants {
+    static final String FILES                                       = "files";
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/config/LineCommentPosition.java b/linter/src/main/java/org/apache/royale/linter/config/LineCommentPosition.java
new file mode 100644
index 0000000..26b90f2
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/config/LineCommentPosition.java
@@ -0,0 +1,36 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.config;
+
+public enum LineCommentPosition {
+	ABOVE("above"),
+	BESIDE("beside");
+
+	private LineCommentPosition(String position) {
+		this.position = position;
+	}
+
+	private String position;
+
+	public String getPosition() {
+		return position;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/config/SystemPropertyConfigurator.java b/linter/src/main/java/org/apache/royale/linter/config/SystemPropertyConfigurator.java
new file mode 100644
index 0000000..f1698da
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/config/SystemPropertyConfigurator.java
@@ -0,0 +1,82 @@
+/*
+ *
+ *  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.royale.linter.config;
+
+import java.util.Properties;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.StringTokenizer;
+
+import org.apache.royale.compiler.exceptions.ConfigurationException;
+
+/**
+ * A utility class, which is used to load configuration options via
+ * system properties and populate a ConfigurationBuffer.  A
+ * counterpart of CommandLineConfigurator and FileConfigurator.
+ */
+public class SystemPropertyConfigurator
+{
+    /**
+     * Opportunistically find some configuration settings in system properties.
+     * @param buffer the intermediate config buffer
+     * @param prefix an optional prefix to add to the variable, pass null if no prefix
+     */
+    public static void load( final ConfigurationBuffer buffer, String prefix ) throws ConfigurationException
+    {
+        try
+        {
+            Properties props = System.getProperties();
+
+            for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();)
+            {
+                String propname = (String) e.nextElement();
+
+                if (!propname.startsWith( prefix + "."))
+                {
+                    String value = System.getProperty( propname );
+                    buffer.setToken( propname, value );
+                    continue;
+                }
+
+                String varname = propname.substring( prefix.length() + 1 );
+
+                if (!buffer.isValidVar( varname ))
+                    continue;
+
+                String value = System.getProperty( propname );
+
+                List<String> args = new LinkedList<String>();
+                StringTokenizer t = new StringTokenizer( value, "," );
+
+                while (t.hasMoreTokens())
+                {
+                    String token = t.nextToken();
+                    args.add( token );
+                }
+                buffer.setVar( varname, args, "system properties", -1 );
+            }
+        }
+        catch (SecurityException se)
+        {
+            // just ignore, this is an optional for loading configuration   
+        }
+    }
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/internal/BaseLinter.java b/linter/src/main/java/org/apache/royale/linter/internal/BaseLinter.java
new file mode 100644
index 0000000..d825e53
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/internal/BaseLinter.java
@@ -0,0 +1,46 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.internal;
+
+import java.util.Collection;
+
+import org.apache.royale.compiler.clients.problems.CompilerProblemCategorizer;
+import org.apache.royale.compiler.problems.CompilerProblemSeverity;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.linter.LinterSettings;
+
+public class BaseLinter {
+	protected LinterSettings settings;
+
+	protected BaseLinter(LinterSettings settings) {
+		this.settings = settings;
+	}
+
+	protected boolean hasErrors(Collection<ICompilerProblem> problems) {
+		CompilerProblemCategorizer categorizer = new CompilerProblemCategorizer(null);
+		for (ICompilerProblem problem : problems) {
+			CompilerProblemSeverity severity = categorizer.getProblemSeverity(problem);
+			if (CompilerProblemSeverity.ERROR.equals(severity)) {
+				return true;
+			}
+		}
+		return false;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/AnyTypeRule.java b/linter/src/main/java/org/apache/royale/linter/rules/AnyTypeRule.java
new file mode 100644
index 0000000..3215ec9
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/AnyTypeRule.java
@@ -0,0 +1,127 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.constants.IASLanguageConstants;
+import org.apache.royale.compiler.internal.tree.as.IdentifierNode;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IExpressionNode;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.compiler.tree.as.IIdentifierNode;
+import org.apache.royale.compiler.tree.as.IParameterNode;
+import org.apache.royale.compiler.tree.as.IVariableNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Checks for uses of the * type.
+ */
+public class AnyTypeRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.VariableID, (node, tokenQuery, problems) -> {
+			checkVariableNode((IVariableNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.FunctionID, (node, tokenQuery, problems) -> {
+			checkFunctionNode((IFunctionNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkVariableNode(IVariableNode variableNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IExpressionNode typeNode = variableNode.getVariableTypeNode();
+		if (!isAnyType(typeNode)) {
+			return;
+		}
+		problems.add(new AnyTypeOnVariableLinterProblem(variableNode));
+	}
+
+	private void checkFunctionNode(IFunctionNode functionNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		for (IParameterNode paramNode : functionNode.getParameterNodes()) {
+			IExpressionNode typeNode = paramNode.getVariableTypeNode();
+			if (isAnyType(typeNode)) {
+				problems.add(new AnyTypeOnParameterLinterProblem(paramNode));
+			}
+		}
+		if (functionNode.isConstructor()) {
+			return;
+		}
+		IExpressionNode typeNode = functionNode.getReturnTypeNode();
+		if (isAnyType(typeNode)) {
+			problems.add(new AnyTypeReturnLinterProblem(functionNode));
+		}
+	}
+
+	private boolean isAnyType(IExpressionNode typeNode) {
+		if (!(typeNode instanceof IIdentifierNode)) {
+			return false;
+		}
+		// isImplicit() is not on the interface, for some reason
+		if (typeNode instanceof IdentifierNode && ((IdentifierNode) typeNode).isImplicit()) {
+			return false;
+		}
+		IIdentifierNode identifierNode = (IIdentifierNode) typeNode;
+		if (!IASLanguageConstants.ANY_TYPE.equals(identifierNode.getName())) {
+			return false;
+		}
+		return true;
+	}
+
+	public static class AnyTypeOnVariableLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Must not use the * type for variable '${varName}'";
+
+		public AnyTypeOnVariableLinterProblem(IVariableNode node)
+		{
+			super(node.getVariableTypeNode());
+			varName = node.getName();
+		}
+
+		public String varName;
+	}
+
+	public static class AnyTypeOnParameterLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Must not use the * type for function parameter '${paramName}'";
+
+		public AnyTypeOnParameterLinterProblem(IParameterNode node)
+		{
+			super(node.getVariableTypeNode());
+			paramName = node.getName();
+		}
+
+		public String paramName;
+	}
+
+	public static class AnyTypeReturnLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Must not use the * type for function return type";
+
+		public AnyTypeReturnLinterProblem(IFunctionNode node)
+		{
+			super(node.getReturnTypeNode());
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/BooleanEqualityRule.java b/linter/src/main/java/org/apache/royale/linter/rules/BooleanEqualityRule.java
new file mode 100644
index 0000000..0aba1e4
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/BooleanEqualityRule.java
@@ -0,0 +1,75 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IBinaryOperatorNode;
+import org.apache.royale.compiler.tree.as.IExpressionNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Checks for redundant equality comparisons with 'true' and 'false' boolean
+ * literals using the '==' and '!=' operators.
+ * 
+ * Does not check for strict equality using the '===' and '!==' operators
+ * because these operators do not type coerce the two sides to determine if they
+ * are 'truthy' or 'falsy'.
+ */
+public class BooleanEqualityRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.Op_EqualID, (node, tokenQuery, problems) -> {
+			checkBinaryOperatorNode((IBinaryOperatorNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.Op_NotEqualID, (node, tokenQuery, problems) -> {
+			checkBinaryOperatorNode((IBinaryOperatorNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkBinaryOperatorNode(IBinaryOperatorNode operatorNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IExpressionNode leftOperandNode = operatorNode.getLeftOperandNode();
+		if (ASTNodeID.LiteralBooleanID.equals(leftOperandNode.getNodeID())) {
+			problems.add(new BooleanEqualityLinterProblem(leftOperandNode));
+		}
+		IExpressionNode rightOperandNode = operatorNode.getRightOperandNode();
+		if (ASTNodeID.LiteralBooleanID.equals(rightOperandNode.getNodeID())) {
+			problems.add(new BooleanEqualityLinterProblem(rightOperandNode));
+		}
+	}
+
+	public static class BooleanEqualityLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Must simplify statement to remove redundant comparison with true or false";
+
+		public BooleanEqualityLinterProblem(IExpressionNode node)
+		{
+			super(node);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/ClassNameRule.java b/linter/src/main/java/org/apache/royale/linter/rules/ClassNameRule.java
new file mode 100644
index 0000000..462f28a
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/ClassNameRule.java
@@ -0,0 +1,77 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IClassNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that class names match a specific pattern.
+ */
+public class ClassNameRule extends LinterRule {
+	public static final Pattern DEFAULT_NAME_PATTERN = Pattern.compile("^[A-Z][a-zA-Z0-9]*$");
+
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.ClassID, (node, tokenQuery, problems) -> {
+			checkClassNode((IClassNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	public Pattern pattern;
+
+	private void checkClassNode(IClassNode classNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		String className = classNode.getName();
+		Pattern thePattern = pattern;
+		if (thePattern == null) {
+			thePattern = DEFAULT_NAME_PATTERN;
+		}
+		Matcher matcher = thePattern.matcher(className);
+		if (matcher.matches()) {
+			return;
+		}
+		problems.add(new ClassNameLinterProblem(classNode, thePattern));
+	}
+
+	public static class ClassNameLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Class name does not match the pattern '${pattern}'";
+
+		public ClassNameLinterProblem(IClassNode node, Pattern pattern)
+		{
+			super(node.getNameExpressionNode());
+			this.pattern = pattern.toString();
+		}
+
+		public String pattern;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/ConstantNameRule.java b/linter/src/main/java/org/apache/royale/linter/rules/ConstantNameRule.java
new file mode 100644
index 0000000..16f8b1c
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/ConstantNameRule.java
@@ -0,0 +1,80 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IVariableNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that constant names match a specific pattern.
+ */
+public class ConstantNameRule extends LinterRule {
+	public static final Pattern DEFAULT_NAME_PATTERN = Pattern.compile("^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$");
+
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.VariableID, (node, tokenQuery, problems) -> {
+			checkVariableNode((IVariableNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	public Pattern pattern;
+
+	private void checkVariableNode(IVariableNode variableNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (!variableNode.isConst()) {
+			return;
+		}
+		String constantName = variableNode.getName();
+		Pattern thePattern = pattern;
+		if (thePattern == null) {
+			thePattern = DEFAULT_NAME_PATTERN;
+		}
+		Matcher matcher = thePattern.matcher(constantName);
+		if (matcher.matches()) {
+			return;
+		}
+		problems.add(new ConstantNameLinterProblem(variableNode, thePattern));
+	}
+
+	public static class ConstantNameLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Constant name does not match the pattern '${pattern}'";
+
+		public ConstantNameLinterProblem(IVariableNode node, Pattern pattern)
+		{
+			super(node.getNameExpressionNode());
+			this.pattern = pattern.toString();
+		}
+
+		public String pattern;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/ConstructorDispatchEventRule.java b/linter/src/main/java/org/apache/royale/linter/rules/ConstructorDispatchEventRule.java
new file mode 100644
index 0000000..736ebb4
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/ConstructorDispatchEventRule.java
@@ -0,0 +1,97 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IExpressionNode;
+import org.apache.royale.compiler.tree.as.IFunctionCallNode;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.compiler.tree.as.IIdentifierNode;
+import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode;
+import org.apache.royale.compiler.tree.as.IMemberAccessExpressionNode;
+import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode.LanguageIdentifierKind;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that a constructor does not call `dispatchEvent` because it's likely
+ * that no listeners have been added yet.
+ */
+public class ConstructorDispatchEventRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.FunctionCallID, (node, tokenQuery, problems) -> {
+			checkFunctionCallNode((IFunctionCallNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkFunctionCallNode(IFunctionCallNode functionCallNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IFunctionNode functionNode = (IFunctionNode) functionCallNode.getAncestorOfType(IFunctionNode.class);
+		if (functionNode == null || !functionNode.isConstructor()) {
+			return;
+		}
+		IExpressionNode nameNode = functionCallNode.getNameNode();
+		if (nameNode instanceof IIdentifierNode) {
+			IIdentifierNode identifierNode = (IIdentifierNode) nameNode;
+			if ("dispatchEvent".equals(identifierNode.getName())) {
+				problems.add(new ConstructorDispatchEventLinterProblem(functionNode, identifierNode));
+				return;
+			}
+			return;
+		}
+		if (nameNode instanceof IMemberAccessExpressionNode) {
+			IMemberAccessExpressionNode memberAccess = (IMemberAccessExpressionNode) nameNode;
+			if (memberAccess.getRightOperandNode() instanceof IIdentifierNode) {
+				IIdentifierNode identifierNode = (IIdentifierNode) memberAccess.getRightOperandNode();
+				if ("dispatchEvent".equals(identifierNode.getName())) {
+					if (memberAccess.getLeftOperandNode() instanceof ILanguageIdentifierNode) {
+						ILanguageIdentifierNode langIdentifierNode = (ILanguageIdentifierNode) memberAccess.getLeftOperandNode();
+						if (LanguageIdentifierKind.THIS.equals(langIdentifierNode.getKind())
+								|| LanguageIdentifierKind.SUPER.equals(langIdentifierNode.getKind())) {
+							problems.add(new ConstructorDispatchEventLinterProblem(functionNode, identifierNode));
+							return;
+						}
+					}
+				}
+			}
+			return;
+		}
+	}
+
+	public static class ConstructorDispatchEventLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Constructor '${functionName}' must not call 'dispatchEvent'";
+
+		public ConstructorDispatchEventLinterProblem(IFunctionNode functionNode, IExpressionNode dispatchEventNode) {
+			super(dispatchEventNode);
+			functionName = functionNode.getName();
+		}
+
+		public String functionName;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/ConstructorReturnTypeRule.java b/linter/src/main/java/org/apache/royale/linter/rules/ConstructorReturnTypeRule.java
new file mode 100644
index 0000000..2dc8700
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/ConstructorReturnTypeRule.java
@@ -0,0 +1,72 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IExpressionNode;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that a constructor does not specify a return type (not even `void`).
+ */
+public class ConstructorReturnTypeRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.FunctionID, (node, tokenQuery, problems) -> {
+			checkFunctionNode((IFunctionNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkFunctionNode(IFunctionNode functionNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (!functionNode.isConstructor()) {
+			return;
+		}
+		IExpressionNode returnTypeNode = functionNode.getReturnTypeNode();
+		if (returnTypeNode == null) {
+			return;
+		}
+		problems.add(new ConstructorReturnTypeLinterProblem(functionNode));
+	}
+
+	public static class ConstructorReturnTypeLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Constructor '${functionName}' must not specify '${returnType}' return type";
+
+		public ConstructorReturnTypeLinterProblem(IFunctionNode node)
+		{
+			super(node.getNameExpressionNode());
+			functionName = node.getName();
+			returnType = node.getReturnType();
+		}
+
+		public String functionName;
+		public String returnType;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/DynamicClassRule.java b/linter/src/main/java/org/apache/royale/linter/rules/DynamicClassRule.java
new file mode 100644
index 0000000..6df5ee9
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/DynamicClassRule.java
@@ -0,0 +1,66 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.common.ASModifier;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IClassNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that symbols in package or class scopes have a namespace.
+ */
+public class DynamicClassRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.ClassID, (node, tokenQuery, problems) -> {
+			checkClassNode((IClassNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkClassNode(IClassNode classNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (!classNode.hasModifier(ASModifier.DYNAMIC)) {
+			return;
+		}
+		problems.add(new DynamicClassLinterProblem(classNode));
+	}
+
+	public static class DynamicClassLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Class '${className}' must not be dynamic";
+
+		public DynamicClassLinterProblem(IClassNode node)
+		{
+			super(node);
+			className = node.getName();
+		}
+
+		public String className;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/EmptyCommentRule.java b/linter/src/main/java/org/apache/royale/linter/rules/EmptyCommentRule.java
new file mode 100644
index 0000000..177c45b
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/EmptyCommentRule.java
@@ -0,0 +1,109 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.common.ISourceLocation;
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.parsing.IMXMLToken;
+import org.apache.royale.compiler.parsing.IMXMLToken.MXMLTokenKind;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.MXMLTokenQuery;
+import org.apache.royale.linter.MXMLTokenVisitor;
+import org.apache.royale.linter.TokenQuery;
+import org.apache.royale.linter.TokenVisitor;
+
+/**
+ * Checks for line or block comments that are empty.
+ */
+public class EmptyCommentRule extends LinterRule {
+	@Override
+	public Map<Integer, TokenVisitor> getTokenVisitors() {
+		Map<Integer, TokenVisitor> result = new HashMap<>();
+		result.put(ASTokenTypes.HIDDEN_TOKEN_SINGLE_LINE_COMMENT, (token, tokenQuery, problems) -> {
+			checkSingleLineComment(token, tokenQuery, problems);
+		});
+		result.put(ASTokenTypes.HIDDEN_TOKEN_MULTI_LINE_COMMENT, (token, tokenQuery, problems) -> {
+			checkMultiLineComment(token, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	@Override
+	public Map<MXMLTokenKind, MXMLTokenVisitor> getMXMLTokenVisitors() {
+		Map<MXMLTokenKind, MXMLTokenVisitor> result = new HashMap<>();
+		result.put(MXMLTokenKind.COMMENT, (token, tokenQuery, problems) -> {
+			checkMXMLComment(token, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkSingleLineComment(IASToken comment, TokenQuery tokenQuery,
+			Collection<ICompilerProblem> problems) {
+		String commentText = comment.getText();
+		commentText = commentText.substring(2).trim();
+		if (commentText.length() > 0) {
+			return;
+		}
+		problems.add(new EmptyCommentLinterProblem(comment));
+	}
+
+	private void checkMultiLineComment(IASToken comment, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		String commentText = comment.getText();
+		commentText = commentText.substring(2, commentText.length() - 2).trim();
+		if (commentText.length() > 0) {
+			return;
+		}
+		problems.add(new EmptyCommentLinterProblem(comment));
+	}
+
+	private void checkMXMLComment(IMXMLToken comment, MXMLTokenQuery tokenQuery,
+			Collection<ICompilerProblem> problems) {
+		String commentText = comment.getText();
+		boolean isASDoc = commentText.startsWith("<!---") && commentText.length() > 7;
+		if (isASDoc) {
+			// see ASDocRule
+			return;
+		}
+		commentText = commentText.substring(4, commentText.length() - 3).trim();
+		if (commentText.length() > 0) {
+			return;
+		}
+		problems.add(new EmptyCommentLinterProblem(comment));
+	}
+
+	public static class EmptyCommentLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Comment must not be empty";
+
+		public EmptyCommentLinterProblem(IASToken token) {
+			super(token);
+		}
+
+		public EmptyCommentLinterProblem(IMXMLToken token) {
+			super((ISourceLocation) token);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/EmptyFunctionBodyRule.java b/linter/src/main/java/org/apache/royale/linter/rules/EmptyFunctionBodyRule.java
new file mode 100644
index 0000000..bfa04a6
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/EmptyFunctionBodyRule.java
@@ -0,0 +1,92 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IBlockNode;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Checks for function bodies that are empty.
+ */
+public class EmptyFunctionBodyRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.FunctionID, (node, tokenQuery, problems) -> {
+			checkFunctionNode((IFunctionNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkFunctionNode(IFunctionNode functionNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (functionNode.isConstructor()) {
+			return;
+		}
+		IBlockNode blockNode = getBody(functionNode);
+		if (blockNode == null) {
+			return;
+		}
+		if (!isEmptyBlock(blockNode, tokenQuery)) {
+			return;
+		}
+		problems.add(new EmptyFunctionBodyLinterProblem(blockNode));
+	}
+
+	private IBlockNode getBody(IFunctionNode functionNode) {
+		if (functionNode.getChildCount() == 0) {
+			return null;
+		}
+		IASNode lastChild = functionNode.getChild(functionNode.getChildCount() - 1);
+		if (lastChild instanceof IBlockNode) {
+			return (IBlockNode) lastChild;
+		}
+		return null;
+	}
+
+	private boolean isEmptyBlock(IBlockNode blockNode, TokenQuery tokenQuery) {
+		if (blockNode.getChildCount() > 0) {
+			return false;
+		}
+		if (tokenQuery.getCommentsInside(blockNode).length > 0) {
+			return false;
+		}
+		return true;
+	}
+
+	public static class EmptyFunctionBodyLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Function body must not be empty";
+
+		public EmptyFunctionBodyLinterProblem(IBlockNode node)
+		{
+			super(node);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/EmptyNestedBlockRule.java b/linter/src/main/java/org/apache/royale/linter/rules/EmptyNestedBlockRule.java
new file mode 100644
index 0000000..87319ce
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/EmptyNestedBlockRule.java
@@ -0,0 +1,88 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IBlockNode;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.compiler.tree.as.IPackageNode;
+import org.apache.royale.compiler.tree.as.ITypeNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Checks for empty blocks, as long as they aren't the bodies of classes,
+ * interfaces, or packages.
+ */
+public class EmptyNestedBlockRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.BlockID, (node, tokenQuery, problems) -> {
+			checkBlockNode((IBlockNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkBlockNode(IBlockNode blockNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (!isNested(blockNode)) {
+			return;
+		}
+		if (!isEmptyBlock(blockNode, tokenQuery)) {
+			return;
+		}
+		problems.add(new EmptyNestedBlockLinterProblem(blockNode));
+	}
+
+	private boolean isNested(IBlockNode blockNode) {
+		IASNode parentNode = blockNode.getParent();
+		return parentNode != null
+			&& !(parentNode instanceof IPackageNode)
+			&& !(parentNode instanceof ITypeNode)
+			&& !(parentNode instanceof IFunctionNode);
+	}
+
+	private boolean isEmptyBlock(IBlockNode blockNode, TokenQuery tokenQuery) {
+		if (blockNode.getChildCount() > 0) {
+			return false;
+		}
+		if (tokenQuery.getCommentsInside(blockNode).length > 0) {
+			return false;
+		}
+		return true;
+	}
+
+	public static class EmptyNestedBlockLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Block must not be empty";
+
+		public EmptyNestedBlockLinterProblem(IBlockNode node)
+		{
+			super(node);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/EmptyStatementRule.java b/linter/src/main/java/org/apache/royale/linter/rules/EmptyStatementRule.java
new file mode 100644
index 0000000..ccfb3f8
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/EmptyStatementRule.java
@@ -0,0 +1,70 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.TokenQuery;
+import org.apache.royale.linter.TokenVisitor;
+
+/**
+ * Checks for empty statements. An empty statement consists of only a semicolon
+ * (;) character.
+ */
+public class EmptyStatementRule extends LinterRule {
+	@Override
+	public Map<Integer, TokenVisitor> getTokenVisitors() {
+		Map<Integer, TokenVisitor> result = new HashMap<>();
+		result.put(ASTokenTypes.TOKEN_SEMICOLON, (token, tokenQuery, problems) -> {
+			checkSemicolon(token, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkSemicolon(IASToken semicolon, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IASToken prevToken = tokenQuery.getTokenBefore(semicolon);
+		if (prevToken == null) {
+			return;
+		}
+		if (prevToken.getType() != ASTokenTypes.TOKEN_SEMICOLON) {
+			return;
+		}
+		if (prevToken.isImplicit()) {
+			return;
+		}
+		problems.add(new EmptyStatementLinterProblem(semicolon));
+	}
+
+	public static class EmptyStatementLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Statement must not be empty";
+
+		public EmptyStatementLinterProblem(IASToken token)
+		{
+			super(token);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/FieldNameRule.java b/linter/src/main/java/org/apache/royale/linter/rules/FieldNameRule.java
new file mode 100644
index 0000000..974613e
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/FieldNameRule.java
@@ -0,0 +1,93 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IScopedNode;
+import org.apache.royale.compiler.tree.as.ITypeNode;
+import org.apache.royale.compiler.tree.as.IVariableNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that field names match a specific pattern.
+ */
+public class FieldNameRule extends LinterRule {
+	public static final Pattern DEFAULT_NAME_PATTERN = Pattern.compile("^[_a-z][a-zA-Z0-9]*$");
+
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.VariableID, (node, tokenQuery, problems) -> {
+			checkVariableNode((IVariableNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	public Pattern pattern;
+
+	private void checkVariableNode(IVariableNode variableNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (variableNode.isConst()) {
+			return;
+		}
+		IScopedNode containingScope = variableNode.getContainingScope();
+		if (containingScope == null) {
+			return;
+		}
+		IASNode possibleType = containingScope.getParent();
+		if (!(possibleType instanceof ITypeNode)) {
+			return;
+		}
+		String variableName = variableNode.getName();
+		Pattern thePattern = pattern;
+		if (thePattern == null) {
+			thePattern = DEFAULT_NAME_PATTERN;
+		}
+		Matcher matcher = thePattern.matcher(variableName);
+		if (matcher.matches()) {
+			return;
+		}
+		problems.add(new FieldNameLinterProblem(variableNode, thePattern));
+	}
+
+	public static class FieldNameLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Field name '${varName}' does not match the pattern '${pattern}'";
+
+		public FieldNameLinterProblem(IVariableNode node, Pattern pattern)
+		{
+			super(node.getNameExpressionNode());
+			this.pattern = pattern.toString();
+			varName = node.getName();
+		}
+
+		public String pattern;
+		public String varName;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/FunctionNameRule.java b/linter/src/main/java/org/apache/royale/linter/rules/FunctionNameRule.java
new file mode 100644
index 0000000..9cb10db
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/FunctionNameRule.java
@@ -0,0 +1,86 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that function names match a specific pattern.
+ */
+public class FunctionNameRule extends LinterRule {
+	public static final Pattern DEFAULT_NAME_PATTERN = Pattern.compile("^[a-z][a-zA-Z0-9]*$");
+
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.FunctionID, (node, tokenQuery, problems) -> {
+			checkFunctionNode((IFunctionNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	public Pattern pattern;
+
+	private void checkFunctionNode(IFunctionNode functionNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (functionNode.isConstructor()) {
+			// checked by ClassNameRule
+			return;
+		}
+		String functionName = functionNode.getName();
+		if (functionName == null || functionName.length() == 0) {
+			return;
+		}
+		Pattern thePattern = pattern;
+		if (thePattern == null) {
+			thePattern = DEFAULT_NAME_PATTERN;
+		}
+		Matcher matcher = thePattern.matcher(functionName);
+		if (matcher.matches()) {
+			return;
+		}
+		problems.add(new FunctionNameLinterProblem(functionNode, thePattern));
+	}
+
+	public static class FunctionNameLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Function name '${functionName}' does not match the pattern '${pattern}'";
+
+		public FunctionNameLinterProblem(IFunctionNode node, Pattern pattern)
+		{
+			super(node.getNameExpressionNode());
+			this.pattern = pattern.toString();
+			functionName = node.getName();
+		}
+
+		public String pattern;
+		public String functionName;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/IfBooleanLiteralRule.java b/linter/src/main/java/org/apache/royale/linter/rules/IfBooleanLiteralRule.java
new file mode 100644
index 0000000..b569399
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/IfBooleanLiteralRule.java
@@ -0,0 +1,73 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IExpressionNode;
+import org.apache.royale.compiler.tree.as.IIfNode;
+import org.apache.royale.compiler.tree.as.ILiteralNode;
+import org.apache.royale.compiler.tree.as.ILiteralNode.LiteralType;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that a boolean literal value is not used as an 'if' condition.
+ */
+public class IfBooleanLiteralRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.IfStatementID, (node, tokenQuery, problems) -> {
+			checkIfNode((IIfNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkIfNode(IIfNode ifNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IExpressionNode conditionalNode = ifNode.getConditionalExpressionNode();
+		if (!(conditionalNode instanceof ILiteralNode)) {
+			return;
+		}
+		ILiteralNode literalNode = (ILiteralNode) conditionalNode;
+		if (!LiteralType.BOOLEAN.equals(literalNode.getLiteralType())) {
+			return;
+		}
+		problems.add(new IfBooleanLiteralLinterProblem(literalNode));
+	}
+
+	public static class IfBooleanLiteralLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Condition is always '${value}'";
+
+		public IfBooleanLiteralLinterProblem(ILiteralNode node)
+		{
+			super(node);
+			value = node.getValue();
+		}
+
+		public String value;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/InterfaceNameRule.java b/linter/src/main/java/org/apache/royale/linter/rules/InterfaceNameRule.java
new file mode 100644
index 0000000..23a4d27
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/InterfaceNameRule.java
@@ -0,0 +1,77 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IInterfaceNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that interface names match a specific pattern.
+ */
+public class InterfaceNameRule extends LinterRule {
+	public static final Pattern DEFAULT_NAME_PATTERN = Pattern.compile("^I[A-Z][a-zA-Z0-9]*$");
+
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.InterfaceID, (node, tokenQuery, problems) -> {
+			checkInterfaceNode((IInterfaceNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	public Pattern pattern;
+
+	private void checkInterfaceNode(IInterfaceNode interfaceNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		String interfaceName = interfaceNode.getName();
+		Pattern thePattern = pattern;
+		if (thePattern == null) {
+			thePattern = DEFAULT_NAME_PATTERN;
+		}
+		Matcher matcher = thePattern.matcher(interfaceName);
+		if (matcher.matches()) {
+			return;
+		}
+		problems.add(new InterfaceNameLinterProblem(interfaceNode, thePattern));
+	}
+
+	public static class InterfaceNameLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Interface name does not match the pattern '${pattern}'";
+
+		public InterfaceNameLinterProblem(IInterfaceNode node, Pattern pattern)
+		{
+			super(node.getNameExpressionNode());
+			this.pattern = pattern.toString();
+		}
+
+		public String pattern;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/LineCommentPositionRule.java b/linter/src/main/java/org/apache/royale/linter/rules/LineCommentPositionRule.java
new file mode 100644
index 0000000..ae3fc4b
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/LineCommentPositionRule.java
@@ -0,0 +1,79 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.TokenQuery;
+import org.apache.royale.linter.TokenVisitor;
+import org.apache.royale.linter.config.LineCommentPosition;
+
+/**
+ * Checks if line comments appear beside code on the same line, or on a
+ * separate line.
+ */
+public class LineCommentPositionRule extends LinterRule {
+	@Override
+	public Map<Integer, TokenVisitor> getTokenVisitors() {
+		Map<Integer, TokenVisitor> result = new HashMap<>();
+		result.put(ASTokenTypes.HIDDEN_TOKEN_SINGLE_LINE_COMMENT, (token, tokenQuery, problems) -> {
+			checkSingleLineComment(token, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	public LineCommentPosition position = LineCommentPosition.ABOVE;
+
+	private void checkSingleLineComment(IASToken comment, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IASToken prevToken = tokenQuery.getTokenBefore(comment);
+		if (prevToken == null) {
+			return;
+		}
+		if (LineCommentPosition.ABOVE.equals(position)) {
+			if (prevToken.getLine() == comment.getLine()) {
+				problems.add(new LineCommentPositionLinterProblem(comment, position));
+			}
+		}
+		else if (LineCommentPosition.BESIDE.equals(position)) {
+			if (prevToken.getLine() != comment.getLine()) {
+				problems.add(new LineCommentPositionLinterProblem(comment, position));
+			}
+		}
+	}
+
+	public static class LineCommentPositionLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Comment must be ${position} code";
+
+		public LineCommentPositionLinterProblem(IASToken token, LineCommentPosition position)
+		{
+			super(token);
+			this.position = position.getPosition();
+		}
+
+		public String position;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/LocalVarAndParameterNameRule.java b/linter/src/main/java/org/apache/royale/linter/rules/LocalVarAndParameterNameRule.java
new file mode 100644
index 0000000..6a1be14
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/LocalVarAndParameterNameRule.java
@@ -0,0 +1,125 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IPackageNode;
+import org.apache.royale.compiler.tree.as.IParameterNode;
+import org.apache.royale.compiler.tree.as.IScopedNode;
+import org.apache.royale.compiler.tree.as.ITypeNode;
+import org.apache.royale.compiler.tree.as.IVariableNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that local variable and parameter names match a specific pattern.
+ */
+public class LocalVarAndParameterNameRule extends LinterRule {
+	public static final Pattern DEFAULT_NAME_PATTERN = Pattern.compile("^[a-z][a-zA-Z0-9]*$");
+
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.VariableID, (node, tokenQuery, problems) -> {
+			checkVariableNode((IVariableNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.ArgumentID, (node, tokenQuery, problems) -> {
+			checkParameterNode((IParameterNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	public Pattern pattern;
+
+	private void checkVariableNode(IVariableNode variableNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (variableNode.isConst()) {
+			return;
+		}
+		IScopedNode containingScope = variableNode.getContainingScope();
+		if (containingScope == null) {
+			return;
+		}
+		IASNode possibleType = containingScope.getParent();
+		if (possibleType instanceof ITypeNode || possibleType instanceof IPackageNode) {
+			return;
+		}
+		String variableName = variableNode.getName();
+		Pattern thePattern = pattern;
+		if (thePattern == null) {
+			thePattern = DEFAULT_NAME_PATTERN;
+		}
+		Matcher matcher = thePattern.matcher(variableName);
+		if (matcher.matches()) {
+			return;
+		}
+		problems.add(new LocalVarNameLinterProblem(variableNode, thePattern));
+	}
+
+	private void checkParameterNode(IParameterNode paramNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		String variableName = paramNode.getName();
+		Pattern thePattern = pattern;
+		if (thePattern == null) {
+			thePattern = DEFAULT_NAME_PATTERN;
+		}
+		Matcher matcher = thePattern.matcher(variableName);
+		if (matcher.matches()) {
+			return;
+		}
+		problems.add(new ParameterNameLinterProblem(paramNode, thePattern));
+	}
+
+	public static class LocalVarNameLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Variable name '${varName}' does not match the pattern '${pattern}'";
+
+		public LocalVarNameLinterProblem(IVariableNode node, Pattern pattern)
+		{
+			super(node.getNameExpressionNode());
+			this.pattern = pattern.toString();
+			varName = node.getName();
+		}
+
+		public String pattern;
+		public String varName;
+	}
+
+	public static class ParameterNameLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Parameter name '${paramName}' does not match the pattern '${pattern}'";
+
+		public ParameterNameLinterProblem(IVariableNode node, Pattern pattern)
+		{
+			super(node.getNameExpressionNode());
+			this.pattern = pattern.toString();
+			paramName = node.getName();
+		}
+
+		public String pattern;
+		public String paramName;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/LocalVarShadowsFieldRule.java b/linter/src/main/java/org/apache/royale/linter/rules/LocalVarShadowsFieldRule.java
new file mode 100644
index 0000000..4fce9ce
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/LocalVarShadowsFieldRule.java
@@ -0,0 +1,88 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IClassNode;
+import org.apache.royale.compiler.tree.as.IPackageNode;
+import org.apache.royale.compiler.tree.as.IScopedNode;
+import org.apache.royale.compiler.tree.as.ITypeNode;
+import org.apache.royale.compiler.tree.as.IVariableNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+public class LocalVarShadowsFieldRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.VariableID, (node, tokenQuery, problems) -> {
+			checkVariableNode((IVariableNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkVariableNode(IVariableNode variableNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IScopedNode containingScope = variableNode.getContainingScope();
+		if (containingScope == null) {
+			return;
+		}
+		IASNode possibleType = containingScope.getParent();
+		if (possibleType instanceof ITypeNode || possibleType instanceof IPackageNode) {
+			return;
+		}
+		IClassNode containingClass = (IClassNode) variableNode.getAncestorOfType(IClassNode.class);
+		if (containingClass == null) {
+			return;
+		}
+		IScopedNode classScope = containingClass.getScopedNode();
+		for(int i = 0; i < classScope.getChildCount(); i++) {
+			IASNode child = classScope.getChild(i);
+			if (child instanceof IVariableNode) {
+				IVariableNode childVar = (IVariableNode) child;
+				if (variableNode.getName().equals(childVar.getName())) {
+					problems.add(new LocalVarShadowsFieldLinterProblem(variableNode, containingClass));
+					return;
+				}
+			}
+		}
+	}
+
+	public static class LocalVarShadowsFieldLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Local variable '${varName}' has the same name as field in class '${className}'";
+
+		public LocalVarShadowsFieldLinterProblem(IVariableNode variableNode, IClassNode classNode)
+		{
+			super(variableNode.getNameExpressionNode());
+			varName = variableNode.getName();
+			className = classNode.getName();
+		}
+
+		public String varName;
+		public String className;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/MXMLEmptyAttributeRule.java b/linter/src/main/java/org/apache/royale/linter/rules/MXMLEmptyAttributeRule.java
new file mode 100644
index 0000000..147ea2b
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/MXMLEmptyAttributeRule.java
@@ -0,0 +1,65 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.royale.compiler.mxml.IMXMLTagAttributeData;
+import org.apache.royale.compiler.mxml.IMXMLTagData;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.MXMLTagVisitor;
+import org.apache.royale.linter.MXMLTokenQuery;
+
+/**
+ * Check that MXML attribute values are not empty.
+ */
+public class MXMLEmptyAttributeRule extends LinterRule {
+	@Override
+	public List<MXMLTagVisitor> getMXMLTagVisitors() {
+		List<MXMLTagVisitor> result = new ArrayList<>();
+		result.add((tag, tokenQuery, problems) -> {
+			checkTag(tag, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkTag(IMXMLTagData tag, MXMLTokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		for (IMXMLTagAttributeData attribute : tag.getAttributeDatas()) {
+			if (attribute.getRawValue().trim().length() == 0) {
+				problems.add(new MXMLEmptyAttributeLinterProblem(attribute));
+			}
+		}
+	}
+
+	public static class MXMLEmptyAttributeLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "MXML attribute '${attributeName}' value is empty";
+
+		public MXMLEmptyAttributeLinterProblem(IMXMLTagAttributeData attribute) {
+			super(attribute);
+			this.attributeName = attribute.getName();
+		}
+
+		public String attributeName;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/MXMLIDRule.java b/linter/src/main/java/org/apache/royale/linter/rules/MXMLIDRule.java
new file mode 100644
index 0000000..4287044
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/MXMLIDRule.java
@@ -0,0 +1,82 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.royale.compiler.mxml.IMXMLTagAttributeData;
+import org.apache.royale.compiler.mxml.IMXMLTagData;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.MXMLTagVisitor;
+import org.apache.royale.linter.MXMLTokenQuery;
+
+/**
+ * Check that MXML id attribute values match a specific pattern.
+ */
+public class MXMLIDRule extends LinterRule {
+	public static final Pattern DEFAULT_NAME_PATTERN = Pattern.compile("^[a-z][a-zA-Z0-9]*$");
+
+	@Override
+	public List<MXMLTagVisitor> getMXMLTagVisitors() {
+		List<MXMLTagVisitor> result = new ArrayList<>();
+		result.add((tag, tokenQuery, problems) -> {
+			checkTag(tag, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	public Pattern pattern;
+
+	private void checkTag(IMXMLTagData tag, MXMLTokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IMXMLTagAttributeData idAttribute = tag.getTagAttributeData("id");
+		if (idAttribute == null) {
+			return;
+		}
+		Pattern thePattern = pattern;
+		if (thePattern == null) {
+			thePattern = DEFAULT_NAME_PATTERN;
+		}
+		Matcher matcher = thePattern.matcher(idAttribute.getRawValue());
+		if (matcher.matches()) {
+			return;
+		}
+		problems.add(new MXMLIDLinterProblem(idAttribute, thePattern));
+	}
+
+	public static class MXMLIDLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "MXML id '${idValue}' does not match the pattern '${pattern}'";
+
+		public MXMLIDLinterProblem(IMXMLTagAttributeData attribute, Pattern pattern)
+		{
+			super(attribute);
+			this.pattern = pattern.toString();
+			this.idValue = attribute.getRawValue();
+		}
+
+		public String pattern;
+		public String idValue;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/MaxBlockDepthRule.java b/linter/src/main/java/org/apache/royale/linter/rules/MaxBlockDepthRule.java
new file mode 100644
index 0000000..49aa594
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/MaxBlockDepthRule.java
@@ -0,0 +1,113 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IBlockNode;
+import org.apache.royale.compiler.tree.as.IConditionalNode;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.compiler.tree.as.IStatementNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Checks the number of nested blocks in a function.
+ */
+public class MaxBlockDepthRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.FunctionID, (node, tokenQuery, problems) -> {
+			checkFunctionNode((IFunctionNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	public int maximum = 4;
+
+	private void checkFunctionNode(IFunctionNode functionNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		int maxBlockDepth = getMaxDepth(functionNode.getScopedNode());
+		if (maxBlockDepth <= maximum) {
+			return;
+		}
+		problems.add(new MaxBlockDepthLinterProblem(functionNode, maxBlockDepth, maximum));
+	}
+
+	private int getMaxDepth(IASNode node) {
+		int maxDepth = 0;
+		for(int i = 0; i < node.getChildCount(); i++) {
+			IASNode child = node.getChild(i);
+			if (child instanceof IFunctionNode) {
+				// nested functions don't count
+				continue;
+			}
+			if (child instanceof IStatementNode) {
+				IStatementNode statementNode = (IStatementNode) child;
+				IASNode possibleBlock = null;
+				if (statementNode.getChildCount() > 0) {
+					possibleBlock = statementNode.getChild(statementNode.getChildCount() - 1);
+					if (possibleBlock instanceof IConditionalNode) {
+						IConditionalNode conditionalNode = (IConditionalNode) possibleBlock;
+						if (conditionalNode.getChildCount() > 0) {
+							possibleBlock = conditionalNode.getChild(conditionalNode.getChildCount() - 1);
+						}
+					}
+				}
+				if (possibleBlock instanceof IBlockNode) {
+					int childMaxDepth = getMaxDepth(possibleBlock) + 1;
+					if (maxDepth < childMaxDepth) {
+						maxDepth = childMaxDepth;
+					}
+				}
+			}
+			else if (child instanceof IBlockNode) {
+				int childMaxDepth = getMaxDepth(child) + 1;
+				if (maxDepth < childMaxDepth) {
+					maxDepth = childMaxDepth;
+				}
+			}
+		}
+		return maxDepth;
+	}
+
+	public static class MaxBlockDepthLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Function '${functionName}' has blocks nested ${depth} levels deep, but expected no deeper than ${maxDepth} levels";
+
+		public MaxBlockDepthLinterProblem(IFunctionNode node, int depth, int maxDepth)
+		{
+			super(node.getParametersContainerNode());
+			this.depth = depth;
+			this.maxDepth = maxDepth;
+			functionName = node.getName();
+		}
+
+		public String functionName;
+		public int depth;
+		public int maxDepth;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/MaxParametersRule.java b/linter/src/main/java/org/apache/royale/linter/rules/MaxParametersRule.java
new file mode 100644
index 0000000..b2005b3
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/MaxParametersRule.java
@@ -0,0 +1,71 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Checks the number of function parameters.
+ */
+public class MaxParametersRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.FunctionID, (node, tokenQuery, problems) -> {
+			checkFunctionNode((IFunctionNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	public int maximum = 6;
+
+	private void checkFunctionNode(IFunctionNode functionNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (functionNode.getParameterNodes().length <= maximum) {
+			return;
+		}
+		problems.add(new MaxParametersLinterProblem(functionNode, maximum));
+	}
+
+	public static class MaxParametersLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Function '${functionName}' has ${params} parameters, but expected no more than ${maxParams} parameters";
+
+		public MaxParametersLinterProblem(IFunctionNode node, int maxParams)
+		{
+			super(node.getParametersContainerNode());
+			this.maxParams = maxParams;
+			params = node.getParameterNodes().length;
+			functionName = node.getName();
+		}
+
+		public String functionName;
+		public int params;
+		public int maxParams;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/MissingASDocRule.java b/linter/src/main/java/org/apache/royale/linter/rules/MissingASDocRule.java
new file mode 100644
index 0000000..5459afd
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/MissingASDocRule.java
@@ -0,0 +1,140 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.common.ISourceLocation;
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.parsing.IMXMLToken;
+import org.apache.royale.compiler.parsing.IMXMLToken.MXMLTokenKind;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IDocumentableDefinitionNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.MXMLTokenQuery;
+import org.apache.royale.linter.MXMLTokenVisitor;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Checks for missing or empty ASDoc comments.
+ */
+public class MissingASDocRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.ClassID, (node, tokenQuery, problems) -> {
+			checkDocumentableDefinitionNode((IDocumentableDefinitionNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.InterfaceID, (node, tokenQuery, problems) -> {
+			checkDocumentableDefinitionNode((IDocumentableDefinitionNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.FunctionID, (node, tokenQuery, problems) -> {
+			checkDocumentableDefinitionNode((IDocumentableDefinitionNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.VariableID, (node, tokenQuery, problems) -> {
+			checkDocumentableDefinitionNode((IDocumentableDefinitionNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	@Override
+	public Map<MXMLTokenKind, MXMLTokenVisitor> getMXMLTokenVisitors() {
+		Map<MXMLTokenKind, MXMLTokenVisitor> result = new HashMap<>();
+		result.put(MXMLTokenKind.COMMENT, (token, tokenQuery, problems) -> {
+			checkMXMLComment(token, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkDocumentableDefinitionNode(IDocumentableDefinitionNode definitionNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (!definitionNode.hasNamespace("public")) {
+			return;
+		}
+		IASToken token = tokenQuery.getTokenBefore(definitionNode);
+		if (token.getType() == ASTokenTypes.TOKEN_ASDOC_COMMENT) {
+			String docComment = token.getText();
+			if (!isDocCommentEmpty(docComment)) {
+				return;
+			}
+			problems.add(new EmptyASDocLinterProblem(token));
+			return;
+		}
+		problems.add(new MissingASDocLinterProblem(definitionNode));
+	}
+
+	private boolean isDocCommentEmpty(String docComment) {
+		docComment = docComment.substring(3, docComment.length() - 2).trim();
+		String[] lines = docComment.split("\\r?\\n");
+		for (int i = 0; i < lines.length; i++) {
+			String line = lines[0];
+			line = line.trim();
+			if (line.startsWith("*")) {
+				line = line.substring(1).trim();
+			}
+			if (line.length() > 0) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	private void checkMXMLComment(IMXMLToken comment, MXMLTokenQuery tokenQuery,
+			Collection<ICompilerProblem> problems) {
+		String commentText = comment.getText();
+		boolean isASDoc = commentText.startsWith("<!---") && commentText.length() > 7;
+		if (!isASDoc) {
+			return;
+		}
+		commentText = commentText.substring(5, commentText.length() - 3).trim();
+		if (commentText.length() > 0) {
+			return;
+		}
+		problems.add(new EmptyASDocLinterProblem(comment));
+	}
+
+	public static class MissingASDocLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Public APIs must have ASDoc comments";
+
+		public MissingASDocLinterProblem(IDocumentableDefinitionNode node)
+		{
+			super(node);
+		}
+	}
+
+	public static class EmptyASDocLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "ASDoc comments must not be empty";
+
+		public EmptyASDocLinterProblem(IASToken token)
+		{
+			super(token);
+		}
+
+		public EmptyASDocLinterProblem(IMXMLToken token)
+		{
+			super((ISourceLocation) token);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/MissingNamespaceRule.java b/linter/src/main/java/org/apache/royale/linter/rules/MissingNamespaceRule.java
new file mode 100644
index 0000000..1d02626
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/MissingNamespaceRule.java
@@ -0,0 +1,219 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.internal.tree.as.BaseDefinitionNode;
+import org.apache.royale.compiler.internal.tree.as.IdentifierNode;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IClassNode;
+import org.apache.royale.compiler.tree.as.IDefinitionNode;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.compiler.tree.as.IFunctionObjectNode;
+import org.apache.royale.compiler.tree.as.IInterfaceNode;
+import org.apache.royale.compiler.tree.as.INamespaceDecorationNode;
+import org.apache.royale.compiler.tree.as.IPackageNode;
+import org.apache.royale.compiler.tree.as.IScopedNode;
+import org.apache.royale.compiler.tree.as.IVariableNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that symbols in package or class scopes have a namespace.
+ */
+public class MissingNamespaceRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.ClassID, (node, tokenQuery, problems) -> {
+			checkClassNode((IClassNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.InterfaceID, (node, tokenQuery, problems) -> {
+			checkInterfaceNode((IInterfaceNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.FunctionID, (node, tokenQuery, problems) -> {
+			checkFunctionNode((IFunctionNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.VariableID, (node, tokenQuery, problems) -> {
+			checkVariableNode((IVariableNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkClassNode(IClassNode classNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IScopedNode scopedNode = classNode.getContainingScope();
+		IASNode possiblePackage = scopedNode.getParent();
+		if (!(possiblePackage instanceof IPackageNode)) {
+			return;
+		}
+		if (hasNamespace(classNode)) {
+			return;
+		}
+		problems.add(new MissingNamespaceOnClassLinterProblem(classNode));
+	}
+
+	private void checkInterfaceNode(IInterfaceNode interfaceNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IScopedNode scopedNode = interfaceNode.getContainingScope();
+		IASNode possiblePackage = scopedNode.getParent();
+		if (!(possiblePackage instanceof IPackageNode)) {
+			return;
+		}
+		if (hasNamespace(interfaceNode)) {
+			return;
+		}
+		problems.add(new MissingNamespaceOnInterfaceLinterProblem(interfaceNode));
+	}
+
+	private void checkFunctionNode(IFunctionNode functionNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IScopedNode scopedNode = functionNode.getContainingScope();
+		IASNode possiblePackageOrClass = scopedNode.getParent();
+		if (possiblePackageOrClass instanceof IPackageNode) {
+			if (hasNamespace(functionNode) || functionNode.getParent() instanceof IFunctionObjectNode) {
+				return;
+			}
+			problems.add(new MissingNamespaceOnPackageFunctionLinterProblem(functionNode));
+			return;
+		}
+		if (possiblePackageOrClass instanceof IClassNode) {
+			if (hasNamespace(functionNode) || functionNode.getParent() instanceof IFunctionObjectNode) {
+				return;
+			}
+			problems.add(new MissingNamespaceOnMethodLinterProblem(functionNode));
+			return;
+		}
+	}
+
+	private void checkVariableNode(IVariableNode variableNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IScopedNode scopedNode = variableNode.getContainingScope();
+		IASNode possiblePackageOrClass = scopedNode.getParent();
+		if (possiblePackageOrClass instanceof IPackageNode) {
+			if (hasNamespace(variableNode)) {
+				return;
+			}
+			problems.add(new MissingNamespaceOnPackageVariableLinterProblem(variableNode));
+			return;
+		}
+		if (possiblePackageOrClass instanceof IClassNode) {
+			if (hasNamespace(variableNode)) {
+				return;
+			}
+			problems.add(new MissingNamespaceOnFieldLinterProblem(variableNode));
+			return;
+		}
+	}
+
+	private boolean hasNamespace(IDefinitionNode node) {
+		if (node instanceof BaseDefinitionNode) {
+			BaseDefinitionNode baseDefNode = (BaseDefinitionNode) node;
+			INamespaceDecorationNode nsNode = baseDefNode.getNamespaceNode();
+			if (nsNode == null) {
+				return false;
+			}
+			if (nsNode instanceof IdentifierNode) {
+				IdentifierNode identifierNode = (IdentifierNode) nsNode;
+				if (identifierNode.isImplicit()) {
+					return false;
+				}
+			}
+			return true;
+		}
+		String ns = node.getNamespace();
+		return ns != null;
+	}
+
+	public static class MissingNamespaceOnClassLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Missing namespace on class '${className}'";
+
+		public MissingNamespaceOnClassLinterProblem(IClassNode node)
+		{
+			super(node);
+			className = node.getName();
+		}
+
+		public String className;
+	}
+
+	public static class MissingNamespaceOnInterfaceLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Missing namespace on interface '${interfaceName}'";
+
+		public MissingNamespaceOnInterfaceLinterProblem(IInterfaceNode node)
+		{
+			super(node);
+			interfaceName = node.getName();
+		}
+
+		public String interfaceName;
+	}
+
+	public static class MissingNamespaceOnPackageFunctionLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Missing namespace on package function '${functionName}'";
+
+		public MissingNamespaceOnPackageFunctionLinterProblem(IFunctionNode node)
+		{
+			super(node);
+			functionName = node.getQualifiedName();
+		}
+
+		public String functionName;
+	}
+
+	public static class MissingNamespaceOnMethodLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Missing namespace on method '${functionName}'";
+
+		public MissingNamespaceOnMethodLinterProblem(IFunctionNode node)
+		{
+			super(node);
+			functionName = node.getName();
+		}
+
+		public String functionName;
+	}
+
+	public static class MissingNamespaceOnPackageVariableLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Missing namespace on package variable '${variableName}'";
+
+		public MissingNamespaceOnPackageVariableLinterProblem(IVariableNode node)
+		{
+			super(node);
+			variableName = node.getQualifiedName();
+		}
+
+		public String variableName;
+	}
+
+	public static class MissingNamespaceOnFieldLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Missing namespace on field '${variableName}'";
+
+		public MissingNamespaceOnFieldLinterProblem(IVariableNode node)
+		{
+			super(node);
+			variableName = node.getName();
+		}
+
+		public String variableName;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/MissingSemicolonRule.java b/linter/src/main/java/org/apache/royale/linter/rules/MissingSemicolonRule.java
new file mode 100644
index 0000000..50a5371
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/MissingSemicolonRule.java
@@ -0,0 +1,62 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.TokenQuery;
+import org.apache.royale.linter.TokenVisitor;
+
+/**
+ * Checks for statements with missing semicolons.
+ */
+public class MissingSemicolonRule extends LinterRule {
+	@Override
+	public Map<Integer, TokenVisitor> getTokenVisitors() {
+		Map<Integer, TokenVisitor> result = new HashMap<>();
+		result.put(ASTokenTypes.TOKEN_SEMICOLON, (token, tokenQuery, problems) -> {
+			checkSemicolon(token, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkSemicolon(IASToken semicolon, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (!semicolon.isImplicit()) {
+			return;
+		}
+		problems.add(new MissingSemicolonLinterProblem(semicolon));
+	}
+
+	public static class MissingSemicolonLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Semicolon is required";
+
+		public MissingSemicolonLinterProblem(IASToken token)
+		{
+			super(token);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/MissingTypeRule.java b/linter/src/main/java/org/apache/royale/linter/rules/MissingTypeRule.java
new file mode 100644
index 0000000..1b19034
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/MissingTypeRule.java
@@ -0,0 +1,129 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.internal.tree.as.IdentifierNode;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IExpressionNode;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.compiler.tree.as.IParameterNode;
+import org.apache.royale.compiler.tree.as.IVariableNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Checks that a type has been declared for all variables, function parameters,
+ * and function returns.
+ */
+public class MissingTypeRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.VariableID, (node, tokenQuery, problems) -> {
+			checkVariableNode((IVariableNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.FunctionID, (node, tokenQuery, problems) -> {
+			checkFunctionNode((IFunctionNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkVariableNode(IVariableNode variableNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IExpressionNode typeNode = variableNode.getVariableTypeNode();
+		if (isValidTypeNode(typeNode)) {
+			return;
+		}
+		problems.add(new MissingVariableTypeLinterProblem(variableNode));
+	}
+
+	private void checkFunctionNode(IFunctionNode functionNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		for (IParameterNode paramNode : functionNode.getParameterNodes()) {
+			if (paramNode.isRest()) {
+				//it's okay for a rest parameter not to have a type
+				continue;
+			}
+			IExpressionNode typeNode = paramNode.getVariableTypeNode();
+			if (!isValidTypeNode(typeNode)) {
+				problems.add(new MissingFunctionParameterTypeLinterProblem(paramNode));
+			}
+		}
+		if (functionNode.isConstructor()) {
+			return;
+		}
+		IExpressionNode typeNode = functionNode.getReturnTypeNode();
+		if (!isValidTypeNode(typeNode)) {
+			problems.add(new MissingFunctionReturnTypeLinterProblem(functionNode));
+		}
+	}
+
+	private boolean isValidTypeNode(IExpressionNode typeNode) {
+		if (typeNode == null) {
+			return false;
+		}
+		if (typeNode instanceof IdentifierNode) {
+			IdentifierNode identifierNode = (IdentifierNode) typeNode;
+			// isImplicit() is not on the interface, for some reason
+			if (identifierNode.isImplicit()) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	public static class MissingVariableTypeLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Missing type for variable '${varName}'";
+
+		public MissingVariableTypeLinterProblem(IVariableNode node)
+		{
+			super(node);
+			varName = node.getName();
+		}
+
+		public String varName;
+	}
+
+	public static class MissingFunctionParameterTypeLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Missing type for function parameter '${paramName}'";
+
+		public MissingFunctionParameterTypeLinterProblem(IParameterNode node)
+		{
+			super(node);
+			paramName = node.getName();
+		}
+
+		public String paramName;
+	}
+
+	public static class MissingFunctionReturnTypeLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Missing function return type";
+
+		public MissingFunctionReturnTypeLinterProblem(IFunctionNode node)
+		{
+			super(node);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/NumericLeadingZeroesRule.java b/linter/src/main/java/org/apache/royale/linter/rules/NumericLeadingZeroesRule.java
new file mode 100644
index 0000000..ac07172
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/NumericLeadingZeroesRule.java
@@ -0,0 +1,79 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.common.ISourceLocation;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.INumericLiteralNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Checks for use of numeric literals with leading zeroes, except when it starts
+ * with '0x' (hexadecimal notation). '0123' is not allowed. '0x123' is allowed.
+ * 
+ * Designed to prevent confusion with ECMAScript's deprecated octal notation.
+ */
+public class NumericLeadingZeroesRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.LiteralIntegerID, (node, tokenQuery, problems) -> {
+			checkNumericLiteralNode((INumericLiteralNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.LiteralNumberID, (node, tokenQuery, problems) -> {
+			checkNumericLiteralNode((INumericLiteralNode) node, tokenQuery, problems);
+		});
+		result.put(ASTNodeID.LiteralUintID, (node, tokenQuery, problems) -> {
+			checkNumericLiteralNode((INumericLiteralNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkNumericLiteralNode(INumericLiteralNode numberNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		String stringValue = numberNode.getNumericValue().toString();
+		if (!stringValue.startsWith("0")) {
+			return;
+		}
+		if (stringValue.startsWith("0x")) {
+			return;
+		}
+		problems.add(new NumericLeadingZeroesLinterProblem(numberNode));
+	}
+
+	public static class NumericLeadingZeroesLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Must remove leading zeros from numeric literal '${value}'";
+
+		public NumericLeadingZeroesLinterProblem(INumericLiteralNode node)
+		{
+			super((ISourceLocation) node);
+			value = node.getNumericValue().toString();
+		}
+
+		public String value;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/OverrideContainsOnlySuperCallRule.java b/linter/src/main/java/org/apache/royale/linter/rules/OverrideContainsOnlySuperCallRule.java
new file mode 100644
index 0000000..2138aed
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/OverrideContainsOnlySuperCallRule.java
@@ -0,0 +1,103 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.common.ASModifier;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IExpressionNode;
+import org.apache.royale.compiler.tree.as.IFunctionCallNode;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.compiler.tree.as.IIdentifierNode;
+import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode;
+import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode.LanguageIdentifierKind;
+import org.apache.royale.compiler.tree.as.IMemberAccessExpressionNode;
+import org.apache.royale.compiler.tree.as.IScopedNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that an overridden function contains more than a call to super.
+ */
+public class OverrideContainsOnlySuperCallRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.FunctionID, (node, tokenQuery, problems) -> {
+			checkFunctionNode((IFunctionNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkFunctionNode(IFunctionNode functionNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (!functionNode.hasModifier(ASModifier.OVERRIDE)) {
+			return;
+		}
+		IScopedNode scopedNode = functionNode.getScopedNode();
+		
+		if (scopedNode.getChildCount() == 0 || scopedNode.getChildCount() > 1) {
+			return;
+		}
+		IASNode child = scopedNode.getChild(0);
+		if (!(child instanceof IFunctionCallNode)) {
+			return;
+		}
+		IFunctionCallNode functionCallNode = (IFunctionCallNode) child;
+		IExpressionNode nameNode = functionCallNode.getNameNode();
+		if (!(nameNode instanceof IMemberAccessExpressionNode)) {
+			return;
+		}
+		IMemberAccessExpressionNode memberAccess = (IMemberAccessExpressionNode) nameNode;
+		if (!(memberAccess.getLeftOperandNode() instanceof ILanguageIdentifierNode)) {
+			return;
+		}
+		ILanguageIdentifierNode superNode = (ILanguageIdentifierNode) memberAccess.getLeftOperandNode();
+		if (!LanguageIdentifierKind.SUPER.equals(superNode.getKind())) {
+			return;
+		}
+		if (!(memberAccess.getRightOperandNode() instanceof IIdentifierNode)) {
+			return;
+		}
+		IIdentifierNode rightNode = (IIdentifierNode) memberAccess.getRightOperandNode();
+		if (!functionNode.getName().equals(rightNode.getName())) {
+			return;
+		}
+		problems.add(new OverrideContainsOnlySuperCallLinterProblem(functionNode));
+	}
+
+	public static class OverrideContainsOnlySuperCallLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Method override '${functionName}' must contain more than call to 'super.${functionName}'";
+
+		public OverrideContainsOnlySuperCallLinterProblem(IFunctionNode node)
+		{
+			super(node.getNameExpressionNode());
+			functionName = node.getName();
+		}
+
+		public String functionName;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/PackageNameRule.java b/linter/src/main/java/org/apache/royale/linter/rules/PackageNameRule.java
new file mode 100644
index 0000000..de50e9a
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/PackageNameRule.java
@@ -0,0 +1,82 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IPackageNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that package names match a specific pattern.
+ */
+public class PackageNameRule extends LinterRule {
+	public static final Pattern DEFAULT_NAME_PATTERN = Pattern.compile("^[a-z][a-zA-Z0-9]*(\\.[a-z][a-zA-Z0-9]*)*$");
+
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.PackageID, (node, tokenQuery, problems) -> {
+			checkPackageNode((IPackageNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	public Pattern pattern;
+
+	private void checkPackageNode(IPackageNode packageNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		String packageName = packageNode.getName();
+		if (packageName.length() == 0) {
+			return;
+		}
+		Pattern thePattern = pattern;
+		if (thePattern == null) {
+			thePattern = DEFAULT_NAME_PATTERN;
+		}
+		Matcher matcher = thePattern.matcher(packageName);
+		if (matcher.matches()) {
+			return;
+		}
+		problems.add(new PackageNameLinterProblem(packageNode, thePattern));
+	}
+
+	public static class PackageNameLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Package name '${packageName}' does not match the pattern '${pattern}'";
+
+		public PackageNameLinterProblem(IPackageNode node, Pattern pattern)
+		{
+			super(node);
+			this.pattern = pattern.toString();
+			packageName = node.getName();
+		}
+
+		public String pattern;
+		public String packageName;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/StaticConstantsRule.java b/linter/src/main/java/org/apache/royale/linter/rules/StaticConstantsRule.java
new file mode 100644
index 0000000..59ddba9
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/StaticConstantsRule.java
@@ -0,0 +1,74 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.common.ASModifier;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IClassNode;
+import org.apache.royale.compiler.tree.as.IScopedNode;
+import org.apache.royale.compiler.tree.as.IVariableNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Checks for constants that are declared on a class, but are not static.
+ */
+public class StaticConstantsRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.VariableID, (node, tokenQuery, problems) -> {
+			checkVariableNode((IVariableNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkVariableNode(IVariableNode variableNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (!variableNode.isConst() || variableNode.hasModifier(ASModifier.STATIC)) {
+			return;
+		}
+		IScopedNode containingScope = variableNode.getContainingScope();
+		if (containingScope == null) {
+			return;
+		}
+		IASNode possibleClass = containingScope.getParent();
+		if (!(possibleClass instanceof IClassNode)) {
+			return;
+		}
+		problems.add(new StaticConstantsLinterProblem(variableNode));
+	}
+
+	public static class StaticConstantsLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Constant must be static";
+
+		public StaticConstantsLinterProblem(IVariableNode node)
+		{
+			super(node);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/StrictEqualityRule.java b/linter/src/main/java/org/apache/royale/linter/rules/StrictEqualityRule.java
new file mode 100644
index 0000000..457617c
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/StrictEqualityRule.java
@@ -0,0 +1,67 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.tree.as.IOperatorNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.TokenVisitor;
+
+/**
+ * Checks for uses of the '==' and '!='' operators instead of the stricter '==='
+ * and '!==' operators.
+ */
+public class StrictEqualityRule extends LinterRule {
+	@Override
+	public Map<Integer, TokenVisitor> getTokenVisitors() {
+		Map<Integer, TokenVisitor> result = new HashMap<>();
+		result.put(ASTokenTypes.TOKEN_OPERATOR_EQUAL, (token, tokenQuery, problems) -> {
+			problems.add(new StrictEqualityLinterProblem(token));
+		});
+		result.put(ASTokenTypes.TOKEN_OPERATOR_NOT_EQUAL, (token, tokenQuery, problems) -> {
+			problems.add(new StrictEqualityLinterProblem(token));
+		});
+		return result;
+	}
+
+	public static class StrictEqualityLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Must use ${requiredTokenText} instead of ${tokenText}";
+
+		public StrictEqualityLinterProblem(IASToken token)
+		{
+			super(token);
+			tokenText = token.getText();
+			if (IOperatorNode.OperatorType.EQUAL.getOperatorText().equals(tokenText)) {
+				requiredTokenText = IOperatorNode.OperatorType.STRICT_EQUAL.getOperatorText();
+			}
+			else if (IOperatorNode.OperatorType.NOT_EQUAL.getOperatorText().equals(tokenText)) {
+				requiredTokenText = IOperatorNode.OperatorType.STRICT_NOT_EQUAL.getOperatorText();
+			}
+		}
+
+		public String tokenText;
+		public String requiredTokenText;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/StringEventNameRule.java b/linter/src/main/java/org/apache/royale/linter/rules/StringEventNameRule.java
new file mode 100644
index 0000000..8f0540c
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/StringEventNameRule.java
@@ -0,0 +1,107 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IExpressionNode;
+import org.apache.royale.compiler.tree.as.IFunctionCallNode;
+import org.apache.royale.compiler.tree.as.IIdentifierNode;
+import org.apache.royale.compiler.tree.as.ILiteralNode;
+import org.apache.royale.compiler.tree.as.ILiteralNode.LiteralType;
+import org.apache.royale.compiler.tree.as.IMemberAccessExpressionNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that calls to event dispatcher methods don't use string values for
+ * event names.
+ */
+public class StringEventNameRule extends LinterRule {
+	private static final String[] EVENT_DISPATCHER_FUNCTION_NAMES = {
+		"addEventListener",
+		"removeEventListener",
+		"hasEventListener"
+	};
+
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.FunctionCallID, (node, tokenQuery, problems) -> {
+			checkFunctionCallNode((IFunctionCallNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkFunctionCallNode(IFunctionCallNode functionCallNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		String functionName = null;
+		IExpressionNode nameNode = functionCallNode.getNameNode();
+		if (nameNode instanceof IIdentifierNode) {
+			IIdentifierNode identifierNode = (IIdentifierNode) nameNode;
+			functionName = identifierNode.getName();
+		}
+		else if(nameNode instanceof IMemberAccessExpressionNode) {
+			IMemberAccessExpressionNode memberAccessNode = (IMemberAccessExpressionNode) nameNode;
+			IExpressionNode rightNode = memberAccessNode.getRightOperandNode();
+			if (rightNode instanceof IIdentifierNode) {
+				IIdentifierNode identifierNode = (IIdentifierNode) rightNode;
+				functionName = identifierNode.getName();
+			}
+		}
+		if (functionName == null) {
+			return;
+		}
+		if (!Arrays.asList(EVENT_DISPATCHER_FUNCTION_NAMES).contains(functionName)) {
+			return;
+		}
+		IExpressionNode[] argumentNodes = functionCallNode.getArgumentNodes();
+		if (argumentNodes.length == 0) {
+			return;
+		}
+		IExpressionNode firstArgument = argumentNodes[0];
+		if (!(firstArgument instanceof ILiteralNode)) {
+			return;
+		}
+		ILiteralNode literalNode = (ILiteralNode) firstArgument;
+		if (!LiteralType.STRING.equals(literalNode.getLiteralType())) {
+			return;
+		}
+		problems.add(new StringEventNameProblem(functionCallNode, functionName));
+	}
+
+	public static class StringEventNameProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Calls to '${functionName}' must use constant value instead of string literal for event name";
+
+		public StringEventNameProblem(IFunctionCallNode node, String functionName)
+		{
+			super(node.getArgumentNodes()[0]);
+			this.functionName = functionName;
+		}
+
+		public String functionName;
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/SwitchWithoutDefaultRule.java b/linter/src/main/java/org/apache/royale/linter/rules/SwitchWithoutDefaultRule.java
new file mode 100644
index 0000000..3a9a7b6
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/SwitchWithoutDefaultRule.java
@@ -0,0 +1,64 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.ISwitchNode;
+import org.apache.royale.compiler.tree.as.ITerminalNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check for 'switch' statements that are missing a 'default' clause.
+ */
+public class SwitchWithoutDefaultRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.SwitchID, (node, tokenQuery, problems) -> {
+			checkSwitchNode((ISwitchNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkSwitchNode(ISwitchNode switchNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		ITerminalNode defaultNode = switchNode.getDefaultNode();
+		if (defaultNode != null) {
+			return;
+		}
+		problems.add(new SwitchWithoutDefaultLinterProblem(switchNode));
+	}
+
+	public static class SwitchWithoutDefaultLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Missing 'default' clause in 'switch' statement";
+
+		public SwitchWithoutDefaultLinterProblem(ISwitchNode node)
+		{
+			super(node);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/ThisInClosureRule.java b/linter/src/main/java/org/apache/royale/linter/rules/ThisInClosureRule.java
new file mode 100644
index 0000000..b994da6
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/ThisInClosureRule.java
@@ -0,0 +1,86 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IBlockNode;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Checks for use of the 'this' keyword in closures.
+ */
+public class ThisInClosureRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.FunctionID, (node, tokenQuery, problems) -> {
+			checkFunctionNode((IFunctionNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkFunctionNode(IFunctionNode functionNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		IFunctionNode ancestorFunction = (IFunctionNode) functionNode.getAncestorOfType(IFunctionNode.class);
+		if (ancestorFunction == null) {
+			return;
+		}
+		IBlockNode blockNode = getBody(functionNode);
+		if (blockNode == null) {
+			return;
+		}
+		for(IASToken token : tokenQuery.getTokens(blockNode)) {
+			if (token.getType() == ASTokenTypes.TOKEN_KEYWORD_THIS) {
+				problems.add(new ThisInClosureLinterProblem(token));
+			}
+		}
+	}
+
+	private IBlockNode getBody(IFunctionNode functionNode) {
+		if (functionNode.getChildCount() == 0) {
+			return null;
+		}
+		IASNode lastChild = functionNode.getChild(functionNode.getChildCount() - 1);
+		if (lastChild instanceof IBlockNode) {
+			return (IBlockNode) lastChild;
+		}
+		return null;
+	}
+
+	public static class ThisInClosureLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Closure must not contain 'this' keyword";
+
+		public ThisInClosureLinterProblem(IASToken token)
+		{
+			super(token);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/TraceRule.java b/linter/src/main/java/org/apache/royale/linter/rules/TraceRule.java
new file mode 100644
index 0000000..bd5e6b6
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/TraceRule.java
@@ -0,0 +1,68 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IFunctionCallNode;
+import org.apache.royale.compiler.tree.as.IMemberAccessExpressionNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check for calls to the 'trace()' function.
+ */
+public class TraceRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.FunctionCallID, (node, tokenQuery, problems) -> {
+			checkFunctionCallNode((IFunctionCallNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkFunctionCallNode(IFunctionCallNode functionCallNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (!"trace".equals(functionCallNode.getFunctionName())) {
+			return;
+		}
+		IASNode parentNode = functionCallNode.getParent();
+		if (parentNode instanceof IMemberAccessExpressionNode) {
+			return;
+		}
+		problems.add(new TraceLinterProblem(functionCallNode));
+	}
+
+	public static class TraceLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Must not call trace() function";
+
+		public TraceLinterProblem(IFunctionCallNode node)
+		{
+			super(node);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/WildcardImportRule.java b/linter/src/main/java/org/apache/royale/linter/rules/WildcardImportRule.java
new file mode 100644
index 0000000..8f7bf9f
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/WildcardImportRule.java
@@ -0,0 +1,62 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IImportNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check for import statements that import the entire package.
+ */
+public class WildcardImportRule extends LinterRule {
+	@Override
+	public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+		Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+		result.put(ASTNodeID.ImportID, (node, tokenQuery, problems) -> {
+			checkImportNode((IImportNode) node, tokenQuery, problems);
+		});
+		return result;
+	}
+
+	private void checkImportNode(IImportNode importNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+		if (!importNode.isWildcardImport()) {
+			return;
+		}
+		problems.add(new WildcardImportLinterProblem(importNode));
+	}
+
+	public static class WildcardImportLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Must not use wildcard import";
+
+		public WildcardImportLinterProblem(IImportNode node)
+		{
+			super(node);
+		}
+	}
+}
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/WithRule.java b/linter/src/main/java/org/apache/royale/linter/rules/WithRule.java
new file mode 100644
index 0000000..840a98c
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/WithRule.java
@@ -0,0 +1,52 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  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.royale.linter.rules;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.TokenVisitor;
+
+/**
+ * Checks for uses of 'with(x)'.
+ */
+public class WithRule extends LinterRule {
+	@Override
+	public Map<Integer, TokenVisitor> getTokenVisitors() {
+		Map<Integer, TokenVisitor> result = new HashMap<>();
+		result.put(ASTokenTypes.TOKEN_KEYWORD_WITH, (token, tokenQuery, problems) -> {
+			problems.add(new WithLinterProblem(token));
+		});
+		return result;
+	}
+
+	public static class WithLinterProblem extends CompilerProblem {
+		public static final String DESCRIPTION = "Must not use 'with' statement";
+
+		public WithLinterProblem(IASToken token)
+		{
+			super(token);
+		}
+	}
+}
diff --git a/pom.xml b/pom.xml
index f25b068..68f6a38 100644
--- a/pom.xml
+++ b/pom.xml
@@ -107,6 +107,7 @@
     <module>swfutils</module>
     <module>debugger</module>
     <module>formatter</module>
+    <module>linter</module>
     <module>flex-compiler-oem</module>
     <module>royale-ant-tasks</module>
     <module>royaleunit-ant-tasks</module>