Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
added hint checking for cfscript: components,functions,arguments.
  • Loading branch information
ryaneberly committed Oct 23, 2016
1 parent 2e05039 commit 1b07e49
Show file tree
Hide file tree
Showing 23 changed files with 336 additions and 47 deletions.
22 changes: 9 additions & 13 deletions src/main/java/com/cflint/CFLint.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.cflint.tools.CFLintFilter;
import com.cflint.tools.CFNestedExpressionProvider;
import com.cflint.tools.FileUtil;
import com.cflint.tools.PrecedingCommentReader;
import com.cflint.tools.ScanningProgressMonitorLookAhead;

import cfml.CFSCRIPTLexer;
Expand Down Expand Up @@ -435,7 +436,7 @@ private void process(final CFScriptStatement expression, Context context) {
} else if (expression instanceof CFFuncDeclStatement) {
final CFFuncDeclStatement function = (CFFuncDeclStatement) expression;
final Context functionContext = context.subContext(null);
functionContext.setFunctionIdentifier(function.getName());
functionContext.setFunctionInfo(function);
registerRuleOverrides(functionContext, function.getToken());
inFunction = true;
handler.push("function");
Expand Down Expand Up @@ -509,18 +510,13 @@ protected void scanExpression(final CFScriptStatement expression, Context contex
* Register any overrides from multi-line comments.
*/
protected void registerRuleOverrides(Context context, final Token functionToken) {
Iterable<Token> tokens = context.beforeTokens(functionToken);
for (Token currentTok : tokens) {
if (currentTok.getChannel() == Token.HIDDEN_CHANNEL && currentTok.getType() == CFSCRIPTLexer.ML_COMMENT) {
String mlText = currentTok.getText();
Pattern pattern = Pattern.compile(".*\\s*@CFLintIgnore\\s+([\\w,_]+)\\s*.*", Pattern.DOTALL);
Matcher matcher = pattern.matcher(mlText);
if (matcher.matches()) {
String ignoreCodes = matcher.group(1);
context.ignore(Arrays.asList(ignoreCodes.split(",\\s*")));
}
} else if (currentTok.getLine() < functionToken.getLine()) {
break;
final String mlText = PrecedingCommentReader.getMultiLine(context, functionToken);
if(mlText != null && !mlText.isEmpty()){
final Pattern pattern = Pattern.compile(".*\\s*@CFLintIgnore\\s+([\\w,_]+)\\s*.*", Pattern.DOTALL);
final Matcher matcher = pattern.matcher(mlText);
if (matcher.matches()) {
String ignoreCodes = matcher.group(1);
context.ignore(Arrays.asList(ignoreCodes.split(",\\s*")));
}
}
}
Expand Down
32 changes: 27 additions & 5 deletions src/main/java/com/cflint/plugins/Context.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.cflint.plugins;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Token;
Expand All @@ -11,6 +14,7 @@
import com.cflint.StackHandler;

import cfml.parsing.cfscript.CFIdentifier;
import cfml.parsing.cfscript.script.CFFuncDeclStatement;
import net.htmlparser.jericho.Element;
import static com.cflint.tools.CFTool.*;

Expand All @@ -20,6 +24,8 @@ public class Context {
String componentName;
final Element element;
List<Element> siblingElements;
CFFuncDeclStatement functionInfo;

String functionName;
boolean inAssignmentExpression;
public void setInAssignmentExpression(boolean inAssignmentExpression) {
Expand Down Expand Up @@ -74,9 +80,15 @@ public String getFunctionName() {
public String getComponentName() {
return componentName;
}

public void setFunctionIdentifier(final CFIdentifier functionName) {
this.functionName = functionName == null ? "" : functionName.Decompile(0);
public String calcComponentName() {
if (componentName!= null && !componentName.trim().isEmpty()){
return componentName.trim();
}
if(filename == null){
return "";
}
//Return filename without the cfc extension
return new File(filename).getName().replaceAll("\\.\\w+$", "");
}

public void setFunctionName(final String functionName) {
Expand Down Expand Up @@ -254,14 +266,14 @@ public ContextTokensIterator(Token token, int direction){
@Override
public boolean hasNext() {
if(direction <0)
return tokens != null && tokenIndex > 0;
return tokens != null && tokenIndex >= 0;
else
return tokens != null && tokenIndex < tokens.getTokens().size();
}

@Override
public Token next() {
if (tokens != null && tokenIndex > 0){
if (tokens != null && tokenIndex >= 0){
Token retval = tokens.getTokens().get(tokenIndex);
tokenIndex += direction;
return retval;
Expand Down Expand Up @@ -298,5 +310,15 @@ public Element getPreviousSiblingElement(){
return null;
}

public CFFuncDeclStatement getFunctionInfo() {
return functionInfo;
}

public void setFunctionInfo(CFFuncDeclStatement functionInfo) {
this.functionInfo = functionInfo;
if(this.functionInfo != null){
this.functionName = functionInfo.getName() == null ? "" : functionInfo.getName().Decompile(0);
}
}

}
48 changes: 48 additions & 0 deletions src/main/java/com/cflint/plugins/core/ArgHintChecker.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
package com.cflint.plugins.core;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.cflint.BugList;
import com.cflint.plugins.CFLintScannerAdapter;
import com.cflint.plugins.Context;
import com.cflint.tools.CFTool;
import com.cflint.tools.PrecedingCommentReader;

import cfml.parsing.cfscript.CFAssignmentExpression;
import cfml.parsing.cfscript.CFExpression;
import cfml.parsing.cfscript.script.CFFuncDeclStatement;
import cfml.parsing.cfscript.script.CFFunctionParameter;
import cfml.parsing.cfscript.script.CFScriptStatement;
import net.htmlparser.jericho.Element;

public class ArgHintChecker extends CFLintScannerAdapter {
Expand All @@ -19,4 +34,37 @@ public void element(final Element element, final Context context, final BugList
}
}

@Override
public void expression(CFScriptStatement expression, Context context, BugList bugs) {
if(expression instanceof CFFuncDeclStatement){
final CFFuncDeclStatement funcDeclStatement = (CFFuncDeclStatement) expression;
final String _mlText = PrecedingCommentReader.getMultiLine(context, expression.getToken());
final String mlText = _mlText==null?null:_mlText.replaceFirst("^/\\*", "").replaceAll("\\*/$", "").trim();

//Read the function comments to get the javadoc style annotations
final Map<String,String> annotations = new HashMap<String,String>();
if(mlText != null && !mlText.isEmpty()){
final Pattern pattern = Pattern.compile("^.*\\s*@(\\w+)\\s+(.*+)$");
BufferedReader reader = new BufferedReader(new StringReader(mlText));
try {
String line = reader.readLine();
while(line != null){
final Matcher matcher = pattern.matcher(line.trim());
if (matcher.matches()) {
annotations.put(matcher.group(1).trim().toLowerCase(),matcher.group(2).trim());
}
line = reader.readLine();
}
} catch (IOException e) {}

}

for (final CFFunctionParameter expr : funcDeclStatement.getFormals()) {
if (expr != null && expr.getName() != null && !annotations.containsKey(expr.getName().toLowerCase())) {
context.addMessage("ARG_HINT_MISSING_SCRIPT", expr.getName());
}
}

}
}
}
47 changes: 40 additions & 7 deletions src/main/java/com/cflint/plugins/core/ComponentHintChecker.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,57 @@
package com.cflint.plugins.core;

import com.cflint.BugInfo;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.cflint.BugList;
import com.cflint.plugins.CFLintScannerAdapter;
import com.cflint.plugins.Context;
import com.cflint.tools.CFTool;
import com.cflint.tools.PrecedingCommentReader;

import cfml.parsing.cfscript.CFExpression;
import cfml.parsing.cfscript.CFIdentifier;
import cfml.parsing.cfscript.script.CFCompDeclStatement;
import cfml.parsing.cfscript.script.CFScriptStatement;
import net.htmlparser.jericho.Element;

public class ComponentHintChecker extends CFLintScannerAdapter {
final String severity = "INFO";

@Override
public void element(final Element element, final Context context, final BugList bugs) {
if (element.getName().equals("cfcomponent")) {
final String name = context.getComponentName();
final String hint = element.getAttributeValue("hint");
if (hint == null || hint.length() == 0) {
bugs.add(new BugInfo.BugInfoBuilder().setLine(1).setMessageCode("COMPONENT_HINT_MISSING")
.setSeverity(severity).setFilename(context.getFilename())
.setMessage("Component " + name + " is missing a hint.").build());
if (hint == null || hint.trim().isEmpty()) {
context.addMessage("COMPONENT_HINT_MISSING", context.calcComponentName());
}
}
}

@Override
public void expression(CFScriptStatement expression, Context context, BugList bugs) {
if(expression instanceof CFCompDeclStatement){
final CFCompDeclStatement compDeclStatement = (CFCompDeclStatement) expression;
final CFExpression hintAttribute = CFTool.convertMap(compDeclStatement.getAttributes()).get("hint");
if(hintAttribute == null){
final String _mlText = PrecedingCommentReader.getMultiLine(context, expression.getToken());
final String mlText = _mlText==null?null:_mlText.replaceFirst("^/\\*", "").replaceAll("\\*/$", "").trim();
if(mlText != null && !mlText.isEmpty()){
final Pattern pattern = Pattern.compile(".*\\s*@hint\\s+([\\w,_]+)\\s*.*", Pattern.DOTALL);
final Matcher matcher = pattern.matcher(mlText);
if (matcher.matches()) {
String hintText = matcher.group(1);
if(hintText.trim().isEmpty()){
context.addMessage("COMPONENT_HINT_MISSING", context.calcComponentName());
}
}
}else{
context.addMessage("COMPONENT_HINT_MISSING", context.calcComponentName());
}
}
}
}
Expand Down
38 changes: 33 additions & 5 deletions src/main/java/com/cflint/plugins/core/FunctionHintChecker.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package com.cflint.plugins.core;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.cflint.BugInfo;
import com.cflint.BugList;
import com.cflint.plugins.CFLintScannerAdapter;
import com.cflint.plugins.Context;
import com.cflint.tools.CFTool;
import com.cflint.tools.PrecedingCommentReader;

import cfml.parsing.cfscript.CFExpression;
import cfml.parsing.cfscript.script.CFFuncDeclStatement;
import cfml.parsing.cfscript.script.CFScriptStatement;
import net.htmlparser.jericho.Element;

public class FunctionHintChecker extends CFLintScannerAdapter {
Expand All @@ -15,13 +23,33 @@ public void element(final Element element, final Context context, final BugList
if (element.getName().equals("cffunction")) {
final String name = element.getAttributeValue("name");
final String hint = element.getAttributeValue("hint");
if (hint == null || hint.length() == 0) {
final int begLine = element.getSource().getRow(element.getBegin());
bugs.add(new BugInfo.BugInfoBuilder().setLine(begLine).setMessageCode("FUNCTION_HINT_MISSING")
.setSeverity(severity).setFilename(context.getFilename()).setFunction(context.getFunctionName())
.setMessage("Function " + name + " is missing a hint.").build());
if (hint == null || hint.trim().isEmpty()) {
context.addMessage("FUNCTION_HINT_MISSING", context.getFunctionName());
}
}
}

@Override
public void expression(CFScriptStatement expression, Context context, BugList bugs) {
if(expression instanceof CFFuncDeclStatement){
final CFFuncDeclStatement funcDeclStatement = (CFFuncDeclStatement) expression;
final CFExpression hintAttribute = CFTool.convertMap(funcDeclStatement.getAttributes()).get("hint");
if(hintAttribute == null){
final String _mlText = PrecedingCommentReader.getMultiLine(context, expression.getToken());
final String mlText = _mlText==null?null:_mlText.replaceFirst("^/\\*", "").replaceAll("\\*/$", "").trim();
if(mlText != null && !mlText.isEmpty()){
final Pattern pattern = Pattern.compile(".*\\s*@hint\\s+([\\w,_]+)\\s*.*", Pattern.DOTALL);
final Matcher matcher = pattern.matcher(mlText);
if (matcher.matches()) {
String hintText = matcher.group(1);
if(hintText.trim().isEmpty()){
context.addMessage("FUNCTION_HINT_MISSING", context.getFunctionName());
}
}
}else{
context.addMessage("FUNCTION_HINT_MISSING", context.getFunctionName());
}
}
}
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/cflint/tools/CFTool.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.cflint.tools;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import cfml.parsing.cfscript.CFExpression;
import net.htmlparser.jericho.Element;

public class CFTool {
Expand All @@ -27,4 +31,13 @@ public static Element getElementBefore(Element element, List<Element> elements)
}
return null;
}

public static Map<String, CFExpression> convertMap(Map<? extends CFExpression, CFExpression> map){
Map<String, CFExpression> retval = new HashMap<String, CFExpression>();
for(Entry<? extends CFExpression, CFExpression> entry: map.entrySet()){
retval.put(entry.getKey().toString().toLowerCase(), entry.getValue());
}
return retval;
}

}
26 changes: 26 additions & 0 deletions src/main/java/com/cflint/tools/PrecedingCommentReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.cflint.tools;

import org.antlr.v4.runtime.Token;

import com.cflint.plugins.Context;

import cfml.CFSCRIPTLexer;

public class PrecedingCommentReader {

public static final String CFC_DEFAULT_EXTENSION = ".cfc";
public static final String CFM_DEFAULT_EXTENSION = ".cfm";

public static String getMultiLine(Context context, final Token token) {
Iterable<Token> tokens = context.beforeTokens(token);
for (Token currentTok : tokens) {
if (currentTok.getChannel() == Token.HIDDEN_CHANNEL && currentTok.getType() == CFSCRIPTLexer.ML_COMMENT) {
String mlText = currentTok.getText();
return mlText == null?null:mlText.trim();
} else if (currentTok.getLine() < token.getLine()) {
break;
}
}
return null;
}
}
7 changes: 7 additions & 0 deletions src/main/resources/cflint.definition.json
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@
"message": [
{
"code": "COMPONENT_HINT_MISSING",
"messageText" : "Component ${variable} is missing a hint.",
"severity": "WARNING"
}
],
Expand All @@ -283,6 +284,7 @@
"message": [
{
"code": "FUNCTION_HINT_MISSING",
"messageText" : "Function ${variable} is missing a hint.",
"severity": "INFO"
}
],
Expand All @@ -298,6 +300,11 @@
"code": "ARG_HINT_MISSING",
"severity": "INFO",
"messageText" : "Argument ${variable} is missing a hint."
},
{
"code": "ARG_HINT_MISSING_SCRIPT",
"severity": "INFO",
"messageText" : "Argument ${variable} is missing a hint. Use javadoc style annotations on cfscript functions."
}
],
"parameter": [
Expand Down
Loading

0 comments on commit 1b07e49

Please sign in to comment.