diff --git a/jslint4java-ant/src/main/java/com/googlecode/jslint4java/ant/JSLintErrorList.java b/jslint4java-ant/src/main/java/com/googlecode/jslint4java/ant/JSLintErrorList.java new file mode 100644 index 0000000..cef4cbd --- /dev/null +++ b/jslint4java-ant/src/main/java/com/googlecode/jslint4java/ant/JSLintErrorList.java @@ -0,0 +1,204 @@ +package com.googlecode.jslint4java.ant; + +import java.util.HashMap; +import java.util.Map; + +public class JSLintErrorList { + private Map errors = new HashMap(); + + /* + * Steps to recreate the HashMap from the .js file: + * + * 1. Pull out the "bundle" variable definition from fulljslint.js; paste into text editor + * 2. Replace all the spacing at the start of each line so that each line starts at column 1 + * 3. Ensure any messages that span 2+ lines in the JS are trimmed onto a single line (search for the + character; should be no occurrences) + * 4. Search for: ",\n" (including quotes) and replace with: ");\nerrorss.put(" (with quotes) + * 5. Now search for: : " (colon space quote) and replace with ", " (quote comma space quote) + * 6. You will have to manually 'fix' the first and last message as the search/replace misses these + * 7. We now have to swap around the code and message.... + * 8. Regex search for: (".*"), (".*") and replace with: $2, $1 + * 9. Paste over the code in the constructor below. + * + */ + + public JSLintErrorList() { + errors.put("'{a}' is a function.", "a_function"); + errors.put("'{a}' is a statement label.", "a_label"); + errors.put("'{a}' is not allowed.", "a_not_allowed"); + errors.put("'{a}' is not defined.", "a_not_defined"); + errors.put("'{a}' used out of scope.", "a_scope"); + errors.put("ADsafe violation.", "adsafe"); + errors.put("ADsafe violation: '{a}'.", "adsafe_a"); + errors.put("ADsafe autocomplete violation.", "adsafe_autocomplete"); + errors.put("ADSAFE violation: bad id.", "adsafe_bad_id"); + errors.put("ADsafe violation: Wrap the widget in a div.", "adsafe_div"); + errors.put("ADSAFE: Use the fragment option.", "adsafe_fragment"); + errors.put("ADsafe violation: Missing ADSAFE.go.", "adsafe_go"); + errors.put("Currently, ADsafe does not operate on whole HTML documents. It operates on
fragments and .js files.", "adsafe_html"); + errors.put("ADsafe violation: id does not match.", "adsafe_id"); + errors.put("ADsafe violation: Missing ADSAFE.id or ADSAFE.go.", "adsafe_id_go"); + errors.put("ADsafe lib violation.", "adsafe_lib"); + errors.put("ADsafe: The second argument to lib must be a function.", "adsafe_lib_second"); + errors.put("ADSAFE violation: missing ID_.", "adsafe_missing_id"); + errors.put("ADsafe name violation: '{a}'.", "adsafe_name_a"); + errors.put("ADsafe script placement violation.", "adsafe_placement"); + errors.put("ADsafe violation: An id must have a '{a}' prefix", "adsafe_prefix_a"); + errors.put("ADsafe script violation.", "adsafe_script"); + errors.put("ADsafe unapproved script source.", "adsafe_source"); + errors.put("ADsafe subscript '{a}'.", "adsafe_subscript_a"); + errors.put("ADsafe violation: Disallowed tag '{a}'.", "adsafe_tag"); + errors.put("'{a}' is already defined.", "already_defined"); + errors.put("The '&&' subexpression should be wrapped in parens.", "and"); + errors.put("Do not assign to the exception parameter.", "assign_exception"); + errors.put("Expected an assignment or function call and instead saw an expression.", "assignment_function_expression"); + errors.put("Attribute '{a}' not all lower case.", "attribute_case_a"); + errors.put("Avoid '{a}'.", "avoid_a"); + errors.put("Bad assignment.", "bad_assignment"); + errors.put("Bad hex color '{a}'.", "bad_color_a"); + errors.put("Bad constructor.", "bad_constructor"); + errors.put("Bad entity.", "bad_entity"); + errors.put("Bad HTML string", "bad_html"); + errors.put("Bad id: '{a}'.", "bad_id_a"); + errors.put("Bad for in variable '{a}'.", "bad_in_a"); + errors.put("Bad invocation.", "bad_invocation"); + errors.put("Bad name: '{a}'.", "bad_name_a"); + errors.put("Do not use 'new' for side effects.", "bad_new"); + errors.put("Bad number '{a}'.", "bad_number"); + errors.put("Bad operand.", "bad_operand"); + errors.put("Bad type.", "bad_type"); + errors.put("Bad url string.", "bad_url"); + errors.put("Do not wrap function literals in parens unless they are to be immediately invoked.", "bad_wrap"); + errors.put("Combine this with the previous 'var' statement.", "combine_var"); + errors.put("Expected a conditional expression and instead saw an assignment.", "conditional_assignment"); + errors.put("Confusing use of '{a}'.", "confusing_a"); + errors.put("Confusing regular expression.", "confusing_regexp"); + errors.put("A constructor name '{a}' should start with an uppercase letter.", "constructor_name_a"); + errors.put("Unexpected control character '{a}'.", "control_a"); + errors.put("A css file should begin with @charset 'UTF-8';", "css"); + errors.put("Unexpected dangling '_' in '{a}'.", "dangling_a"); + errors.put("Dangerous comment.", "dangerous_comment"); + errors.put("Only properties should be deleted.", "deleted"); + errors.put("Duplicate '{a}'.", "duplicate_a"); + errors.put("Empty block.", "empty_block"); + errors.put("Empty case.", "empty_case"); + errors.put("Empty class.", "empty_class"); + errors.put("eval is evil.", "evil"); + errors.put("Expected '{a}'.", "expected_a"); + errors.put("Expected '{a}' and instead saw '{b}'.", "expected_a_b"); + errors.put("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", "expected_a_b_from_c_d"); + errors.put("Expected an at-rule, and instead saw @{a}.", "expected_at_a"); + errors.put("Expected '{a}' at column {b}, not column {c}.", "expected_a_at_b_c"); + errors.put("Expected an attribute, and instead saw [{a}].", "expected_attribute_a"); + errors.put("Expected an attribute value and instead saw '{a}'.", "expected_attribute_value_a"); + errors.put("Expected a class, and instead saw .{a}.", "expected_class_a"); + errors.put("Expected a number between 0 and 1 and instead saw '{a}'", "expected_fraction_a"); + errors.put("Expected an id, and instead saw #{a}.", "expected_id_a"); + errors.put("Expected an identifier and instead saw '{a}'.", "expected_identifier_a"); + errors.put("Expected an identifier and instead saw '{a}' (a reserved word).", "expected_identifier_a_reserved"); + errors.put("Expected a linear unit and instead saw '{a}'.", "expected_linear_a"); + errors.put("Expected a lang code, and instead saw :{a}.", "expected_lang_a"); + errors.put("Expected a CSS media type, and instead saw '{a}'.", "expected_media_a"); + errors.put("Expected a name and instead saw '{a}'.", "expected_name_a"); + errors.put("Expected a non-standard style attribute and instead saw '{a}'.", "expected_nonstandard_style_attribute"); + errors.put("Expected a number and instead saw '{a}'.", "expected_number_a"); + errors.put("Expected an operator and instead saw '{a}'.", "expected_operator_a"); + errors.put("Expected a percentage and instead saw '{a}'", "expected_percent_a"); + errors.put("Expected a positive number and instead saw '{a}'", "expected_positive_a"); + errors.put("Expected a pseudo, and instead saw :{a}.", "expected_pseudo_a"); + errors.put("Expected a CSS selector, and instead saw {a}.", "expected_selector_a"); + errors.put("Expected a small number and instead saw '{a}'", "expected_small_a"); + errors.put("Expected exactly one space between '{a}' and '{b}'.", "expected_space_a_b"); + errors.put("Expected a string and instead saw {a}.", "expected_string_a"); + errors.put("Excepted a style attribute, and instead saw '{a}'.", "expected_style_attribute"); + errors.put("Expected a style pattern, and instead saw '{a}'.", "expected_style_pattern"); + errors.put("Expected a tagName, and instead saw {a}.", "expected_tagname_a"); + errors.put("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", "for_if"); + errors.put("Function statements should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.", "function_block"); + errors.put("The Function constructor is eval.", "function_eval"); + errors.put("Don't make functions within a loop.", "function_loop"); + errors.put("Function statements are not invocable. Wrap the whole function invocation in parens.", "function_statement"); + errors.put("Use the function form of \"use strict\".", "function_strict"); + errors.put("get/set are ES5 features.", "get_set"); + errors.put("HTML confusion in regular expression '<{a}'.", "html_confusion_a"); + errors.put("Avoid HTML event handlers.", "html_handlers"); + errors.put("Expected an identifier in an assignment and instead saw a function invocation.", "identifier_function"); + errors.put("Implied eval is evil. Pass a function instead of a string.", "implied_evil"); + errors.put("Unexpected 'in'. Compare with undefined, or use the hasOwnProperty method instead.", "infix_in"); + errors.put("Insecure '{a}'.", "insecure_a"); + errors.put("Use the isNaN function to compare with NaN.", "isNaN"); + errors.put("Label '{a}' on '{b}' statement.", "label_a_b"); + errors.put("lang is deprecated.", "lang"); + errors.put("A leading decimal point can be confused with a dot: '.{a}'.", "leading_decimal_a"); + errors.put("Missing '{a}'.", "missing_a"); + errors.put("Missing '{a}' after '{b}'.", "missing_a_after_b"); + errors.put("Missing option value.", "missing_option"); + errors.put("Missing property name.", "missing_property"); + errors.put("Missing space between '{a}' and '{b}'.", "missing_space_a_b"); + errors.put("Missing url.", "missing_url"); + errors.put("Missing \"use strict\" statement.", "missing_use_strict"); + errors.put("Mixed spaces and tabs.", "mixed"); + errors.put("Move the invocation into the parens that contain the function.", "move_invocation"); + errors.put("Move 'var' declarations to the top of the function.", "move_var"); + errors.put("Missing name in function statement.", "name_function"); + errors.put("Nested comment.", "nested_comment"); + errors.put("Nested not.", "not"); + errors.put("Do not use {a} as a constructor.", "not_a_constructor"); + errors.put("'{a}' has not been fully defined yet.", "not_a_defined"); + errors.put("'{a}' is not a function.", "not_a_function"); + errors.put("'{a}' is not a label.", "not_a_label"); + errors.put("'{a}' is out of scope.", "not_a_scope"); + errors.put("'{a}' should not be greater than '{b}'.", "not_greater"); + errors.put("Unexpected parameter '{a}' in get {b} function.", "parameter_a_get_b"); + errors.put("Expected parameter (value) in set {a} function.", "parameter_set_a"); + errors.put("Missing radix parameter.", "radix"); + errors.put("Read only.", "read_only"); + errors.put("Redefinition of '{a}'.", "redefinition_a"); + errors.put("Reserved name '{a}'.", "reserved_a"); + errors.put("{a} ({b}% scanned).", "scanned_a_b"); + errors.put("A regular expression literal can be confused with '/='.", "slash_equal"); + errors.put("Expected to see a statement and instead saw a block.", "statement_block"); + errors.put("Stopping. ", "stopping"); + errors.put("Strange loop.", "strange_loop"); + errors.put("Strict violation.", "strict"); + errors.put("['{a}'] is better written in dot notation.", "subscript"); + errors.put("A '<{a}>' must be within '<{b}>'.", "tag_a_in_b"); + errors.put("Line too long.", "too_long"); + errors.put("Too many errors.", "too_many"); + errors.put("A trailing decimal point can be confused with a dot: '.{a}'.", "trailing_decimal_a"); + errors.put("type is unnecessary.", "type"); + errors.put("Unclosed string.", "unclosed"); + errors.put("Unclosed comment.", "unclosed_comment"); + errors.put("Unclosed regular expression.", "unclosed_regexp"); + errors.put("Unescaped '{a}'.", "unescaped_a"); + errors.put("Unexpected '{a}'.", "unexpected_a"); + errors.put("Unexpected character '{a}' in {b}.", "unexpected_char_a_b"); + errors.put("Unexpected comment.", "unexpected_comment"); + errors.put("Unexpected property '{a}'.", "unexpected_member_a"); + errors.put("Unexpected space between '{a}' and '{b}'.", "unexpected_space_a_b"); + errors.put("It is not necessary to initialize '{a}' to 'undefined'.", "unnecessary_initialize"); + errors.put("Unnecessary \"use strict\".", "unnecessary_use"); + errors.put("Unreachable '{a}' after '{b}'.", "unreachable_a_b"); + errors.put("Unrecognized style attribute '{a}'.", "unrecognized_style_attribute_a"); + errors.put("Unrecognized tag '<{a}>'.", "unrecognized_tag_a"); + errors.put("Unsafe character.", "unsafe"); + errors.put("JavaScript URL.", "url"); + errors.put("Use the array literal notation [].", "use_array"); + errors.put("Spaces are hard to count. Use {{a}}.", "use_braces"); + errors.put("Use the object literal notation {}.", "use_object"); + errors.put("'{a}' was used before it was defined.", "used_before_a"); + errors.put("Variable {a} was not declared correctly.", "var_a_not"); + errors.put("Weird assignment.", "weird_assignment"); + errors.put("Weird condition.", "weird_condition"); + errors.put("Weird construction. Delete 'new'.", "weird_new"); + errors.put("Weird program.", "weird_program"); + errors.put("Weird relation.", "weird_relation"); + errors.put("Weird ternary.", "weird_ternary"); + errors.put("Wrap an immediate function invocation in parentheses to assist the reader in understanding that the expression is the result of a function, and not the function itself.", "wrap_immediate"); + errors.put("Wrap the /regexp/ literal in parens to disambiguate the slash operator.", "wrap_regexp"); + errors.put("document.write can be a form of eval.", "write_is_wrong"); + } + + public String getErrorCodeForRawMessage(String rawMessage) { + return errors.get(rawMessage); + } +} \ No newline at end of file diff --git a/jslint4java-ant/src/main/java/com/googlecode/jslint4java/ant/JSLintResultFilterer.java b/jslint4java-ant/src/main/java/com/googlecode/jslint4java/ant/JSLintResultFilterer.java new file mode 100644 index 0000000..e579464 --- /dev/null +++ b/jslint4java-ant/src/main/java/com/googlecode/jslint4java/ant/JSLintResultFilterer.java @@ -0,0 +1,40 @@ +package com.googlecode.jslint4java.ant; + +import java.util.Iterator; + +import com.googlecode.jslint4java.Issue; +import com.googlecode.jslint4java.JSLintResult; + +public class JSLintResultFilterer { + + private JSLintErrorList jsLintErrors = new JSLintErrorList(); + + public JSLintResult filterResults(JSLintResult result, String[] filters) { + Iterator issues = result.getIssues().iterator(); + + while(issues.hasNext()) { + Issue issue = issues.next(); + if (matchesFilter(issue, filters)) { + issues.remove(); + } + } + return result; + } + + private boolean matchesFilter(Issue issue, String[] filters) { + for(String filter : filters) { + String rawError = jsLintErrors.getErrorCodeForRawMessage(issue.getRaw()); + + if (filter.equals("ignore_triple_equals")) { + if (issue.getReason().contains("===") || issue.getReason().contains("!==")) { + return true; + } + } + else if (filter.equals(rawError)) { + return true; + } + } + return false; + } + +} diff --git a/jslint4java-ant/src/main/java/com/googlecode/jslint4java/ant/JSLintTask.java b/jslint4java-ant/src/main/java/com/googlecode/jslint4java/ant/JSLintTask.java index 0ee6cd0..7dcb4a2 100644 --- a/jslint4java-ant/src/main/java/com/googlecode/jslint4java/ant/JSLintTask.java +++ b/jslint4java-ant/src/main/java/com/googlecode/jslint4java/ant/JSLintTask.java @@ -18,6 +18,7 @@ import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.types.LogLevel; import org.apache.tools.ant.types.Resource; import org.apache.tools.ant.types.ResourceCollection; import org.apache.tools.ant.types.resources.Union; @@ -88,6 +89,12 @@ public class JSLintTask extends Task { private final Map options = new HashMap(); private PredefElement predef = null; + private String[] filters = new String[0]; + + public void setFilter(String filterString) { + filterString = filterString.replace(" ", ""); + this.filters = filterString.split(","); + } /** * Check the contents of this {@link ResourceCollection}. @@ -148,7 +155,7 @@ public void execute() throws BuildException { // issue 53: this isn't a fail, just a notice. log(NO_FILES_TO_LINT); } - + JSLint lint = makeLint(); applyOptions(lint); @@ -216,6 +223,9 @@ private int lintStream(JSLint lint, Resource resource) throws UnsupportedEncodin String name = resource.toString(); JSLintResult result = lint.lint(name, new BufferedReader(new InputStreamReader(stream, encoding))); + + result = new JSLintResultFilterer().filterResults(result, this.filters); + log("Found " + result.getIssues().size() + " issues in " + name, Project.MSG_VERBOSE); for (ResultFormatter rf : formatters) { rf.output(result); diff --git a/jslint4java-ant/src/test/java/com/googlecode/jslint4java/ant/JSLIntResultFiltererTest.java b/jslint4java-ant/src/test/java/com/googlecode/jslint4java/ant/JSLIntResultFiltererTest.java new file mode 100644 index 0000000..b795791 --- /dev/null +++ b/jslint4java-ant/src/test/java/com/googlecode/jslint4java/ant/JSLIntResultFiltererTest.java @@ -0,0 +1,94 @@ +package com.googlecode.jslint4java.ant; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +import com.googlecode.jslint4java.Issue; +import com.googlecode.jslint4java.Issue.IssueBuilder; +import com.googlecode.jslint4java.JSLintResult; +import com.googlecode.jslint4java.JSLintResult.ResultBuilder; + +public class JSLIntResultFiltererTest { + private JSLintResultFilterer filterer; + + // test data + private Issue weirdCondition = new IssueBuilder("systemID", 1, 1, "Weird condition.").raw("Weird condition.").build(); + private Issue strangeLoop = new IssueBuilder("systemID", 1, 1, "Strange Loop.").raw("Strange loop.").build(); + private Issue nonMatchingError = new IssueBuilder("systemID", 1, 1, "nonMatchingError.").raw("nonMatchingError").build(); + private Issue tripleEquals = new IssueBuilder("systemID", 1, 1, "Expected '===' and instead saw '=='.").raw("Expected '{a}' and instead saw '{b}'.").build(); + private Issue tripleNotEquals = new IssueBuilder("systemID", 1, 1, "Expected '!==' and instead saw '!='.").raw("Expected '{a}' and instead saw '{b}'.").build(); + + @Before + public void setUp() { + filterer = new JSLintResultFilterer(); + } + + @Test + public void filterResults_nonMatchingFilter() { + // given + String[] filters = new String[] { "a-filter-that-does-not-exist" }; + JSLintResult result = createResult(); + int numberOfIssues = result.getIssues().size(); + + // when + filterer.filterResults(result, filters); + int filteredIssues = result.getIssues().size(); + + // then + assertEquals(numberOfIssues, filteredIssues); + } + + @Test + public void filterResults_matchingFilter() { + // given + String[] filters = new String[] { "weird_condition" }; + JSLintResult result = createResult(); + + // when + filterer.filterResults(result, filters); + + // then + assertFalse(result.getIssues().contains(weirdCondition)); + } + + @Test + public void filterResults_twoMatchingFilters() { + // given + String[] filters = new String[] { "weird_condition", "strange_loop" }; + JSLintResult result = createResult(); + + // when + filterer.filterResults(result, filters); + + // then + assertFalse(result.getIssues().contains(weirdCondition)); + assertFalse(result.getIssues().contains(strangeLoop)); + } + + @Test + public void filterResults_ignoreTripleEquals() { + // given + String[] filters = new String[] { "ignore_triple_equals", "" }; + JSLintResult result = createResult(); + + // when + filterer.filterResults(result, filters); + + // then + assertFalse(result.getIssues().contains(tripleEquals)); + assertFalse(result.getIssues().contains(tripleNotEquals)); + } + + private JSLintResult createResult() { + ResultBuilder b = new JSLintResult.ResultBuilder("systemID"); + + b.addIssue(weirdCondition); + b.addIssue(nonMatchingError); + b.addIssue(strangeLoop); + b.addIssue(tripleEquals); + b.addIssue(tripleNotEquals); + return b.build(); + } +} diff --git a/jslint4java-ant/src/test/resources/antunit/filters-multiple-with-errors.js b/jslint4java-ant/src/test/resources/antunit/filters-multiple-with-errors.js new file mode 100644 index 0000000..3963d69 --- /dev/null +++ b/jslint4java-ant/src/test/resources/antunit/filters-multiple-with-errors.js @@ -0,0 +1,19 @@ +// weird condition +if (true){ + var a=1; +} +else { + var b=2; +} + +// strange loop +function MyFunc(){ + var x = 0; + for (x=0; x<10; x++){ + //do something + break; + } +} + +// '{a}' is not defined +a; \ No newline at end of file diff --git a/jslint4java-ant/src/test/resources/antunit/filters-multiple.js b/jslint4java-ant/src/test/resources/antunit/filters-multiple.js new file mode 100644 index 0000000..90e2e85 --- /dev/null +++ b/jslint4java-ant/src/test/resources/antunit/filters-multiple.js @@ -0,0 +1,16 @@ +// weird condition +if (true){ + var a=1; +} +else { + var b=2; +} + +// strange loop +function MyFunc(){ + var x = 0; + for (x=0; x<10; x++){ + //do something + break; + } +} \ No newline at end of file diff --git a/jslint4java-ant/src/test/resources/antunit/filters-single.js b/jslint4java-ant/src/test/resources/antunit/filters-single.js new file mode 100644 index 0000000..e6113f0 --- /dev/null +++ b/jslint4java-ant/src/test/resources/antunit/filters-single.js @@ -0,0 +1,7 @@ +// weird condition +if (true){ + var a=1; +} +else { + var b=2; +} \ No newline at end of file diff --git a/jslint4java-ant/src/test/resources/antunit/tests.xml b/jslint4java-ant/src/test/resources/antunit/tests.xml index 1bead4f..c590903 100644 --- a/jslint4java-ant/src/test/resources/antunit/tests.xml +++ b/jslint4java-ant/src/test/resources/antunit/tests.xml @@ -58,7 +58,70 @@ Tests for the JSLint ant task. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jslint4java-docs/src/main/resources/ant.html b/jslint4java-docs/src/main/resources/ant.html index a67af51..3653985 100644 --- a/jslint4java-docs/src/main/resources/ant.html +++ b/jslint4java-docs/src/main/resources/ant.html @@ -51,6 +51,11 @@

Parameters

A comma separated list of options to pass to JSLint. No + + filter + A comma separated list of error messages to filter from the JSLint report. + No +

The valid list of options is defined by the @@ -95,6 +100,20 @@

Parameters

If a parameter is required (as in the case of indent), then it can be supplied by appending an equals and the value. e.g. indent=2.

+ +

Filtering Errors

+ +

+ JSLint can produce some 'errors' that may not be of interest to you - especially if JSLint is run on a project + as part of a continuous integration build. + The list of filters can be found + inside jslint.js - each key to the bundle map is the filter name for that error message. (e.g. a_function, a_label) +

+ +

+ There is a special filter named ignore_triple_equals which will filter errors about === and !==. + The filter expected_a_b will also filter this error, but will also remove warnings about "expected ';' but was ')'" and others. +

Parameters specified as nested elements

@@ -219,7 +238,18 @@

Examples

<formatter type="plain" /> <fileset dir="web/js" includes="*.js" /> </jslint> + +

+ To check a directory for all .js files, and emit all errors besides + "Mixed spaces and tabs" and "Move 'var' declarations to the top of the function" + to the console: +

+
  <jslint filter="mixed, move_var">
+    <formatter type="plain" />
+    <fileset dir="web/js" includes="*.js" />
+  </jslint>
+

To check a directory for all .js files, excluding packed files. Send any problems to a file jslint.out. @@ -252,6 +282,7 @@

Examples

$$ function that Prototype uses, you'd have to say <predef>$$$$</predef>!

+