From 05dabc5e44672cbe669bc947f0682b801c1df412 Mon Sep 17 00:00:00 2001 From: Wolfgang Steiner Date: Sat, 16 Apr 2016 21:05:03 +0200 Subject: [PATCH 01/11] refactored declaration of tslint core rules - loading core rules from property file - added missing rules to tests - added test for unexpected rules in plugin --- .../com/pablissimo/sonar/TsLintSensor.java | 2 +- .../pablissimo/sonar/TsRulesDefinition.java | 194 ++++++-------- .../sonar/TypeScriptRuleProfile.java | 65 +---- .../pablissimo/sonar/model/TsLintRule.java | 15 ++ .../resources/tslint/tslint-rules.properties | 223 ++++++++++++++++ .../sonar/TsRulesDefinitionTest.java | 250 ++++++++++++++---- .../sonar/TypeScriptRuleProfileTest.java | 130 ++++----- 7 files changed, 593 insertions(+), 286 deletions(-) create mode 100644 src/main/java/com/pablissimo/sonar/model/TsLintRule.java create mode 100644 src/main/resources/tslint/tslint-rules.properties diff --git a/src/main/java/com/pablissimo/sonar/TsLintSensor.java b/src/main/java/com/pablissimo/sonar/TsLintSensor.java index de7d8e5..fd79116 100644 --- a/src/main/java/com/pablissimo/sonar/TsLintSensor.java +++ b/src/main/java/com/pablissimo/sonar/TsLintSensor.java @@ -108,7 +108,7 @@ else if (pathToTsLintConfig == null) { // fall back to the generic 'tslint-issue' rule String ruleName = issue.getRuleName(); if (!ruleNames.contains(ruleName)) { - ruleName = TsRulesDefinition.RULE_TSLINT_ISSUE; + ruleName = TsRulesDefinition.TSLINT_UNKNOWN_RULE.key; } issuable.addIssue diff --git a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java index 28605fb..197fd95 100644 --- a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java +++ b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java @@ -1,137 +1,91 @@ package com.pablissimo.sonar; +import com.pablissimo.sonar.model.TsLintRule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rule.Severity; import org.sonar.api.server.rule.RulesDefinition; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Properties; + public class TsRulesDefinition implements RulesDefinition { + private static final Logger LOG = LoggerFactory.getLogger(TsRulesDefinition.class); + public static final String REPOSITORY_NAME = "tslint"; - public static final String RULE_TSLINT_ISSUE = "tslint-issue"; - - public static final String RULE_ALIGN = "align"; - public static final String RULE_BAN = "ban"; - public static final String RULE_CLASS_NAME = "class-name"; - public static final String RULE_COMMENT_FORMAT = "comment-format"; - public static final String RULE_CURLY = "curly"; - public static final String RULE_EOFLINE = "eofline"; - public static final String RULE_FORIN = "forin"; - public static final String RULE_INDENT = "indent"; - public static final String RULE_INTERFACE_NAME = "interface-name"; - public static final String RULE_JSDOC_FORMAT = "jsdoc-format"; - public static final String RULE_LABEL_POSITION = "label-position"; - public static final String RULE_LABEL_UNDEFINED = "label-undefined"; - public static final String RULE_MAX_LINE_LENGTH = "max-line-length"; - public static final String RULE_MEMBER_ACCESS = "member-access"; - public static final String RULE_MEMBER_ORDERING = "member-ordering"; - public static final String RULE_NO_ANY = "no-any"; - public static final String RULE_NO_ARG = "no-arg"; - public static final String RULE_NO_BITWISE = "no-bitwise"; - public static final String RULE_NO_CONDITIONAL_ASSIGNMENT = "no-conditional-assignment"; - public static final String RULE_NO_CONSECUTIVE_BLANK_LINES = "no-consecutive-blank-lines"; - public static final String RULE_NO_CONSOLE = "no-console"; - public static final String RULE_NO_CONSTRUCT = "no-construct"; - public static final String RULE_NO_CONSTRUCTOR_VARS = "no-constructor-vars"; - public static final String RULE_NO_DEBUGGER = "no-debugger"; - public static final String RULE_NO_DUPLICATE_KEY = "no-duplicate-key"; - public static final String RULE_NO_DUPLICATE_VARIABLE = "no-duplicate-variable"; - public static final String RULE_NO_EMPTY = "no-empty"; - public static final String RULE_NO_EVAL = "no-eval"; - public static final String RULE_NO_INFERRABLE_TYPES = "no-inferrable-types"; - public static final String RULE_NO_INTERNAL_MODULE = "no-internal-module"; - public static final String RULE_NO_NULL_KEYWORD = "no-null-keyword"; - public static final String RULE_NO_REQUIRE_IMPORTS = "no-require-imports"; - public static final String RULE_NO_SHADOWED_VARIABLE = "no-shadowed-variable"; - public static final String RULE_NO_STRING_LITERAL = "no-string-literal"; - public static final String RULE_NO_SWITCH_CASE_FALL_THROUGH = "no-switch-case-fall-through"; - public static final String RULE_NO_TRAILING_WHITESPACE = "no-trailing-whitespace"; - public static final String RULE_NO_UNREACHABLE = "no-unreachable"; - public static final String RULE_NO_UNUSED_EXPRESSION = "no-unused-expression"; - public static final String RULE_NO_UNUSED_VARIABLE = "no-unused-variable"; - public static final String RULE_NO_USE_BEFORE_DECLARE = "no-use-before-declare"; - public static final String RULE_NO_VAR_KEYWORD = "no-var-keyword"; - public static final String RULE_NO_VAR_REQUIRES = "no-var-requires"; - public static final String RULE_OBJECT_LITERAL_SORT_KEYS = "object-literal-sort-keys"; - public static final String RULE_ONE_LINE = "one-line"; - public static final String RULE_QUOTEMARK = "quotemark"; - public static final String RULE_RADIX = "radix"; - public static final String RULE_SEMICOLON = "semicolon"; - public static final String RULE_SWITCH_DEFAULT = "switch-default"; - public static final String RULE_TRAILING_COMMA = "trailing-comma"; - public static final String RULE_TRIPLE_EQUALS = "triple-equals"; - public static final String RULE_TYPEDEF = "typedef"; - public static final String RULE_TYPEDEF_WHITESPACE = "typedef-whitespace"; - public static final String RULE_USE_STRICT = "use-strict"; - public static final String RULE_VARIABLE_NAME = "variable-name"; - public static final String RULE_WHITESPACE = "whitespace"; + public static TsLintRule TSLINT_UNKNOWN_RULE = new TsLintRule("tslint-issue", Severity.MAJOR, "tslint issues that are not yet known to the plugin", "HTML description to follow"); + + public static List TSLINT_CORE_RULES = new ArrayList<>(); + + static + { + if (TSLINT_CORE_RULES.size() == 0) { + InputStream tslint_builtin_rules = TsRulesDefinition.class.getResourceAsStream("/tslint/tslint-rules.properties"); + + Properties properties = new Properties(); + try { + properties.load(tslint_builtin_rules); + } catch (IOException e) { + LOG.error("Error while loading tslint.rules ... " + e.getMessage()); + } + + for(String prop_key : properties.stringPropertyNames()) { + + if (prop_key.contains(".")) + continue; + + String rule_enabled = properties.getProperty(prop_key); + + if (!rule_enabled.equals("true")) + continue; + + String rule_id = prop_key; + String rule_severity = properties.getProperty(prop_key + ".severity", Severity.defaultSeverity()); + String rule_name = properties.getProperty(prop_key + ".name", "Unnamed TsLint rule"); + String rule_description = properties.getProperty(prop_key + ".description", "HTML description to follow"); + + TSLINT_CORE_RULES.add(new TsLintRule( + rule_id, + rule_severity, + rule_name, + rule_description + )); + } + + TSLINT_CORE_RULES.sort(new Comparator() { + @Override + public int compare(TsLintRule o1, TsLintRule o2) { + return o1.key.compareTo(o2.key); + } + }); + } + } + + private void applyRule(NewRepository repository, TsLintRule rule) { + repository + .createRule(rule.key) + .setName(rule.name) + .setSeverity(rule.severity) + .setHtmlDescription(rule.html_description) + .setStatus(RuleStatus.READY); + } public void define(Context context) { NewRepository repository = context .createRepository(REPOSITORY_NAME, TypeScriptLanguage.LANGUAGE_KEY) - .setName("TsLint Analyser"); - - repository.createRule(RULE_TSLINT_ISSUE).setName("tslint issues that are not yet known to the plugin").setSeverity(Severity.MAJOR); - - repository.createRule(RULE_ALIGN).setName("enforces vertical alignment of parameters, arguments and statements").setSeverity(Severity.MINOR); - repository.createRule(RULE_BAN).setName("Use of this method is banned by current configuration").setSeverity(Severity.CRITICAL); - repository.createRule(RULE_CLASS_NAME).setName("Name must use PascalCase").setSeverity(Severity.MAJOR); - repository.createRule(RULE_COMMENT_FORMAT).setName("Comments must be correctly formatted").setSeverity(Severity.MINOR); - repository.createRule(RULE_CURLY).setName("enforces braces for if/for/do/while statements").setSeverity(Severity.MAJOR); - repository.createRule(RULE_EOFLINE).setName("enforces the file to end with a newline").setSeverity(Severity.MINOR); - repository.createRule(RULE_FORIN).setName("enforces a for ... in statement to be filtered with an if statement").setSeverity(Severity.MAJOR); - repository.createRule(RULE_INDENT).setName("enforces consistent indentation with tabs or spaces").setSeverity(Severity.MINOR); - repository.createRule(RULE_INTERFACE_NAME).setName("enforces the rule that interface names must begin with a capital I").setSeverity(Severity.MAJOR); - repository.createRule(RULE_JSDOC_FORMAT).setName("enforces basic format rules for jsdoc comments - comments starting with /**").setSeverity(Severity.MAJOR); - repository.createRule(RULE_LABEL_POSITION).setName("enforces labels only on sensible statements").setSeverity(Severity.MAJOR); - repository.createRule(RULE_LABEL_UNDEFINED).setName("checks that labels are defined before usage").setSeverity(Severity.CRITICAL); - repository.createRule(RULE_MAX_LINE_LENGTH).setName("sets the maximum length of a line").setSeverity(Severity.MAJOR); - repository.createRule(RULE_MEMBER_ACCESS).setName("enforces using explicit visibility on class members").setSeverity(Severity.MAJOR); - repository.createRule(RULE_MEMBER_ORDERING).setName("enforces ordering of class members").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_ANY).setName("'any' must not be used as a type decoration").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_ARG).setName("arguments.callee must not be used").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_BITWISE).setName("bitwise operators must not be used").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_CONDITIONAL_ASSIGNMENT).setName("disallows any type of assignment in conditionals - this applies to do-while, for, if and while statements").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_CONSECUTIVE_BLANK_LINES).setName("No more than one blank line should appear in a row").setSeverity(Severity.MINOR); - repository.createRule(RULE_NO_CONSOLE).setName("Specified function must not be called on the global console object").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_CONSTRUCT).setName("Constructors of String, Number and Boolean must not be used").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_CONSTRUCTOR_VARS).setName("Public and private modifiers must not be used on constructor arguments").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_DEBUGGER).setName("Debugger statements are not allowed").setSeverity(Severity.CRITICAL); - repository.createRule(RULE_NO_DUPLICATE_KEY).setName("Duplicate keys must not be specified in object literals").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_DUPLICATE_VARIABLE).setName("Duplicate variable definitions are not allowed").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_EMPTY).setName("Empty blocks are not allowed").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_EVAL).setName("Use of eval is not allowed").setSeverity(Severity.CRITICAL); - repository.createRule(RULE_NO_INFERRABLE_TYPES).setName("disallows explicit type declarations for variables or parameters initialised to a number, string or boolean").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_INTERNAL_MODULE).setName("disallows internal modules - use namespaces instead").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_NULL_KEYWORD).setName("disallows use of the null keyword literal").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_REQUIRE_IMPORTS).setName("disallows invocation of require() - use ES6-style imports instead").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_SHADOWED_VARIABLE).setName("disallows shadowed variable declarations").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_STRING_LITERAL).setName("Object access via string literals is not allowed").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_SWITCH_CASE_FALL_THROUGH).setName("Falling through one case statement to another is not allowed").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_TRAILING_WHITESPACE).setName("Trailing whitespace at the end of lines is not allowed").setSeverity(Severity.MINOR); - repository.createRule(RULE_NO_UNREACHABLE).setName("Unreachable code after break, catch, throw and return statements is not allowed").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_UNUSED_EXPRESSION).setName("Unused expressions (those that aren't assignments or function calls) are not allowed").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_UNUSED_VARIABLE).setName("Unused imports, variables, functions and private class members are not allowed").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_USE_BEFORE_DECLARE).setName("Variable use before declaration is not allowed").setSeverity(Severity.CRITICAL); - repository.createRule(RULE_NO_VAR_KEYWORD).setName("disallows usage of the var keyword - use let or const instead").setSeverity(Severity.MAJOR); - repository.createRule(RULE_NO_VAR_REQUIRES).setName("Require is only allowed in import statements").setSeverity(Severity.MAJOR); - repository.createRule(RULE_OBJECT_LITERAL_SORT_KEYS).setName("checks that keys in object literals are declared in alphabetical order (useful to prevent merge conflicts)").setSeverity(Severity.MINOR); - repository.createRule(RULE_ONE_LINE).setName("No newline is allowed before keyword").setSeverity(Severity.MINOR); - repository.createRule(RULE_QUOTEMARK).setName("Consistent use of single or double quotes is required - a mixture is not allowed").setSeverity(Severity.MAJOR); - repository.createRule(RULE_RADIX).setName("A radix must be specified when calling parseInt").setSeverity(Severity.CRITICAL); - repository.createRule(RULE_SEMICOLON).setName("Statement must end with a semicolon").setSeverity(Severity.MAJOR); - repository.createRule(RULE_SWITCH_DEFAULT).setName("enforces a default case in switch statements").setSeverity(Severity.MAJOR); - repository.createRule(RULE_TRAILING_COMMA).setName("enforces a standard for trailing commas within array and object literals, destructuring assignment and named imports").setSeverity(Severity.MINOR); - repository.createRule(RULE_TRIPLE_EQUALS).setName("== and != must not be used - use === or !== instead").setSeverity(Severity.MAJOR); - repository.createRule(RULE_TYPEDEF).setName("Type definition must be specified").setSeverity(Severity.MAJOR); - repository.createRule(RULE_TYPEDEF_WHITESPACE).setName("Whitespace around type definitions must be correct").setSeverity(Severity.MINOR); - repository.createRule(RULE_USE_STRICT).setName("Strict mode must be used").setSeverity(Severity.CRITICAL); - repository.createRule(RULE_VARIABLE_NAME).setName("Variable names must be either camelCased or UPPER_CASED").setSeverity(Severity.MAJOR); - repository.createRule(RULE_WHITESPACE).setName("Inappropriate whitespace between tokens").setSeverity(Severity.MINOR); - - for (NewRule rule : repository.rules()) { - rule.setHtmlDescription("HTML description to follow"); - rule.setStatus(RuleStatus.READY); + .setName("TsLint Analyzer"); + + applyRule(repository, TSLINT_UNKNOWN_RULE); + + for (TsLintRule builtin : TSLINT_CORE_RULES) { + applyRule(repository, builtin); } repository.done(); diff --git a/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java b/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java index 76168a1..a28861e 100644 --- a/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java +++ b/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java @@ -1,5 +1,8 @@ package com.pablissimo.sonar; +import com.pablissimo.sonar.model.TsLintRule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.profiles.ProfileDefinition; import org.sonar.api.profiles.RulesProfile; import org.sonar.api.rules.Rule; @@ -8,66 +11,16 @@ public class TypeScriptRuleProfile extends ProfileDefinition { public static final String PROFILE_NAME = "tslint"; + private static final Logger LOG = LoggerFactory.getLogger(TypeScriptRuleProfile.class); + @Override public RulesProfile createProfile(ValidationMessages validation) { RulesProfile profile = RulesProfile.create("TsLint", TypeScriptLanguage.LANGUAGE_KEY); - activateRule(profile, TsRulesDefinition.RULE_TSLINT_ISSUE); - activateRule(profile, TsRulesDefinition.RULE_ALIGN); - activateRule(profile, TsRulesDefinition.RULE_BAN); - activateRule(profile, TsRulesDefinition.RULE_CLASS_NAME); - activateRule(profile, TsRulesDefinition.RULE_COMMENT_FORMAT); - activateRule(profile, TsRulesDefinition.RULE_CURLY); - activateRule(profile, TsRulesDefinition.RULE_EOFLINE); - activateRule(profile, TsRulesDefinition.RULE_FORIN); - activateRule(profile, TsRulesDefinition.RULE_INDENT); - activateRule(profile, TsRulesDefinition.RULE_INTERFACE_NAME); - activateRule(profile, TsRulesDefinition.RULE_JSDOC_FORMAT); - activateRule(profile, TsRulesDefinition.RULE_LABEL_POSITION); - activateRule(profile, TsRulesDefinition.RULE_LABEL_UNDEFINED); - activateRule(profile, TsRulesDefinition.RULE_MAX_LINE_LENGTH); - activateRule(profile, TsRulesDefinition.RULE_MEMBER_ACCESS); - activateRule(profile, TsRulesDefinition.RULE_MEMBER_ORDERING); - activateRule(profile, TsRulesDefinition.RULE_NO_ANY); - activateRule(profile, TsRulesDefinition.RULE_NO_ARG); - activateRule(profile, TsRulesDefinition.RULE_NO_BITWISE); - activateRule(profile, TsRulesDefinition.RULE_NO_CONDITIONAL_ASSIGNMENT); - activateRule(profile, TsRulesDefinition.RULE_NO_CONSECUTIVE_BLANK_LINES); - activateRule(profile, TsRulesDefinition.RULE_NO_CONSOLE); - activateRule(profile, TsRulesDefinition.RULE_NO_CONSTRUCT); - activateRule(profile, TsRulesDefinition.RULE_NO_CONSTRUCTOR_VARS); - activateRule(profile, TsRulesDefinition.RULE_NO_DEBUGGER); - activateRule(profile, TsRulesDefinition.RULE_NO_DUPLICATE_KEY); - activateRule(profile, TsRulesDefinition.RULE_NO_DUPLICATE_VARIABLE); - activateRule(profile, TsRulesDefinition.RULE_NO_EMPTY); - activateRule(profile, TsRulesDefinition.RULE_NO_EVAL); - activateRule(profile, TsRulesDefinition.RULE_NO_INFERRABLE_TYPES); - activateRule(profile, TsRulesDefinition.RULE_NO_INTERNAL_MODULE); - activateRule(profile, TsRulesDefinition.RULE_NO_NULL_KEYWORD); - activateRule(profile, TsRulesDefinition.RULE_NO_REQUIRE_IMPORTS); - activateRule(profile, TsRulesDefinition.RULE_NO_SHADOWED_VARIABLE); - activateRule(profile, TsRulesDefinition.RULE_NO_STRING_LITERAL); - activateRule(profile, TsRulesDefinition.RULE_NO_SWITCH_CASE_FALL_THROUGH); - activateRule(profile, TsRulesDefinition.RULE_NO_TRAILING_WHITESPACE); - activateRule(profile, TsRulesDefinition.RULE_NO_UNREACHABLE); - activateRule(profile, TsRulesDefinition.RULE_NO_UNUSED_EXPRESSION); - activateRule(profile, TsRulesDefinition.RULE_NO_UNUSED_VARIABLE); - activateRule(profile, TsRulesDefinition.RULE_NO_USE_BEFORE_DECLARE); - activateRule(profile, TsRulesDefinition.RULE_NO_VAR_KEYWORD); - activateRule(profile, TsRulesDefinition.RULE_NO_VAR_REQUIRES); - activateRule(profile, TsRulesDefinition.RULE_OBJECT_LITERAL_SORT_KEYS); - activateRule(profile, TsRulesDefinition.RULE_ONE_LINE); - activateRule(profile, TsRulesDefinition.RULE_QUOTEMARK); - activateRule(profile, TsRulesDefinition.RULE_RADIX); - activateRule(profile, TsRulesDefinition.RULE_SEMICOLON); - activateRule(profile, TsRulesDefinition.RULE_SWITCH_DEFAULT); - activateRule(profile, TsRulesDefinition.RULE_TRAILING_COMMA); - activateRule(profile, TsRulesDefinition.RULE_TRIPLE_EQUALS); - activateRule(profile, TsRulesDefinition.RULE_TYPEDEF); - activateRule(profile, TsRulesDefinition.RULE_TYPEDEF_WHITESPACE); - activateRule(profile, TsRulesDefinition.RULE_USE_STRICT); - activateRule(profile, TsRulesDefinition.RULE_VARIABLE_NAME); - activateRule(profile, TsRulesDefinition.RULE_WHITESPACE); + for (TsLintRule core_rule : TsRulesDefinition.TSLINT_CORE_RULES) + activateRule(profile, core_rule.key); + + activateRule(profile, TsRulesDefinition.TSLINT_UNKNOWN_RULE.key); return profile; } diff --git a/src/main/java/com/pablissimo/sonar/model/TsLintRule.java b/src/main/java/com/pablissimo/sonar/model/TsLintRule.java new file mode 100644 index 0000000..0f85420 --- /dev/null +++ b/src/main/java/com/pablissimo/sonar/model/TsLintRule.java @@ -0,0 +1,15 @@ +package com.pablissimo.sonar.model; + +public class TsLintRule { + public final String key; + public final String name; + public final String severity; + public final String html_description; + + public TsLintRule(String key, String severity, String name, String html_description) { + this.key = key; + this.severity = severity; + this.name = name; + this.html_description = html_description; + } +} diff --git a/src/main/resources/tslint/tslint-rules.properties b/src/main/resources/tslint/tslint-rules.properties new file mode 100644 index 0000000..54c60a0 --- /dev/null +++ b/src/main/resources/tslint/tslint-rules.properties @@ -0,0 +1,223 @@ +align=true +align.name=enforces vertical alignment of parameters, arguments and statements +align.severity=MINOR + +ban=true +ban.name=Use of this method is banned by current configuration +ban.severity=CRITICAL + +class-name=true +class-name.name=Name must use PascalCase +class-name.severity=MAJOR + +comment-format=true +comment-format.name=Comments must be correctly formatted +comment-format.severity=MINOR + +curly=true +curly.name=enforces braces for if/for/do/while statements +curly.severity=MAJOR + +eofline=true +eofline.name=enforces the file to end with a newline +eofline.severity=MINOR + +forin=true +forin.name=enforces a for ... in statement to be filtered with an if statement +forin.severity=MAJOR + +indent=true +indent.name=enforces consistent indentation with tabs or spaces +indent.severity=MINOR + +interface-name=true +interface-name.name=enforces the rule that interface names must begin with a capital I +interface-name.severity=MAJOR + +jsdoc-format=true +jsdoc-format.name=enforces basic format rules for jsdoc comments - comments starting with /** +jsdoc-format.severity=MAJOR + +label-position=true +label-position.name=enforces labels only on sensible statements +label-position.severity=MAJOR + +label-undefined=true +label-undefined.name=checks that labels are defined before usage +label-undefined.severity=CRITICAL + +max-line-length=true +max-line-length.name=sets the maximum length of a line +max-line-length.severity=MAJOR + +member-access=true +member-access.name=enforces using explicit visibility on class members +member-access.severity=MAJOR + +member-ordering=true +member-ordering.name=enforces ordering of class members +member-ordering.severity=MAJOR + +no-angle-bracket-type-assertion=true +no-angle-bracket-type-assertion.name=enforces use of the 'as' operator for type assertions +no-angle-bracket-type-assertion.severity=MAJOR + +no-any=true +no-any.name='any' must not be used as a type decoration +no-any.severity=MAJOR + +no-arg=true +no-arg.name=arguments.callee must not be used +no-arg.severity=MAJOR + +no-bitwise=true +no-bitwise.name=bitwise operators must not be used +no-bitwise.severity=MAJOR + +no-conditional-assignment=true +no-conditional-assignment.name=disallows any type of assignment in conditionals - this applies to do-while, for, if and while statements +no-conditional-assignment.severity=MAJOR + +no-consecutive-blank-lines=true +no-consecutive-blank-lines.name=No more than one blank line should appear in a row +no-consecutive-blank-lines.severity=MINOR + +no-console=true +no-console.name=Specified function must not be called on the global console object +no-console.severity=MAJOR + +no-construct=true +no-construct.name=Constructors of String, Number and Boolean must not be used +no-construct.severity=MAJOR + +no-constructor-vars=true +no-constructor-vars.name=Public and private modifiers must not be used on constructor arguments +no-constructor-vars.severity=MAJOR + +no-debugger=true +no-debugger.name=Debugger statements are not allowed +no-debugger.severity=CRITICAL + +no-duplicate-key=true +no-duplicate-key.name=Duplicate keys must not be specified in object literals +no-duplicate-key.severity=MAJOR + +no-duplicate-variable=true +no-duplicate-variable.name=Duplicate variable definitions are not allowed +no-duplicate-variable.severity=MAJOR + +no-empty=true +no-empty.name=Empty blocks are not allowed +no-empty.severity=MAJOR + +no-eval=true +no-eval.name=Use of eval is not allowed +no-eval.severity=CRITICAL + +no-inferrable-types=true +no-inferrable-types.name=disallows explicit type declarations for variables or parameters initialised to a number, string or boolean +no-inferrable-types.severity=MAJOR + +no-internal-module=true +no-internal-module.name=disallows internal modules - use namespaces instead +no-internal-module.severity=MAJOR + +no-null-keyword=true +no-null-keyword.name=disallows use of the null keyword literal +no-null-keyword.severity=MAJOR + +no-require-imports=true +no-require-imports.name=disallows invocation of require() - use ES6-style imports instead +no-require-imports.severity=MAJOR + +no-shadowed-variable=true +no-shadowed-variable.name=disallows shadowed variable declarations +no-shadowed-variable.severity=MAJOR + +no-string-literal=true +no-string-literal.name=Object access via string literals is not allowed +no-string-literal.severity=MAJOR + +no-switch-case-fall-through=true +no-switch-case-fall-through.name=Falling through one case statement to another is not allowed +no-switch-case-fall-through.severity=MAJOR + +no-trailing-whitespace=true +no-trailing-whitespace.name=Trailing whitespace at the end of lines is not allowed +no-trailing-whitespace.severity=MINOR + +no-unreachable=true +no-unreachable.name=Unreachable code after break, catch, throw and return statements is not allowed +no-unreachable.severity=MAJOR + +no-unused-expression=true +no-unused-expression.name=Unused expressions (those that aren't assignments or function calls) are not allowed +no-unused-expression.severity=MAJOR + +no-unused-variable=true +no-unused-variable.name=Unused imports, variables, functions and private class members are not allowed +no-unused-variable.severity=MAJOR + +no-use-before-declare=true +no-use-before-declare.name=Variable use before declaration is not allowed +no-use-before-declare.severity=CRITICAL + +no-var-keyword=true +no-var-keyword.name=disallows usage of the var keyword - use let or const instead +no-var-keyword.severity=MAJOR + +no-var-requires=true +no-var-requires.name=Require is only allowed in import statements +no-var-requires.severity=MAJOR + +object-literal-sort-keys=true +object-literal-sort-keys.name=checks that keys in object literals are declared in alphabetical order (useful to prevent merge conflicts) +object-literal-sort-keys.severity=MINOR + +one-line=true +one-line.name=No newline is allowed before keyword +one-line.severity=MINOR + +quotemark=true +quotemark.name=Consistent use of single or double quotes is required - a mixture is not allowed +quotemark.severity=MAJOR + +radix=true +radix.name=A radix must be specified when calling parseInt +radix.severity=CRITICAL + +semicolon=true +semicolon.name=Statement must end with a semicolon +semicolon.severity=MAJOR + +switch-default=true +switch-default.name=enforces a default case in switch statements +switch-default.severity=MAJOR + +trailing-comma=true +trailing-comma.name=enforces a standard for trailing commas within array and object literals, destructuring assignment and named imports +trailing-comma.severity=MINOR + +triple-equals=true +triple-equals.name=== and != must not be used - use === or !== instead +triple-equals.severity=MAJOR + +typedef=true +typedef.name=Type definition must be specified +typedef.severity=MAJOR + +typedef-whitespace=true +typedef-whitespace.name=Whitespace around type definitions must be correct +typedef-whitespace.severity=MINOR + +use-strict=true +use-strict.name=Strict mode must be used +use-strict.severity=CRITICAL + +variable-name=true +variable-name.name=Variable names must be either camelCased or UPPER_CASED +variable-name.severity=MAJOR + +whitespace=true +whitespace.name=Inappropriate whitespace between tokens +whitespace.severity=MINOR diff --git a/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java b/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java index 6275c5c..75d64e8 100644 --- a/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java +++ b/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java @@ -34,245 +34,395 @@ public void CreatesRepository() { verify(context).createRepository(eq(TsRulesDefinition.REPOSITORY_NAME), eq(TypeScriptLanguage.LANGUAGE_KEY)); } + @Test + public void ConfiguresAlignRule() { + Rule rule = getRule("align"); + assertNotNull(rule); + assertEquals(Severity.MINOR, rule.severity()); + } + @Test public void ConfiguresBanRule() { - Rule rule = getRule(TsRulesDefinition.RULE_BAN); + Rule rule = getRule("ban"); + assertNotNull(rule); assertEquals(Severity.CRITICAL, rule.severity()); } @Test public void ConfiguresClassNameRule() { - Rule rule = getRule(TsRulesDefinition.RULE_CLASS_NAME); + Rule rule = getRule("class-name"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresCommentFormatRule() { - Rule rule = getRule(TsRulesDefinition.RULE_COMMENT_FORMAT); - assertEquals(Severity.MINOR, rule.severity()); } + Rule rule = getRule("comment-format"); + assertNotNull(rule); + assertEquals(Severity.MINOR, rule.severity()); + } @Test public void ConfiguresCurlyRule() { - Rule rule = getRule(TsRulesDefinition.RULE_CURLY); + Rule rule = getRule("curly"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresEofLineRule() { - Rule rule = getRule(TsRulesDefinition.RULE_EOFLINE); + Rule rule = getRule("eofline"); + assertNotNull(rule); assertEquals(Severity.MINOR, rule.severity()); } @Test public void ConfiguresForInRule() { - Rule rule = getRule(TsRulesDefinition.RULE_FORIN); + Rule rule = getRule("forin"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresIndentRule() { - Rule rule = getRule(TsRulesDefinition.RULE_INDENT); - assertEquals(Severity.MINOR, rule.severity()); } + Rule rule = getRule("indent"); + assertNotNull(rule); + assertEquals(Severity.MINOR, rule.severity()); + } @Test public void ConfiguresInterfaceNameRule() { - Rule rule = getRule(TsRulesDefinition.RULE_INTERFACE_NAME); + Rule rule = getRule("interface-name"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresJsDocFormat() { - Rule rule = getRule(TsRulesDefinition.RULE_JSDOC_FORMAT); + Rule rule = getRule("jsdoc-format"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresLabelPositionRule() { - Rule rule = getRule(TsRulesDefinition.RULE_LABEL_POSITION); + Rule rule = getRule("label-position"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresLabelUndefinedRule() { - Rule rule = getRule(TsRulesDefinition.RULE_LABEL_UNDEFINED); + Rule rule = getRule("label-undefined"); + assertNotNull(rule); assertEquals(Severity.CRITICAL, rule.severity()); } @Test public void ConfiguresMaxLineLengthRule() { - Rule rule = getRule(TsRulesDefinition.RULE_MAX_LINE_LENGTH); - assertEquals(Severity.MAJOR, rule.severity()); } + Rule rule = getRule("max-line-length"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } + + @Test + public void ConfiguresMemberAccessRule() { + Rule rule = getRule("member-access"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } @Test public void ConfiguresMemberOrderingRule() { - Rule rule = getRule(TsRulesDefinition.RULE_MEMBER_ORDERING); - assertEquals(Severity.MAJOR, rule.severity()); } + Rule rule = getRule("member-ordering"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } + + @Test + public void ConfiguresNoAngleBracketTypeAssertionRule() { + Rule rule = getRule("no-angle-bracket-type-assertion"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } @Test public void ConfiguresNoAnyRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_ANY); + Rule rule = getRule("no-any"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresNoArgRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_ARG); + Rule rule = getRule("no-arg"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresNoBitwiseRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_BITWISE); + Rule rule = getRule("no-bitwise"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test - public void ConfiguresNoConsoleRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_CONSOLE); + public void ConfiguresNoConditionalAssignmentRule() { + Rule rule = getRule("no-conditional-assignment"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresNoConsecutiveBlankLinesRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_CONSECUTIVE_BLANK_LINES); + Rule rule = getRule("no-consecutive-blank-lines"); + assertNotNull(rule); assertEquals(Severity.MINOR, rule.severity()); } + @Test + public void ConfiguresNoConsoleRule() { + Rule rule = getRule("no-console"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } + @Test public void ConfiguresNoConstructRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_CONSTRUCT); + Rule rule = getRule("no-construct"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresNoConstructorVarsRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_CONSTRUCTOR_VARS); + Rule rule = getRule("no-constructor-vars"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresNoDebuggerRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_DEBUGGER); + Rule rule = getRule("no-debugger"); + assertNotNull(rule); assertEquals(Severity.CRITICAL, rule.severity()); } @Test public void ConfiguresNoDuplicateKeyRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_DUPLICATE_KEY); + Rule rule = getRule("no-duplicate-key"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresNoDuplicateVariableRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_DUPLICATE_VARIABLE); + Rule rule = getRule("no-duplicate-variable"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresNoEmptyRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_EMPTY); + Rule rule = getRule("no-empty"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresNoEvalRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_EVAL); + Rule rule = getRule("no-eval"); + assertNotNull(rule); assertEquals(Severity.CRITICAL, rule.severity()); } @Test - public void ConfiguresNoStringLiteralRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_STRING_LITERAL); + public void ConfiguresNoInferrableTypesRule() { + Rule rule = getRule("no-inferrable-types"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test - public void ConfiguresNoSwitchFallThroughRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_SWITCH_CASE_FALL_THROUGH); + public void ConfiguresNoInternalModuleRule() { + Rule rule = getRule("no-internal-module"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test - public void ConfiguresNoTrailingCommaRule() { - Rule rule = getRule(TsRulesDefinition.RULE_TRAILING_COMMA); - assertEquals(Severity.MINOR, rule.severity()); + public void ConfiguresNoNullKeywordRule() { + Rule rule = getRule("no-null-keyword"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } + + @Test + public void ConfiguresNoRequireImportsRule() { + Rule rule = getRule("no-require-imports"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } + + @Test + public void ConfiguresNoShadowedVariableRule() { + Rule rule = getRule("no-shadowed-variable"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } + + @Test + public void ConfiguresNoStringLiteralRule() { + Rule rule = getRule("no-string-literal"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } + + @Test + public void ConfiguresNoSwitchCaseFallThroughRule() { + Rule rule = getRule("no-switch-case-fall-through"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresNoTrailingWhitespaceRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_TRAILING_WHITESPACE); + Rule rule = getRule("no-trailing-whitespace"); + assertNotNull(rule); assertEquals(Severity.MINOR, rule.severity()); } @Test public void ConfiguresNoUnreachableRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_UNREACHABLE); + Rule rule = getRule("no-unreachable"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresNoUnusedExpressionRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_UNUSED_EXPRESSION); + Rule rule = getRule("no-unused-expression"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresNoUnusedVariableRule() { - Rule rule = getRule(TsRulesDefinition.RULE_NO_UNUSED_VARIABLE); + Rule rule = getRule("no-unused-variable"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } + @Test + public void ConfiguresNoUseBeforeDeclareRule() { + Rule rule = getRule("no-use-before-declare"); + assertNotNull(rule); + assertEquals(Severity.CRITICAL, rule.severity()); + } + + @Test + public void ConfiguresNoVarKeywordRule() { + Rule rule = getRule("no-var-keyword"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } + + @Test + public void ConfiguresNoVarRequiresRule() { + Rule rule = getRule("no-var-requires"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } + + @Test + public void ConfiguresObjectLiteralSortKeysRule() { + Rule rule = getRule("object-literal-sort-keys"); + assertNotNull(rule); + assertEquals(Severity.MINOR, rule.severity()); + } + @Test public void ConfiguresOneLineRule() { - Rule rule = getRule(TsRulesDefinition.RULE_ONE_LINE); + Rule rule = getRule("one-line"); + assertNotNull(rule); assertEquals(Severity.MINOR, rule.severity()); } @Test public void ConfiguresQuoteMarkRule() { - Rule rule = getRule(TsRulesDefinition.RULE_QUOTEMARK); + Rule rule = getRule("quotemark"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresRadixRule() { - Rule rule = getRule(TsRulesDefinition.RULE_RADIX); + Rule rule = getRule("radix"); + assertNotNull(rule); assertEquals(Severity.CRITICAL, rule.severity()); } @Test public void ConfiguresSemicolonRule() { - Rule rule = getRule(TsRulesDefinition.RULE_SEMICOLON); + Rule rule = getRule("semicolon"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } + + @Test + public void ConfiguresSwitchDefaultRule() { + Rule rule = getRule("switch-default"); + assertNotNull(rule); + assertEquals(Severity.MAJOR, rule.severity()); + } + + @Test + public void ConfiguresTrailingCommaRule() { + Rule rule = getRule("trailing-comma"); + assertNotNull(rule); + assertEquals(Severity.MINOR, rule.severity()); + } + + @Test + public void ConfiguresTripleEqualsCommaRule() { + Rule rule = getRule("triple-equals"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresTypedefRule() { - Rule rule = getRule(TsRulesDefinition.RULE_TYPEDEF); + Rule rule = getRule("typedef"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresTypedefWhitespaceRule() { - Rule rule = getRule(TsRulesDefinition.RULE_TYPEDEF_WHITESPACE); + Rule rule = getRule("typedef-whitespace"); + assertNotNull(rule); assertEquals(Severity.MINOR, rule.severity()); } @Test public void ConfiguresUseStrictRule() { - Rule rule = getRule(TsRulesDefinition.RULE_USE_STRICT); + Rule rule = getRule("use-strict"); + assertNotNull(rule); assertEquals(Severity.CRITICAL, rule.severity()); } @Test public void ConfiguresVariableNameRule() { - Rule rule = getRule(TsRulesDefinition.RULE_VARIABLE_NAME); + Rule rule = getRule("variable-name"); + assertNotNull(rule); assertEquals(Severity.MAJOR, rule.severity()); } @Test public void ConfiguresWhitespaceRule() { - Rule rule = getRule(TsRulesDefinition.RULE_WHITESPACE); + Rule rule = getRule("whitespace"); + assertNotNull(rule); assertEquals(Severity.MINOR, rule.severity()); } diff --git a/src/test/java/com/pablissimo/sonar/TypeScriptRuleProfileTest.java b/src/test/java/com/pablissimo/sonar/TypeScriptRuleProfileTest.java index 9520bf4..ff1d865 100644 --- a/src/test/java/com/pablissimo/sonar/TypeScriptRuleProfileTest.java +++ b/src/test/java/com/pablissimo/sonar/TypeScriptRuleProfileTest.java @@ -4,81 +4,84 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.sonar.api.profiles.RulesProfile; +import org.sonar.api.rules.ActiveRule; import org.sonar.api.utils.ValidationMessages; public class TypeScriptRuleProfileTest { ValidationMessages validationMessages; TypeScriptRuleProfile ruleProfile; - List expectedRuleNames; + HashSet expectedRuleNames; @Before public void setUp() throws Exception { this.validationMessages = ValidationMessages.create(); this.ruleProfile = new TypeScriptRuleProfile(); - this.expectedRuleNames = new ArrayList(Arrays.asList( - TsRulesDefinition.RULE_TSLINT_ISSUE, - TsRulesDefinition.RULE_ALIGN, - TsRulesDefinition.RULE_BAN, - TsRulesDefinition.RULE_CLASS_NAME, - TsRulesDefinition.RULE_COMMENT_FORMAT, - TsRulesDefinition.RULE_CURLY, - TsRulesDefinition.RULE_EOFLINE, - TsRulesDefinition.RULE_FORIN, - TsRulesDefinition.RULE_INDENT, - TsRulesDefinition.RULE_INTERFACE_NAME, - TsRulesDefinition.RULE_JSDOC_FORMAT, - TsRulesDefinition.RULE_LABEL_POSITION, - TsRulesDefinition.RULE_LABEL_UNDEFINED, - TsRulesDefinition.RULE_MAX_LINE_LENGTH, - TsRulesDefinition.RULE_MEMBER_ACCESS, - TsRulesDefinition.RULE_MEMBER_ORDERING, - TsRulesDefinition.RULE_NO_ANY, - TsRulesDefinition.RULE_NO_ARG, - TsRulesDefinition.RULE_NO_BITWISE, - TsRulesDefinition.RULE_NO_CONDITIONAL_ASSIGNMENT, - TsRulesDefinition.RULE_NO_CONSECUTIVE_BLANK_LINES, - TsRulesDefinition.RULE_NO_CONSOLE, - TsRulesDefinition.RULE_NO_CONSTRUCT, - TsRulesDefinition.RULE_NO_CONSTRUCTOR_VARS, - TsRulesDefinition.RULE_NO_DEBUGGER, - TsRulesDefinition.RULE_NO_DUPLICATE_KEY, - TsRulesDefinition.RULE_NO_DUPLICATE_VARIABLE, - TsRulesDefinition.RULE_NO_EMPTY, - TsRulesDefinition.RULE_NO_EVAL, - TsRulesDefinition.RULE_NO_INFERRABLE_TYPES, - TsRulesDefinition.RULE_NO_INTERNAL_MODULE, - TsRulesDefinition.RULE_NO_NULL_KEYWORD, - TsRulesDefinition.RULE_NO_REQUIRE_IMPORTS, - TsRulesDefinition.RULE_NO_SHADOWED_VARIABLE, - TsRulesDefinition.RULE_NO_STRING_LITERAL, - TsRulesDefinition.RULE_NO_SWITCH_CASE_FALL_THROUGH, - TsRulesDefinition.RULE_NO_TRAILING_WHITESPACE, - TsRulesDefinition.RULE_NO_UNREACHABLE, - TsRulesDefinition.RULE_NO_UNUSED_EXPRESSION, - TsRulesDefinition.RULE_NO_UNUSED_VARIABLE, - TsRulesDefinition.RULE_NO_USE_BEFORE_DECLARE, - TsRulesDefinition.RULE_NO_VAR_KEYWORD, - TsRulesDefinition.RULE_NO_VAR_REQUIRES, - TsRulesDefinition.RULE_OBJECT_LITERAL_SORT_KEYS, - TsRulesDefinition.RULE_ONE_LINE, - TsRulesDefinition.RULE_QUOTEMARK, - TsRulesDefinition.RULE_RADIX, - TsRulesDefinition.RULE_SEMICOLON, - TsRulesDefinition.RULE_SWITCH_DEFAULT, - TsRulesDefinition.RULE_TRAILING_COMMA, - TsRulesDefinition.RULE_TRIPLE_EQUALS, - TsRulesDefinition.RULE_TYPEDEF, - TsRulesDefinition.RULE_TYPEDEF_WHITESPACE, - TsRulesDefinition.RULE_USE_STRICT, - TsRulesDefinition.RULE_VARIABLE_NAME, - TsRulesDefinition.RULE_WHITESPACE + this.expectedRuleNames = new HashSet(Arrays.asList( + TsRulesDefinition.TSLINT_UNKNOWN_RULE.key, + "align", + "ban", + "class-name", + "comment-format", + "curly", + "eofline", + "forin", + "indent", + "interface-name", + "jsdoc-format", + "label-position", + "label-undefined", + "max-line-length", + "member-access", + "member-ordering", + "no-angle-bracket-type-assertion", + "no-any", + "no-arg", + "no-bitwise", + "no-conditional-assignment", + "no-consecutive-blank-lines", + "no-console", + "no-construct", + "no-constructor-vars", + "no-debugger", + "no-duplicate-key", + "no-duplicate-variable", + "no-empty", + "no-eval", + "no-inferrable-types", + "no-internal-module", + "no-null-keyword", + "no-require-imports", + "no-shadowed-variable", + "no-string-literal", + "no-switch-case-fall-through", + "no-trailing-whitespace", + "no-unreachable", + "no-unused-expression", + "no-unused-variable", + "no-use-before-declare", + "no-var-keyword", + "no-var-requires", + "object-literal-sort-keys", + "one-line", + "quotemark", + "radix", + "semicolon", + "switch-default", + "trailing-comma", + "triple-equals", + "typedef", + "typedef-whitespace", + "use-strict", + "variable-name", + "whitespace" )); } @@ -91,7 +94,16 @@ public void definesExpectedRules() { RulesProfile profile = this.ruleProfile.createProfile(this.validationMessages); for (String ruleName : this.expectedRuleNames) { - assertNotNull(profile.getActiveRule(TsRulesDefinition.REPOSITORY_NAME, ruleName)); + assertNotNull("Expected rule missing in plugin: " + ruleName, profile.getActiveRule(TsRulesDefinition.REPOSITORY_NAME, ruleName)); + } + } + + @Test + public void definesUnexpectedRules() { + RulesProfile profile = this.ruleProfile.createProfile(this.validationMessages); + + for (ActiveRule rule : profile.getActiveRules()) { + assertTrue("Unexpected rule in plugin: " + rule.getRuleKey(), this.expectedRuleNames.contains(rule.getRuleKey())); } } } From 824a23827d4c16c78edf93b501537ec7fba7530d Mon Sep 17 00:00:00 2001 From: Wolfgang Steiner Date: Sun, 17 Apr 2016 15:03:15 +0200 Subject: [PATCH 02/11] load custom TSLint rules added configuration property to define custom TSLint rules for sonar analysis --- .../com/pablissimo/sonar/TsLintSensor.java | 5 + .../pablissimo/sonar/TsRulesDefinition.java | 137 ++++++++++++------ .../pablissimo/sonar/TypeScriptPlugin.java | 10 ++ .../sonar/TypeScriptRuleProfile.java | 6 +- .../sonar/TsLintCustomRulesTest.java | 79 ++++++++++ .../sonar/TypeScriptPluginTest.java | 10 +- 6 files changed, 196 insertions(+), 51 deletions(-) create mode 100644 src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java diff --git a/src/main/java/com/pablissimo/sonar/TsLintSensor.java b/src/main/java/com/pablissimo/sonar/TsLintSensor.java index fd79116..263fdf1 100644 --- a/src/main/java/com/pablissimo/sonar/TsLintSensor.java +++ b/src/main/java/com/pablissimo/sonar/TsLintSensor.java @@ -80,6 +80,7 @@ else if (pathToTsLintConfig == null) { TsLintExecutor executor = this.getTsLintExecutor(); TsLintParser parser = this.getTsLintParser(); + TsRulesDefinition rules = this.getTsRulesDefinition(); boolean skipTypeDefFiles = settings.getBoolean(TypeScriptPlugin.SETTING_EXCLUDE_TYPE_DEFINITION_FILES); @@ -136,4 +137,8 @@ protected TsLintExecutor getTsLintExecutor() { protected TsLintParser getTsLintParser() { return new TsLintParserImpl(); } + + protected TsRulesDefinition getTsRulesDefinition() { + return new TsRulesDefinition(this.settings); + } } diff --git a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java index 197fd95..60d3648 100644 --- a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java +++ b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java @@ -3,71 +3,104 @@ import com.pablissimo.sonar.model.TsLintRule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.sonar.api.config.Settings; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rule.Severity; import org.sonar.api.server.rule.RulesDefinition; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Properties; +import java.io.*; +import java.nio.charset.Charset; +import java.util.*; public class TsRulesDefinition implements RulesDefinition { private static final Logger LOG = LoggerFactory.getLogger(TsRulesDefinition.class); public static final String REPOSITORY_NAME = "tslint"; - public static TsLintRule TSLINT_UNKNOWN_RULE = new TsLintRule("tslint-issue", Severity.MAJOR, "tslint issues that are not yet known to the plugin", "HTML description to follow"); + private static final String CORE_RULES_CONFIG_RESOURCE_PATH = "/tslint/tslint-rules.properties"; - public static List TSLINT_CORE_RULES = new ArrayList<>(); + /** The SonarQube rule that will contain all unknown TsLint issues. */ + public static final TsLintRule TSLINT_UNKNOWN_RULE = new TsLintRule( + "tslint-issue", + Severity.MAJOR, + "tslint issues that are not yet known to the plugin", + "No description for TsLint rule"); - static - { - if (TSLINT_CORE_RULES.size() == 0) { - InputStream tslint_builtin_rules = TsRulesDefinition.class.getResourceAsStream("/tslint/tslint-rules.properties"); + private List tslint_core_rules = new ArrayList<>(); + private List tslint_custom_rules = new ArrayList<>(); - Properties properties = new Properties(); - try { - properties.load(tslint_builtin_rules); - } catch (IOException e) { - LOG.error("Error while loading tslint.rules ... " + e.getMessage()); - } + private final Settings settings; - for(String prop_key : properties.stringPropertyNames()) { + public TsRulesDefinition() { + this(null); + } - if (prop_key.contains(".")) - continue; + public TsRulesDefinition(Settings settings) { - String rule_enabled = properties.getProperty(prop_key); + this.settings = settings; - if (!rule_enabled.equals("true")) - continue; + loadCoreRules(); + loadCustomRules(); + } - String rule_id = prop_key; - String rule_severity = properties.getProperty(prop_key + ".severity", Severity.defaultSeverity()); - String rule_name = properties.getProperty(prop_key + ".name", "Unnamed TsLint rule"); - String rule_description = properties.getProperty(prop_key + ".description", "HTML description to follow"); + private void loadCoreRules() { + InputStream core_rules_stream = TsRulesDefinition.class.getResourceAsStream(CORE_RULES_CONFIG_RESOURCE_PATH); + loadRules(core_rules_stream, tslint_core_rules); + } - TSLINT_CORE_RULES.add(new TsLintRule( - rule_id, - rule_severity, - rule_name, - rule_description - )); - } + private void loadCustomRules() { + if (this.settings == null) + return; + + String custom_rules_cfg = this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG); - TSLINT_CORE_RULES.sort(new Comparator() { - @Override - public int compare(TsLintRule o1, TsLintRule o2) { - return o1.key.compareTo(o2.key); - } - }); + if (custom_rules_cfg != null) { + InputStream custom_rules_stream = new ByteArrayInputStream(custom_rules_cfg.getBytes(Charset.defaultCharset())); + loadRules(custom_rules_stream, tslint_custom_rules); } } - private void applyRule(NewRepository repository, TsLintRule rule) { + private static void loadRules(InputStream stream, List rules_collection) { + Properties properties = new Properties(); + + try { + properties.load(stream); + } catch (IOException e) { + LOG.error("Error while loading TsLint rules: " + e.getMessage()); + } + + for(String prop_key : properties.stringPropertyNames()) { + + if (prop_key.contains(".")) + continue; + + String rule_enabled = properties.getProperty(prop_key); + + if (!rule_enabled.equals("true")) + continue; + + String rule_id = prop_key; + String rule_severity = properties.getProperty(prop_key + ".severity", Severity.defaultSeverity()); + String rule_name = properties.getProperty(prop_key + ".name", "Unnamed TsLint rule"); + String rule_description = properties.getProperty(prop_key + ".description", "No description for TsLint rule"); + + rules_collection.add(new TsLintRule( + rule_id, + rule_severity, + rule_name, + rule_description + )); + } + + Collections.sort(rules_collection, new Comparator() { + @Override + public int compare(TsLintRule r1, TsLintRule r2) { + return r1.key.compareTo(r2.key); + } + }); + } + + private void createRule(NewRepository repository, TsLintRule rule) { repository .createRule(rule.key) .setName(rule.name) @@ -82,12 +115,26 @@ public void define(Context context) { .createRepository(REPOSITORY_NAME, TypeScriptLanguage.LANGUAGE_KEY) .setName("TsLint Analyzer"); - applyRule(repository, TSLINT_UNKNOWN_RULE); + createRule(repository, TSLINT_UNKNOWN_RULE); - for (TsLintRule builtin : TSLINT_CORE_RULES) { - applyRule(repository, builtin); + // add the TsLint builtin core rules + for (TsLintRule core_rule : tslint_core_rules) { + createRule(repository, core_rule); + } + + // add additional custom TsLint rules + for (TsLintRule custom_rule : tslint_custom_rules) { + createRule(repository, custom_rule); } repository.done(); } + + public List getCoreRules() { + return tslint_core_rules; + } + + public List getCustomRules() { + return tslint_custom_rules; + } } diff --git a/src/main/java/com/pablissimo/sonar/TypeScriptPlugin.java b/src/main/java/com/pablissimo/sonar/TypeScriptPlugin.java index 71a435a..3b99441 100644 --- a/src/main/java/com/pablissimo/sonar/TypeScriptPlugin.java +++ b/src/main/java/com/pablissimo/sonar/TypeScriptPlugin.java @@ -17,6 +17,15 @@ project = false, global = true ), + @Property( + key = TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG, + defaultValue = "", + type = PropertyType.TEXT, + name = "Custom TSLint rules configuration", + description = "Map custom TSLint rules to SonarQube rules & settings", + project = true, + global = true + ), @Property( key = TypeScriptPlugin.SETTING_EXCLUDE_TYPE_DEFINITION_FILES, type = PropertyType.BOOLEAN, @@ -75,6 +84,7 @@ public class TypeScriptPlugin extends SonarPlugin { public static final String SETTING_EXCLUDE_TYPE_DEFINITION_FILES = "sonar.ts.excludetypedefinitionfiles"; public static final String SETTING_FORCE_ZERO_COVERAGE = "sonar.ts.forceZeroCoverage"; public static final String SETTING_TS_LINT_PATH = "sonar.ts.tslintpath"; + public static final String SETTING_TS_LINT_CUSTOM_RULES_CONFIG = "sonar.ts.tslint.customrules"; public static final String SETTING_TS_LINT_CONFIG_PATH = "sonar.ts.tslintconfigpath"; public static final String SETTING_TS_LINT_TIMEOUT = "sonar.ts.tslinttimeout"; public static final String SETTING_TS_LINT_RULES_DIR = "sonar.ts.tslintrulesdir"; diff --git a/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java b/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java index a28861e..6accc3b 100644 --- a/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java +++ b/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java @@ -17,11 +17,13 @@ public class TypeScriptRuleProfile extends ProfileDefinition { public RulesProfile createProfile(ValidationMessages validation) { RulesProfile profile = RulesProfile.create("TsLint", TypeScriptLanguage.LANGUAGE_KEY); - for (TsLintRule core_rule : TsRulesDefinition.TSLINT_CORE_RULES) - activateRule(profile, core_rule.key); + TsRulesDefinition rules = new TsRulesDefinition(); activateRule(profile, TsRulesDefinition.TSLINT_UNKNOWN_RULE.key); + for (TsLintRule core_rule : rules.getCoreRules()) + activateRule(profile, core_rule.key); + return profile; } diff --git a/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java b/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java new file mode 100644 index 0000000..7670e8f --- /dev/null +++ b/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java @@ -0,0 +1,79 @@ +package com.pablissimo.sonar; + +import com.pablissimo.sonar.model.TsLintRule; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.config.Settings; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TsLintCustomRulesTest { + private Settings settings; + private TsRulesDefinition rules_def; + + @Before + public void setUp() throws Exception { + this.settings = mock(Settings.class); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void parsesSingleCustomRules() throws IOException { + when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) + .thenReturn( + "custom-rule-1=true\n" + + "custom-rule-1.name=test rule #1\n" + + "custom-rule-1.severity=MAJOR\n" + + "custom-rule-1.description=#1 description\n" + + "\n" + + "custom-rule-2=true\n" + + "custom-rule-2.name=test rule #2\n" + + "custom-rule-2.severity=MINOR\n" + + "custom-rule-2.description=#2 description\n" + + "\n"); + + this.rules_def = new TsRulesDefinition(this.settings); + + final int num_custom_rules = 2; + + assertEquals(this.rules_def.getCustomRules().size(), num_custom_rules); + + List custom_rules = this.rules_def.getCustomRules(); + + if (custom_rules.size() == num_custom_rules) { + TsLintRule rule_no_1 = custom_rules.get(0); + TsLintRule rule_no_2 = custom_rules.get(1); + + assertEquals(rule_no_1.key, "custom-rule-1"); + assertEquals(rule_no_1.name, "test rule #1"); + assertEquals(rule_no_1.severity, "MAJOR"); + assertEquals(rule_no_1.html_description, "#1 description"); + + assertEquals(rule_no_2.key, "custom-rule-2"); + assertEquals(rule_no_2.name, "test rule #2"); + assertEquals(rule_no_2.severity, "MINOR"); + assertEquals(rule_no_2.html_description, "#2 description"); + } + } + + @Test + public void parsesEmptyCustomRules() throws IOException { + when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) + .thenReturn("#empty config\n"); + + this.rules_def = new TsRulesDefinition(this.settings); + + final int num_custom_rules = 0; + + assertEquals(this.rules_def.getCustomRules().size(), num_custom_rules); + } +} diff --git a/src/test/java/com/pablissimo/sonar/TypeScriptPluginTest.java b/src/test/java/com/pablissimo/sonar/TypeScriptPluginTest.java index 55e1dc1..42c99cb 100644 --- a/src/test/java/com/pablissimo/sonar/TypeScriptPluginTest.java +++ b/src/test/java/com/pablissimo/sonar/TypeScriptPluginTest.java @@ -53,7 +53,7 @@ public void definesExpectedProperties() { Annotation annotation = plugin.getClass().getAnnotations()[0]; Properties propertiesAnnotation = (Properties) annotation; - assertEquals(7, propertiesAnnotation.value().length); + assertEquals(8, propertiesAnnotation.value().length); Property[] properties = propertiesAnnotation.value(); assertNotNull(findPropertyByName(properties, @@ -68,6 +68,8 @@ public void definesExpectedProperties() { TypeScriptPlugin.SETTING_TS_LINT_CONFIG_PATH)); assertNotNull(findPropertyByName(properties, TypeScriptPlugin.SETTING_TS_LINT_TIMEOUT)); + assertNotNull(findPropertyByName(properties, + TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)); assertNotNull(findPropertyByName(properties, TypeScriptPlugin.SETTING_TS_LINT_RULES_DIR)); } @@ -115,13 +117,13 @@ public void forceZeroCoverageSetting_definedAppropriately() { @Test public void tsLintTimeoutSettings_definedAppropriately() { Property property = findPropertyByName(TypeScriptPlugin.SETTING_TS_LINT_TIMEOUT); - + assertEquals(PropertyType.INTEGER, property.type()); assertEquals("60000", property.defaultValue()); assertEquals(true, property.project()); assertEquals(false, property.global()); } - + @Test public void rulesDirSetting_definedAppropriately() { Property property = findPropertyByName(TypeScriptPlugin.SETTING_TS_LINT_RULES_DIR); @@ -131,7 +133,7 @@ public void rulesDirSetting_definedAppropriately() { assertEquals(true, property.project()); assertEquals(false, property.global()); } - + private Property findPropertyByName(String property) { return findPropertyByName(((Properties) plugin.getClass() .getAnnotations()[0]).value(), property); From dac34d9ba9939cbb8a228071a588b7b4dce69290 Mon Sep 17 00:00:00 2001 From: Wolfgang Steiner Date: Sun, 17 Apr 2016 15:50:22 +0200 Subject: [PATCH 03/11] added README documentation about custom TSLint rules - added documentation for the 'sonar.ts.tslint.customrules' property - added a test for disabled TSLint rules in the configuration --- README.md | 15 +++++++++ .../sonar/TsLintCustomRulesTest.java | 33 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/README.md b/README.md index aea0698..ac1f55b 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ The plugin has so far *only been tested on Windows* and it'll be no surprise if KeyDescription sonar.ts.tslintpathMandatoryPath to the installed copy of TsLint to use +sonar.ts.tslint.customrulesOptionalConfiguration to map custom TSLint rules to SonarQube rules & settings @@ -84,10 +85,24 @@ The plugin has so far *only been tested on Windows* and it'll be no surprise if sonar.ts.forceZeroCoverageOptionalForces code coverage percentage to zero when no report is supplied, defaults to false sonar.ts.tslinttimeoutOptionalMax time to wait for TsLint to finish processing a single file (in milliseconds), defaults to 60 seconds sonar.ts.tslintrulesdirOptionalPath to a folder containing custom TsLint rules referenced in tslint.json +sonar.ts.tslint.customrulesOptionalConfiguration to map custom TSLint rules to SonarQube rules & settings sonar.ts.lcov.reportpathOptionalPath to an LCOV code-coverage report to be included in analysis +## TSLint Custom Rules + +To present custom TSLint rules in SonarQube analysis, you can provide a configuration that maps the TSLint rules from your `sonar.ts.tslintrulesdir` +directory to dedicated Sonar rules for analysis. +The configuration for a TSLint Sonar rule consists of a line declaring the TSLint rule id, and some attached properties that are used by Sonar for analysis and reporting. + +For example taking the `no-constant-condition` rule from the [tslint-eslint-rules](https://github.com/buzinas/tslint-eslint-rules) package, a configuration in Sonar could look as follows: + + no-constant-condition=true + no-constant-condition.name=disallow use of constant expressions in conditions + no-constant-condition.severity=MAJOR + no-constant-condition.description=Comparing a literal expression in a condition is usually a typo or development trigger for a specific behavior. + ##Licence MIT diff --git a/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java b/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java index 7670e8f..5a2281f 100644 --- a/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java +++ b/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java @@ -65,6 +65,39 @@ public void parsesSingleCustomRules() throws IOException { } } + @Test + public void disabledCustomRule() throws IOException { + when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) + .thenReturn( + "custom-rule-1=false\n" + + "custom-rule-1.name=test rule #1\n" + + "custom-rule-1.severity=MAJOR\n" + + "custom-rule-1.description=#1 description\n" + + "\n" + + "custom-rule-2=true\n" + + "custom-rule-2.name=test rule #2\n" + + "custom-rule-2.severity=MINOR\n" + + "custom-rule-2.description=#2 description\n" + + "\n"); + + this.rules_def = new TsRulesDefinition(this.settings); + + final int num_custom_rules = 1; + + assertEquals(this.rules_def.getCustomRules().size(), num_custom_rules); + + List custom_rules = this.rules_def.getCustomRules(); + + if (custom_rules.size() == num_custom_rules) { + TsLintRule rule_no_2 = custom_rules.get(0); + + assertEquals(rule_no_2.key, "custom-rule-2"); + assertEquals(rule_no_2.name, "test rule #2"); + assertEquals(rule_no_2.severity, "MINOR"); + assertEquals(rule_no_2.html_description, "#2 description"); + } + } + @Test public void parsesEmptyCustomRules() throws IOException { when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) From d47cd8ddfb25b2e741f67414200b95d198245a28 Mon Sep 17 00:00:00 2001 From: Wolfgang Steiner Date: Sun, 17 Apr 2016 16:27:12 +0200 Subject: [PATCH 04/11] - added some test-cases to increase code-coverage --- .../pablissimo/sonar/TsRulesDefinition.java | 2 +- .../pablissimo/sonar/TsLintSensorTest.java | 22 ++++++++++--- .../sonar/TsRulesDefinitionTest.java | 31 +++++++++++++------ 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java index 60d3648..d1cf2bc 100644 --- a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java +++ b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java @@ -60,7 +60,7 @@ private void loadCustomRules() { } } - private static void loadRules(InputStream stream, List rules_collection) { + public static void loadRules(InputStream stream, List rules_collection) { Properties properties = new Properties(); try { diff --git a/src/test/java/com/pablissimo/sonar/TsLintSensorTest.java b/src/test/java/com/pablissimo/sonar/TsLintSensorTest.java index 973004f..9ddbb28 100644 --- a/src/test/java/com/pablissimo/sonar/TsLintSensorTest.java +++ b/src/test/java/com/pablissimo/sonar/TsLintSensorTest.java @@ -52,6 +52,18 @@ public void setUp() throws Exception { when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_PATH)).thenReturn("/path/to/tslint"); when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CONFIG_PATH)).thenReturn("/path/to/tslint.json"); when(this.settings.getInt(TypeScriptPlugin.SETTING_TS_LINT_TIMEOUT)).thenReturn(45000); + when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) + .thenReturn( + "custom-rule-1=false\n" + + "custom-rule-1.name=test rule #1\n" + + "custom-rule-1.severity=MAJOR\n" + + "custom-rule-1.description=#1 description\n" + + "\n" + + "custom-rule-2=true\n" + + "custom-rule-2.name=test rule #2\n" + + "custom-rule-2.severity=MINOR\n" + + "custom-rule-2.description=#2 description\n" + + "\n"); this.fileSystem = mock(FileSystem.class); this.perspectives = mock(ResourcePerspectives.class); @@ -150,20 +162,20 @@ public void analyse_doesNothingWhenNoConfigPathset() throws IOException { verify(this.issuable, never()).addIssue(any(Issue.class)); } - + @Test public void analyse_callsExecutorWithSuppliedTimeout() throws IOException { this.sensor.analyse(mock(Project.class), mock(SensorContext.class)); - + verify(this.executor, times(1)).execute(any(String.class), any(String.class), any(String.class), any(String.class), eq(45000)); } - + @Test public void analyze_callsExecutorWithAtLeast5000msTimeout() throws IOException { when(this.settings.getInt(TypeScriptPlugin.SETTING_TS_LINT_TIMEOUT)).thenReturn(-500); - + this.sensor.analyse(mock(Project.class), mock(SensorContext.class)); - + verify(this.executor, times(1)).execute(any(String.class), any(String.class), any(String.class), any(String.class), eq(5000)); } } diff --git a/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java b/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java index 75d64e8..5e3d2c5 100644 --- a/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java +++ b/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java @@ -1,21 +1,22 @@ package com.pablissimo.sonar; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -import org.junit.After; +import com.pablissimo.sonar.model.TsLintRule; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.sonar.api.batch.rule.RuleParam; import org.sonar.api.rule.Severity; -import org.sonar.api.server.rule.RuleParamType; import org.sonar.api.server.rule.RulesDefinition; import org.sonar.api.server.rule.RulesDefinition.Context; -import org.sonar.api.server.rule.RulesDefinition.NewRepository; -import org.sonar.api.server.rule.RulesDefinition.NewRule; import org.sonar.api.server.rule.RulesDefinition.Rule; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.*; + public class TsRulesDefinitionTest { TsRulesDefinition definition; Context context; @@ -426,6 +427,18 @@ public void ConfiguresWhitespaceRule() { assertEquals(Severity.MINOR, rule.severity()); } + @Test + public void LoadRulesFromInvalidStream() throws IOException { + List rules = new ArrayList<>(); + InputStream test_stream = new InputStream() { + @Override + public int read() throws IOException { + throw new IOException("Test exception"); + } + }; + TsRulesDefinition.loadRules(test_stream, rules); + } + private Rule getRule(String name) { this.definition.define(context); return this.context.repository(TsRulesDefinition.REPOSITORY_NAME).rule(name); From a6d1d90ccbcebd852383fe16a2338211a204ed67 Mon Sep 17 00:00:00 2001 From: Wolfgang Steiner Date: Sun, 17 Apr 2016 16:56:07 +0200 Subject: [PATCH 05/11] - added test-case to cover the creation of custom rules --- .../sonar/TsRulesDefinitionTest.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java b/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java index 5e3d2c5..29fbdf0 100644 --- a/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java +++ b/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java @@ -3,6 +3,7 @@ import com.pablissimo.sonar.model.TsLintRule; import org.junit.Before; import org.junit.Test; +import org.sonar.api.config.Settings; import org.sonar.api.rule.Severity; import org.sonar.api.server.rule.RulesDefinition; import org.sonar.api.server.rule.RulesDefinition.Context; @@ -18,12 +19,29 @@ import static org.mockito.Mockito.*; public class TsRulesDefinitionTest { + + Settings settings; TsRulesDefinition definition; Context context; @Before public void setUp() throws Exception { - this.definition = new TsRulesDefinition(); + + this.settings = mock(Settings.class); + when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) + .thenReturn( + "custom-rule-1=false\n" + + "custom-rule-1.name=test rule #1\n" + + "custom-rule-1.severity=MAJOR\n" + + "custom-rule-1.description=#1 description\n" + + "\n" + + "custom-rule-2=true\n" + + "custom-rule-2.name=test rule #2\n" + + "custom-rule-2.severity=MINOR\n" + + "custom-rule-2.description=#2 description\n" + + "\n"); + + this.definition = new TsRulesDefinition(this.settings); this.context = new Context(); } @@ -427,6 +445,13 @@ public void ConfiguresWhitespaceRule() { assertEquals(Severity.MINOR, rule.severity()); } + @Test + public void ConfiguresCustomRule() { + Rule rule = getRule("custom-rule-2"); + assertNotNull(rule); + assertEquals(Severity.MINOR, rule.severity()); + } + @Test public void LoadRulesFromInvalidStream() throws IOException { List rules = new ArrayList<>(); From 2194422e5c360d083a48b7529ee4ca04a5d990f7 Mon Sep 17 00:00:00 2001 From: Wolfgang Steiner Date: Sun, 17 Apr 2016 17:32:51 +0200 Subject: [PATCH 06/11] updated README.md for custom rules --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ac1f55b..59c368a 100644 --- a/README.md +++ b/README.md @@ -94,9 +94,11 @@ The plugin has so far *only been tested on Windows* and it'll be no surprise if To present custom TSLint rules in SonarQube analysis, you can provide a configuration that maps the TSLint rules from your `sonar.ts.tslintrulesdir` directory to dedicated Sonar rules for analysis. -The configuration for a TSLint Sonar rule consists of a line declaring the TSLint rule id, and some attached properties that are used by Sonar for analysis and reporting. +The configuration for a TSLint Sonar rule consists of a line declaring the TSLint rule id, a boolean switch to enable or disable the rule if needed +and some attached properties that are used by Sonar for analysis and reporting. -For example taking the `no-constant-condition` rule from the [tslint-eslint-rules](https://github.com/buzinas/tslint-eslint-rules) package, a configuration in Sonar could look as follows: +For example taking the `no-constant-condition` rule from the [tslint-eslint-rules](https://github.com/buzinas/tslint-eslint-rules) package, +a configuration in Sonar could look as follows: no-constant-condition=true no-constant-condition.name=disallow use of constant expressions in conditions From f17b84b12ec5cc964d90c89d105907839186bb32 Mon Sep 17 00:00:00 2001 From: Wolfgang Steiner Date: Sat, 2 Jul 2016 13:15:08 +0200 Subject: [PATCH 07/11] - changed code to camelCase style to be in line with the rest of the plugin --- .../com/pablissimo/sonar/TsLintSensor.java | 24 ++++---- .../pablissimo/sonar/TsRulesDefinition.java | 60 +++++++++---------- .../sonar/TypeScriptRuleProfile.java | 4 +- .../pablissimo/sonar/model/TsLintRule.java | 6 +- .../sonar/TsLintCustomRulesTest.java | 58 +++++++++--------- .../sonar/TsRulesDefinitionTest.java | 4 +- 6 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/main/java/com/pablissimo/sonar/TsLintSensor.java b/src/main/java/com/pablissimo/sonar/TsLintSensor.java index 8824e49..8df4f67 100644 --- a/src/main/java/com/pablissimo/sonar/TsLintSensor.java +++ b/src/main/java/com/pablissimo/sonar/TsLintSensor.java @@ -92,51 +92,51 @@ else if (pathToTsLintConfig == null) { List paths = new ArrayList(); HashMap fileMap = new HashMap(); - + for (File file : fileSystem.files(this.filePredicates.hasLanguage(TypeScriptLanguage.LANGUAGE_KEY))) { if (skipTypeDefFiles && file.getName().toLowerCase().endsWith("." + TypeScriptLanguage.LANGUAGE_DEFINITION_EXTENSION)) { continue; } - + String pathAdjusted = file.getAbsolutePath().replace('\\', '/'); paths.add(pathAdjusted); fileMap.put(pathAdjusted, file); } - + String jsonResult = executor.execute(pathToTsLint, pathToTsLintConfig, rulesDir, paths, tsLintTimeoutMs); - + TsLintIssue[][] issues = parser.parse(jsonResult); - + if (issues == null) { LOG.warn("TsLint returned no result at all"); return; } - + // Each issue bucket will contain info about a single file for (TsLintIssue[] batchIssues : issues) { if (batchIssues == null || batchIssues.length == 0) { continue; } - + String filePath = batchIssues[0].getName(); - + if (!fileMap.containsKey(filePath)) { LOG.warn("TsLint reported issues against a file that wasn't sent to it - will be ignored: " + filePath); continue; } - + File file = fileMap.get(filePath); Resource resource = this.getFileFromIOFile(file, project); Issuable issuable = perspectives.as(Issuable.class, resource); - + for (TsLintIssue issue : batchIssues) { // Make sure the rule we're violating is one we recognise - if not, we'll // fall back to the generic 'tslint-issue' rule String ruleName = issue.getRuleName(); if (!ruleNames.contains(ruleName)) { - ruleName = TsRulesDefinition.RULE_TSLINT_ISSUE; + ruleName = TsRulesDefinition.TSLINT_UNKNOWN_RULE.name; } - + issuable.addIssue ( issuable diff --git a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java index d1cf2bc..9163a3e 100644 --- a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java +++ b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java @@ -26,8 +26,8 @@ public class TsRulesDefinition implements RulesDefinition { "tslint issues that are not yet known to the plugin", "No description for TsLint rule"); - private List tslint_core_rules = new ArrayList<>(); - private List tslint_custom_rules = new ArrayList<>(); + private List tslintCoreRules = new ArrayList<>(); + private List tslintCustomRules = new ArrayList<>(); private final Settings settings; @@ -44,23 +44,23 @@ public TsRulesDefinition(Settings settings) { } private void loadCoreRules() { - InputStream core_rules_stream = TsRulesDefinition.class.getResourceAsStream(CORE_RULES_CONFIG_RESOURCE_PATH); - loadRules(core_rules_stream, tslint_core_rules); + InputStream coreRulesStream = TsRulesDefinition.class.getResourceAsStream(CORE_RULES_CONFIG_RESOURCE_PATH); + loadRules(coreRulesStream, tslintCoreRules); } private void loadCustomRules() { if (this.settings == null) return; - String custom_rules_cfg = this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG); + String customRulesCfg = this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG); - if (custom_rules_cfg != null) { - InputStream custom_rules_stream = new ByteArrayInputStream(custom_rules_cfg.getBytes(Charset.defaultCharset())); - loadRules(custom_rules_stream, tslint_custom_rules); + if (customRulesCfg != null) { + InputStream customRulesStream = new ByteArrayInputStream(customRulesCfg.getBytes(Charset.defaultCharset())); + loadRules(customRulesStream, tslintCustomRules); } } - public static void loadRules(InputStream stream, List rules_collection) { + public static void loadRules(InputStream stream, List rulesCollection) { Properties properties = new Properties(); try { @@ -69,30 +69,30 @@ public static void loadRules(InputStream stream, List rules_collecti LOG.error("Error while loading TsLint rules: " + e.getMessage()); } - for(String prop_key : properties.stringPropertyNames()) { + for(String propKey : properties.stringPropertyNames()) { - if (prop_key.contains(".")) + if (propKey.contains(".")) continue; - String rule_enabled = properties.getProperty(prop_key); + String ruleEnabled = properties.getProperty(propKey); - if (!rule_enabled.equals("true")) + if (!ruleEnabled.equals("true")) continue; - String rule_id = prop_key; - String rule_severity = properties.getProperty(prop_key + ".severity", Severity.defaultSeverity()); - String rule_name = properties.getProperty(prop_key + ".name", "Unnamed TsLint rule"); - String rule_description = properties.getProperty(prop_key + ".description", "No description for TsLint rule"); + String ruleId = propKey; + String ruleSeverity = properties.getProperty(propKey + ".severity", Severity.defaultSeverity()); + String ruleName = properties.getProperty(propKey + ".name", "Unnamed TsLint rule"); + String ruleDescription = properties.getProperty(propKey + ".description", "No description for TsLint rule"); - rules_collection.add(new TsLintRule( - rule_id, - rule_severity, - rule_name, - rule_description + rulesCollection.add(new TsLintRule( + ruleId, + ruleSeverity, + ruleName, + ruleDescription )); } - Collections.sort(rules_collection, new Comparator() { + Collections.sort(rulesCollection, new Comparator() { @Override public int compare(TsLintRule r1, TsLintRule r2) { return r1.key.compareTo(r2.key); @@ -105,7 +105,7 @@ private void createRule(NewRepository repository, TsLintRule rule) { .createRule(rule.key) .setName(rule.name) .setSeverity(rule.severity) - .setHtmlDescription(rule.html_description) + .setHtmlDescription(rule.htmlDescription) .setStatus(RuleStatus.READY); } @@ -118,23 +118,23 @@ public void define(Context context) { createRule(repository, TSLINT_UNKNOWN_RULE); // add the TsLint builtin core rules - for (TsLintRule core_rule : tslint_core_rules) { - createRule(repository, core_rule); + for (TsLintRule coreRule : tslintCoreRules) { + createRule(repository, coreRule); } // add additional custom TsLint rules - for (TsLintRule custom_rule : tslint_custom_rules) { - createRule(repository, custom_rule); + for (TsLintRule customRule : tslintCustomRules) { + createRule(repository, customRule); } repository.done(); } public List getCoreRules() { - return tslint_core_rules; + return tslintCoreRules; } public List getCustomRules() { - return tslint_custom_rules; + return tslintCustomRules; } } diff --git a/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java b/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java index 6accc3b..b4bed5c 100644 --- a/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java +++ b/src/main/java/com/pablissimo/sonar/TypeScriptRuleProfile.java @@ -21,8 +21,8 @@ public RulesProfile createProfile(ValidationMessages validation) { activateRule(profile, TsRulesDefinition.TSLINT_UNKNOWN_RULE.key); - for (TsLintRule core_rule : rules.getCoreRules()) - activateRule(profile, core_rule.key); + for (TsLintRule coreRule : rules.getCoreRules()) + activateRule(profile, coreRule.key); return profile; } diff --git a/src/main/java/com/pablissimo/sonar/model/TsLintRule.java b/src/main/java/com/pablissimo/sonar/model/TsLintRule.java index 0f85420..2311876 100644 --- a/src/main/java/com/pablissimo/sonar/model/TsLintRule.java +++ b/src/main/java/com/pablissimo/sonar/model/TsLintRule.java @@ -4,12 +4,12 @@ public class TsLintRule { public final String key; public final String name; public final String severity; - public final String html_description; + public final String htmlDescription; - public TsLintRule(String key, String severity, String name, String html_description) { + public TsLintRule(String key, String severity, String name, String htmlDescription) { this.key = key; this.severity = severity; this.name = name; - this.html_description = html_description; + this.htmlDescription = htmlDescription; } } diff --git a/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java b/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java index 5a2281f..40f0c50 100644 --- a/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java +++ b/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java @@ -15,7 +15,7 @@ public class TsLintCustomRulesTest { private Settings settings; - private TsRulesDefinition rules_def; + private TsRulesDefinition rulesDef; @Before public void setUp() throws Exception { @@ -41,27 +41,27 @@ public void parsesSingleCustomRules() throws IOException { "custom-rule-2.description=#2 description\n" + "\n"); - this.rules_def = new TsRulesDefinition(this.settings); + this.rulesDef = new TsRulesDefinition(this.settings); - final int num_custom_rules = 2; + final int numCustomRules = 2; - assertEquals(this.rules_def.getCustomRules().size(), num_custom_rules); + assertEquals(this.rulesDef.getCustomRules().size(), numCustomRules); - List custom_rules = this.rules_def.getCustomRules(); + List customRules = this.rulesDef.getCustomRules(); - if (custom_rules.size() == num_custom_rules) { - TsLintRule rule_no_1 = custom_rules.get(0); - TsLintRule rule_no_2 = custom_rules.get(1); + if (customRules.size() == numCustomRules) { + TsLintRule ruleNo1 = customRules.get(0); + TsLintRule ruleNo2 = customRules.get(1); - assertEquals(rule_no_1.key, "custom-rule-1"); - assertEquals(rule_no_1.name, "test rule #1"); - assertEquals(rule_no_1.severity, "MAJOR"); - assertEquals(rule_no_1.html_description, "#1 description"); + assertEquals(ruleNo1.key, "custom-rule-1"); + assertEquals(ruleNo1.name, "test rule #1"); + assertEquals(ruleNo1.severity, "MAJOR"); + assertEquals(ruleNo1.htmlDescription, "#1 description"); - assertEquals(rule_no_2.key, "custom-rule-2"); - assertEquals(rule_no_2.name, "test rule #2"); - assertEquals(rule_no_2.severity, "MINOR"); - assertEquals(rule_no_2.html_description, "#2 description"); + assertEquals(ruleNo2.key, "custom-rule-2"); + assertEquals(ruleNo2.name, "test rule #2"); + assertEquals(ruleNo2.severity, "MINOR"); + assertEquals(ruleNo2.htmlDescription, "#2 description"); } } @@ -80,21 +80,21 @@ public void disabledCustomRule() throws IOException { "custom-rule-2.description=#2 description\n" + "\n"); - this.rules_def = new TsRulesDefinition(this.settings); + this.rulesDef = new TsRulesDefinition(this.settings); - final int num_custom_rules = 1; + final int numCustomRules = 1; - assertEquals(this.rules_def.getCustomRules().size(), num_custom_rules); + assertEquals(this.rulesDef.getCustomRules().size(), numCustomRules); - List custom_rules = this.rules_def.getCustomRules(); + List customRules = this.rulesDef.getCustomRules(); - if (custom_rules.size() == num_custom_rules) { - TsLintRule rule_no_2 = custom_rules.get(0); + if (customRules.size() == numCustomRules) { + TsLintRule ruleNo2 = customRules.get(0); - assertEquals(rule_no_2.key, "custom-rule-2"); - assertEquals(rule_no_2.name, "test rule #2"); - assertEquals(rule_no_2.severity, "MINOR"); - assertEquals(rule_no_2.html_description, "#2 description"); + assertEquals(ruleNo2.key, "custom-rule-2"); + assertEquals(ruleNo2.name, "test rule #2"); + assertEquals(ruleNo2.severity, "MINOR"); + assertEquals(ruleNo2.htmlDescription, "#2 description"); } } @@ -103,10 +103,10 @@ public void parsesEmptyCustomRules() throws IOException { when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) .thenReturn("#empty config\n"); - this.rules_def = new TsRulesDefinition(this.settings); + this.rulesDef = new TsRulesDefinition(this.settings); - final int num_custom_rules = 0; + final int numCustomRules = 0; - assertEquals(this.rules_def.getCustomRules().size(), num_custom_rules); + assertEquals(this.rulesDef.getCustomRules().size(), numCustomRules); } } diff --git a/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java b/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java index 29fbdf0..4677a96 100644 --- a/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java +++ b/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java @@ -455,13 +455,13 @@ public void ConfiguresCustomRule() { @Test public void LoadRulesFromInvalidStream() throws IOException { List rules = new ArrayList<>(); - InputStream test_stream = new InputStream() { + InputStream testStream = new InputStream() { @Override public int read() throws IOException { throw new IOException("Test exception"); } }; - TsRulesDefinition.loadRules(test_stream, rules); + TsRulesDefinition.loadRules(testStream, rules); } private Rule getRule(String name) { From 0a4930109c16a447d765ee3707ecceb701cfec4c Mon Sep 17 00:00:00 2001 From: Wolfgang Steiner Date: Wed, 6 Jul 2016 12:17:17 +0200 Subject: [PATCH 08/11] reworked TsLint-to-Sonar rule mapping - multiple separated rule config collections now supported - added parsing & handling of technical debt properties for rules - added some more test coverage for rule parsing & handling --- .../com/pablissimo/sonar/TsLintSensor.java | 28 +--- .../pablissimo/sonar/TsRulesDefinition.java | 111 +++++++++++--- .../pablissimo/sonar/TypeScriptPlugin.java | 34 ++-- .../pablissimo/sonar/model/TsLintRule.java | 42 ++++- .../sonar/TsLintCustomRulesTest.java | 112 -------------- .../pablissimo/sonar/TsLintSensorTest.java | 84 +++++++++- .../sonar/TsRulesDefinitionTest.java | 145 +++++++++++++++++- .../sonar/TypeScriptPluginTest.java | 25 ++- .../sonar/model/TsLintRuleTest.java | 54 +++++++ 9 files changed, 447 insertions(+), 188 deletions(-) delete mode 100644 src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java create mode 100644 src/test/java/com/pablissimo/sonar/model/TsLintRuleTest.java diff --git a/src/main/java/com/pablissimo/sonar/TsLintSensor.java b/src/main/java/com/pablissimo/sonar/TsLintSensor.java index 8df4f67..96c2e3b 100644 --- a/src/main/java/com/pablissimo/sonar/TsLintSensor.java +++ b/src/main/java/com/pablissimo/sonar/TsLintSensor.java @@ -1,39 +1,24 @@ package com.pablissimo.sonar; -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - +import com.pablissimo.sonar.model.TsLintIssue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.Sensor; import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.fs.FilePredicates; +import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.component.ResourcePerspectives; import org.sonar.api.config.Settings; import org.sonar.api.issue.Issuable; -import org.sonar.api.profiles.RulesProfile; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.ActiveRule; -import org.sonar.api.rules.ActiveRuleParam; import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.rules.RuleQuery; -import org.sonar.api.batch.fs.FilePredicates; -import org.sonar.api.batch.fs.FileSystem; -import com.google.common.base.Charsets; -import com.google.common.base.Throwables; -import com.google.common.io.Files; -import com.google.gson.GsonBuilder; -import com.pablissimo.sonar.model.TsLintConfig; -import com.pablissimo.sonar.model.TsLintIssue; +import java.io.File; +import java.util.*; public class TsLintSensor implements Sensor { public static final String CONFIG_FILENAME = "tslint.json"; @@ -79,7 +64,6 @@ else if (pathToTsLintConfig == null) { TsLintExecutor executor = this.getTsLintExecutor(); TsLintParser parser = this.getTsLintParser(); - TsRulesDefinition rules = this.getTsRulesDefinition(); boolean skipTypeDefFiles = settings.getBoolean(TypeScriptPlugin.SETTING_EXCLUDE_TYPE_DEFINITION_FILES); @@ -134,7 +118,7 @@ else if (pathToTsLintConfig == null) { // fall back to the generic 'tslint-issue' rule String ruleName = issue.getRuleName(); if (!ruleNames.contains(ruleName)) { - ruleName = TsRulesDefinition.TSLINT_UNKNOWN_RULE.name; + ruleName = TsRulesDefinition.TSLINT_UNKNOWN_RULE.key; } issuable.addIssue diff --git a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java index 9163a3e..0c0fae0 100644 --- a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java +++ b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java @@ -6,6 +6,7 @@ import org.sonar.api.config.Settings; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rule.Severity; +import org.sonar.api.server.debt.DebtRemediationFunction; import org.sonar.api.server.rule.RulesDefinition; import java.io.*; @@ -17,6 +18,13 @@ public class TsRulesDefinition implements RulesDefinition { public static final String REPOSITORY_NAME = "tslint"; + public static final String DEFAULT_RULE_NAME = "Unnamed TsLint rule"; + public static final String DEFAULT_RULE_SEVERITY = Severity.defaultSeverity(); + public static final String DEFAULT_RULE_DESCRIPTION = "No description provided for this TsLint rule"; + public static final String DEFAULT_RULE_DEBT_SCALAR = "0min"; + public static final String DEFAULT_RULE_DEBT_OFFSET = "0min"; + public static final String DEFAULT_RULE_DEBT_TYPE = SubCharacteristics.ARCHITECTURE_RELIABILITY; + private static final String CORE_RULES_CONFIG_RESOURCE_PATH = "/tslint/tslint-rules.properties"; /** The SonarQube rule that will contain all unknown TsLint issues. */ @@ -27,7 +35,7 @@ public class TsRulesDefinition implements RulesDefinition { "No description for TsLint rule"); private List tslintCoreRules = new ArrayList<>(); - private List tslintCustomRules = new ArrayList<>(); + private List tslintRules = new ArrayList<>(); private final Settings settings; @@ -52,11 +60,15 @@ private void loadCustomRules() { if (this.settings == null) return; - String customRulesCfg = this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG); + List configKeys = settings.getKeysStartingWith(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS); + + for (String cfgKey : configKeys) { + if (!cfgKey.endsWith("config")) + continue; - if (customRulesCfg != null) { - InputStream customRulesStream = new ByteArrayInputStream(customRulesCfg.getBytes(Charset.defaultCharset())); - loadRules(customRulesStream, tslintCustomRules); + String rulesConfig = settings.getString(cfgKey); + InputStream rulesConfigStream = new ByteArrayInputStream(rulesConfig.getBytes(Charset.defaultCharset())); + loadRules(rulesConfigStream, tslintRules); } } @@ -80,16 +92,44 @@ public static void loadRules(InputStream stream, List rulesCollectio continue; String ruleId = propKey; - String ruleSeverity = properties.getProperty(propKey + ".severity", Severity.defaultSeverity()); - String ruleName = properties.getProperty(propKey + ".name", "Unnamed TsLint rule"); - String ruleDescription = properties.getProperty(propKey + ".description", "No description for TsLint rule"); - - rulesCollection.add(new TsLintRule( - ruleId, - ruleSeverity, - ruleName, - ruleDescription - )); + String ruleName = properties.getProperty(propKey + ".name", DEFAULT_RULE_NAME); + String ruleSeverity = properties.getProperty(propKey + ".severity", DEFAULT_RULE_SEVERITY); + String ruleDescription = properties.getProperty(propKey + ".description", DEFAULT_RULE_DESCRIPTION); + + String debtRemediationFunction = properties.getProperty(propKey + ".debtFunc", null); + String debtRemediationScalar = properties.getProperty(propKey + ".debtScalar", DEFAULT_RULE_DEBT_SCALAR); + String debtRemediationOffset = properties.getProperty(propKey + ".debtOffset", DEFAULT_RULE_DEBT_OFFSET); + String debtCharacteristic = properties.getProperty(propKey + ".debtType", DEFAULT_RULE_DEBT_TYPE); + + TsLintRule tsRule = null; + + // try to apply the specified debt remediation function + if (debtRemediationFunction != null) { + DebtRemediationFunction.Type debtRemediationFunctionEnum = DebtRemediationFunction.Type.valueOf(debtRemediationFunction); + + tsRule = new TsLintRule( + ruleId, + ruleSeverity, + ruleName, + ruleDescription, + debtRemediationFunctionEnum, + debtRemediationScalar, + debtRemediationOffset, + debtCharacteristic + ); + } + + // no debt remediation function specified + if (tsRule == null) { + tsRule = new TsLintRule( + ruleId, + ruleSeverity, + ruleName, + ruleDescription + ); + } + + rulesCollection.add(tsRule); } Collections.sort(rulesCollection, new Comparator() { @@ -100,13 +140,36 @@ public int compare(TsLintRule r1, TsLintRule r2) { }); } - private void createRule(NewRepository repository, TsLintRule rule) { - repository - .createRule(rule.key) - .setName(rule.name) - .setSeverity(rule.severity) - .setHtmlDescription(rule.htmlDescription) + private void createRule(NewRepository repository, TsLintRule tsRule) { + NewRule sonarRule = repository + .createRule(tsRule.key) + .setName(tsRule.name) + .setSeverity(tsRule.severity) + .setHtmlDescription(tsRule.htmlDescription) .setStatus(RuleStatus.READY); + + if (tsRule.hasDebtRemediation) { + DebtRemediationFunction debtRemediationFn = null; + DebtRemediationFunctions funcs = sonarRule.debtRemediationFunctions(); + + switch (tsRule.debtRemediationFunction) + { + case LINEAR: + debtRemediationFn = funcs.linear(tsRule.debtRemediationScalar); + break; + + case LINEAR_OFFSET: + debtRemediationFn = funcs.linearWithOffset(tsRule.debtRemediationScalar, tsRule.debtRemediationOffset); + break; + + case CONSTANT_ISSUE: + debtRemediationFn = funcs.constantPerIssue(tsRule.debtRemediationScalar); + break; + } + + sonarRule.setDebtRemediationFunction(debtRemediationFn); + sonarRule.setDebtSubCharacteristic(tsRule.debtCharacteristic); + } } public void define(Context context) { @@ -123,7 +186,7 @@ public void define(Context context) { } // add additional custom TsLint rules - for (TsLintRule customRule : tslintCustomRules) { + for (TsLintRule customRule : tslintRules) { createRule(repository, customRule); } @@ -134,7 +197,7 @@ public List getCoreRules() { return tslintCoreRules; } - public List getCustomRules() { - return tslintCustomRules; + public List getRules() { + return tslintRules; } } diff --git a/src/main/java/com/pablissimo/sonar/TypeScriptPlugin.java b/src/main/java/com/pablissimo/sonar/TypeScriptPlugin.java index 3b99441..258dd84 100644 --- a/src/main/java/com/pablissimo/sonar/TypeScriptPlugin.java +++ b/src/main/java/com/pablissimo/sonar/TypeScriptPlugin.java @@ -3,10 +3,7 @@ import java.util.Arrays; import java.util.List; -import org.sonar.api.Properties; -import org.sonar.api.Property; -import org.sonar.api.PropertyType; -import org.sonar.api.SonarPlugin; +import org.sonar.api.*; @Properties({ @Property( @@ -18,13 +15,24 @@ global = true ), @Property( - key = TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG, - defaultValue = "", - type = PropertyType.TEXT, - name = "Custom TSLint rules configuration", - description = "Map custom TSLint rules to SonarQube rules & settings", - project = true, - global = true + key = TypeScriptPlugin.SETTING_TS_RULE_CONFIGS, + name = "TsLint Rule-Collections", + description = "A collection of configurations for mapping TsLint rules to Sonar rules", + project = false, + global = true, + fields = { + @PropertyField( + key = "name", + name = "rule collection name", + type = PropertyType.STRING + ), + @PropertyField( + key = "config", + name = "rule configs & parameters", + type = PropertyType.TEXT, + indicativeSize = 120 + ) + } ), @Property( key = TypeScriptPlugin.SETTING_EXCLUDE_TYPE_DEFINITION_FILES, @@ -84,11 +92,11 @@ public class TypeScriptPlugin extends SonarPlugin { public static final String SETTING_EXCLUDE_TYPE_DEFINITION_FILES = "sonar.ts.excludetypedefinitionfiles"; public static final String SETTING_FORCE_ZERO_COVERAGE = "sonar.ts.forceZeroCoverage"; public static final String SETTING_TS_LINT_PATH = "sonar.ts.tslintpath"; - public static final String SETTING_TS_LINT_CUSTOM_RULES_CONFIG = "sonar.ts.tslint.customrules"; public static final String SETTING_TS_LINT_CONFIG_PATH = "sonar.ts.tslintconfigpath"; public static final String SETTING_TS_LINT_TIMEOUT = "sonar.ts.tslinttimeout"; public static final String SETTING_TS_LINT_RULES_DIR = "sonar.ts.tslintrulesdir"; public static final String SETTING_LCOV_REPORT_PATH = "sonar.ts.lcov.reportpath"; + public static final String SETTING_TS_RULE_CONFIGS = "sonar.ts.ruleconfigs"; public List getExtensions() { return Arrays.asList @@ -99,6 +107,6 @@ public List getExtensions() { LOCSensor.class, TsCoverageSensor.class, TsRulesDefinition.class - ); + ); } } diff --git a/src/main/java/com/pablissimo/sonar/model/TsLintRule.java b/src/main/java/com/pablissimo/sonar/model/TsLintRule.java index 2311876..2b55f79 100644 --- a/src/main/java/com/pablissimo/sonar/model/TsLintRule.java +++ b/src/main/java/com/pablissimo/sonar/model/TsLintRule.java @@ -1,15 +1,55 @@ package com.pablissimo.sonar.model; +import org.sonar.api.server.debt.DebtRemediationFunction; + public class TsLintRule { public final String key; public final String name; public final String severity; public final String htmlDescription; - public TsLintRule(String key, String severity, String name, String htmlDescription) { + public boolean hasDebtRemediation; + public final DebtRemediationFunction.Type debtRemediationFunction; + public final String debtRemediationScalar; + public final String debtRemediationOffset; + public final String debtCharacteristic; + + public TsLintRule( + String key, + String severity, + String name, + String htmlDescription + ) { + this.key = key; + this.severity = severity; + this.name = name; + this.htmlDescription = htmlDescription; + + this.debtRemediationFunction = DebtRemediationFunction.Type.CONSTANT_ISSUE; + this.debtRemediationScalar = "0min"; + this.debtRemediationOffset = "0min"; + this.debtCharacteristic = null; + } + + public TsLintRule( + String key, + String severity, + String name, + String htmlDescription, + DebtRemediationFunction.Type debtRemediationFunction, + String debtRemediationScalar, + String debtRemediationOffset, + String debtCharacteristic + ) { this.key = key; this.severity = severity; this.name = name; this.htmlDescription = htmlDescription; + + this.hasDebtRemediation = true; + this.debtRemediationFunction = debtRemediationFunction; + this.debtRemediationScalar = debtRemediationScalar; + this.debtRemediationOffset = debtRemediationOffset; + this.debtCharacteristic = debtCharacteristic; } } diff --git a/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java b/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java deleted file mode 100644 index 40f0c50..0000000 --- a/src/test/java/com/pablissimo/sonar/TsLintCustomRulesTest.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.pablissimo.sonar; - -import com.pablissimo.sonar.model.TsLintRule; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.sonar.api.config.Settings; - -import java.io.IOException; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TsLintCustomRulesTest { - private Settings settings; - private TsRulesDefinition rulesDef; - - @Before - public void setUp() throws Exception { - this.settings = mock(Settings.class); - } - - @After - public void tearDown() throws Exception { - } - - @Test - public void parsesSingleCustomRules() throws IOException { - when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) - .thenReturn( - "custom-rule-1=true\n" + - "custom-rule-1.name=test rule #1\n" + - "custom-rule-1.severity=MAJOR\n" + - "custom-rule-1.description=#1 description\n" + - "\n" + - "custom-rule-2=true\n" + - "custom-rule-2.name=test rule #2\n" + - "custom-rule-2.severity=MINOR\n" + - "custom-rule-2.description=#2 description\n" + - "\n"); - - this.rulesDef = new TsRulesDefinition(this.settings); - - final int numCustomRules = 2; - - assertEquals(this.rulesDef.getCustomRules().size(), numCustomRules); - - List customRules = this.rulesDef.getCustomRules(); - - if (customRules.size() == numCustomRules) { - TsLintRule ruleNo1 = customRules.get(0); - TsLintRule ruleNo2 = customRules.get(1); - - assertEquals(ruleNo1.key, "custom-rule-1"); - assertEquals(ruleNo1.name, "test rule #1"); - assertEquals(ruleNo1.severity, "MAJOR"); - assertEquals(ruleNo1.htmlDescription, "#1 description"); - - assertEquals(ruleNo2.key, "custom-rule-2"); - assertEquals(ruleNo2.name, "test rule #2"); - assertEquals(ruleNo2.severity, "MINOR"); - assertEquals(ruleNo2.htmlDescription, "#2 description"); - } - } - - @Test - public void disabledCustomRule() throws IOException { - when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) - .thenReturn( - "custom-rule-1=false\n" + - "custom-rule-1.name=test rule #1\n" + - "custom-rule-1.severity=MAJOR\n" + - "custom-rule-1.description=#1 description\n" + - "\n" + - "custom-rule-2=true\n" + - "custom-rule-2.name=test rule #2\n" + - "custom-rule-2.severity=MINOR\n" + - "custom-rule-2.description=#2 description\n" + - "\n"); - - this.rulesDef = new TsRulesDefinition(this.settings); - - final int numCustomRules = 1; - - assertEquals(this.rulesDef.getCustomRules().size(), numCustomRules); - - List customRules = this.rulesDef.getCustomRules(); - - if (customRules.size() == numCustomRules) { - TsLintRule ruleNo2 = customRules.get(0); - - assertEquals(ruleNo2.key, "custom-rule-2"); - assertEquals(ruleNo2.name, "test rule #2"); - assertEquals(ruleNo2.severity, "MINOR"); - assertEquals(ruleNo2.htmlDescription, "#2 description"); - } - } - - @Test - public void parsesEmptyCustomRules() throws IOException { - when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) - .thenReturn("#empty config\n"); - - this.rulesDef = new TsRulesDefinition(this.settings); - - final int numCustomRules = 0; - - assertEquals(this.rulesDef.getCustomRules().size(), numCustomRules); - } -} diff --git a/src/test/java/com/pablissimo/sonar/TsLintSensorTest.java b/src/test/java/com/pablissimo/sonar/TsLintSensorTest.java index 3624766..84eeedf 100644 --- a/src/test/java/com/pablissimo/sonar/TsLintSensorTest.java +++ b/src/test/java/com/pablissimo/sonar/TsLintSensorTest.java @@ -27,6 +27,8 @@ import org.sonar.api.rules.RuleFinder; import com.pablissimo.sonar.model.TsLintIssue; import com.pablissimo.sonar.model.TsLintPosition; +import org.sonar.api.server.debt.DebtRemediationFunction; +import org.sonar.api.server.rule.RulesDefinition; public class TsLintSensorTest { Settings settings; @@ -52,18 +54,66 @@ public void setUp() throws Exception { when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_PATH)).thenReturn("/path/to/tslint"); when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CONFIG_PATH)).thenReturn("/path/to/tslint.json"); when(this.settings.getInt(TypeScriptPlugin.SETTING_TS_LINT_TIMEOUT)).thenReturn(45000); - when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) + when(this.settings.getKeysStartingWith(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS)) + .thenReturn(new ArrayList() {{ + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg1.name"); + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg1.config"); + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg2.name"); + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg2.config"); + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg3.name"); + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg3.config"); + }}); + + // config with one disabled rule + when(this.settings.getString(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg1.config")) .thenReturn( "custom-rule-1=false\n" + "custom-rule-1.name=test rule #1\n" + "custom-rule-1.severity=MAJOR\n" + "custom-rule-1.description=#1 description\n" + - "\n" + + "\n" + ); + + // config with a basic rule (no debt settings) + when(this.settings.getString(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg2.config")) + .thenReturn( "custom-rule-2=true\n" + "custom-rule-2.name=test rule #2\n" + "custom-rule-2.severity=MINOR\n" + "custom-rule-2.description=#2 description\n" + - "\n"); + "\n" + ); + + // config with a advanced rules (including debt settings) + when(this.settings.getString(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg3.config")) + .thenReturn( + "custom-rule-3=true\n" + + "custom-rule-3.name=test rule #3\n" + + "custom-rule-3.severity=INFO\n" + + "custom-rule-3.description=#3 description\n" + + "custom-rule-3.debtFunc=" + DebtRemediationFunction.Type.CONSTANT_ISSUE + "\n" + + "custom-rule-3.debtScalar=15min\n" + + "custom-rule-3.debtOffset=1min\n" + + "\n" + + "custom-rule-4=true\n" + + "custom-rule-4.name=test rule #4\n" + + "custom-rule-4.severity=MINOR\n" + + "custom-rule-4.description=#4 description\n" + + "custom-rule-4.debtFunc=" + DebtRemediationFunction.Type.LINEAR + "\n" + + "custom-rule-4.debtScalar=5min\n" + + "custom-rule-4.debtOffset=2h\n" + + "custom-rule-4.debtType=" + RulesDefinition.SubCharacteristics.EXCEPTION_HANDLING + "\n" + + "\n" + + "custom-rule-5=true\n" + + "custom-rule-5.name=test rule #5\n" + + "custom-rule-5.severity=MAJOR\n" + + "custom-rule-5.description=#5 description\n" + + "custom-rule-5.debtFunc=" + DebtRemediationFunction.Type.LINEAR_OFFSET + "\n" + + "custom-rule-5.debtScalar=30min\n" + + "custom-rule-5.debtOffset=15min\n" + + "custom-rule-5.debtType=" + RulesDefinition.SubCharacteristics.HARDWARE_RELATED_PORTABILITY + "\n" + + "\n" + ); this.fileSystem = mock(FileSystem.class); this.perspectives = mock(ResourcePerspectives.class); @@ -168,7 +218,7 @@ public void analyse_doesNothingWhenNoConfigPathset() throws IOException { @Test public void analyse_callsExecutorWithSuppliedTimeout() throws IOException { this.sensor.analyse(mock(Project.class), mock(SensorContext.class)); - + verify(this.executor, times(1)).execute(any(String.class), any(String.class), any(String.class), any(List.class), eq(45000)); } @@ -177,7 +227,31 @@ public void analyze_callsExecutorWithAtLeast5000msTimeout() throws IOException { when(this.settings.getInt(TypeScriptPlugin.SETTING_TS_LINT_TIMEOUT)).thenReturn(-500); this.sensor.analyse(mock(Project.class), mock(SensorContext.class)); - + verify(this.executor, times(1)).execute(any(String.class), any(String.class), any(String.class), any(List.class), eq(5000)); } + + @Test + public void check_getExecutor() + { + TsLintSensor sensor = new TsLintSensor(settings, fileSystem, perspectives, ruleFinder); + TsLintExecutor executor = sensor.getTsLintExecutor(); + assertNotNull(executor); + } + + @Test + public void check_getParser() + { + TsLintSensor sensor = new TsLintSensor(settings, fileSystem, perspectives, ruleFinder); + TsLintParser parser = sensor.getTsLintParser(); + assertNotNull(parser); + } + + @Test + public void check_getTsRulesDefinition() + { + TsLintSensor sensor = new TsLintSensor(settings, fileSystem, perspectives, ruleFinder); + TsRulesDefinition rulesDef = sensor.getTsRulesDefinition(); + assertNotNull(rulesDef); + } } diff --git a/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java b/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java index 4677a96..47c22a1 100644 --- a/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java +++ b/src/test/java/com/pablissimo/sonar/TsRulesDefinitionTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import org.sonar.api.config.Settings; import org.sonar.api.rule.Severity; +import org.sonar.api.server.debt.DebtRemediationFunction; import org.sonar.api.server.rule.RulesDefinition; import org.sonar.api.server.rule.RulesDefinition.Context; import org.sonar.api.server.rule.RulesDefinition.Rule; @@ -16,6 +17,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.mockito.Mockito.*; public class TsRulesDefinitionTest { @@ -28,21 +30,71 @@ public class TsRulesDefinitionTest { public void setUp() throws Exception { this.settings = mock(Settings.class); - when(this.settings.getString(TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)) + + when(this.settings.getKeysStartingWith(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS)) + .thenReturn(new ArrayList() {{ + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg1.name"); + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg1.config"); + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg2.name"); + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg2.config"); + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg3.name"); + add(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg3.config"); + }}); + + // config with one disabled rule + when(this.settings.getString(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg1.config")) .thenReturn( "custom-rule-1=false\n" + "custom-rule-1.name=test rule #1\n" + "custom-rule-1.severity=MAJOR\n" + "custom-rule-1.description=#1 description\n" + - "\n" + + "\n" + ); + + // config with a basic rule (no debt settings) + when(this.settings.getString(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg2.config")) + .thenReturn( "custom-rule-2=true\n" + "custom-rule-2.name=test rule #2\n" + "custom-rule-2.severity=MINOR\n" + "custom-rule-2.description=#2 description\n" + - "\n"); + "\n" + ); + + // config with a advanced rules (including debt settings) + when(this.settings.getString(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS + ".cfg3.config")) + .thenReturn( + "custom-rule-3=true\n" + + "custom-rule-3.name=test rule #3\n" + + "custom-rule-3.severity=INFO\n" + + "custom-rule-3.description=#3 description\n" + + "custom-rule-3.debtFunc=" + DebtRemediationFunction.Type.CONSTANT_ISSUE + "\n" + + "custom-rule-3.debtScalar=15min\n" + + "custom-rule-3.debtOffset=1min\n" + + "\n" + + "custom-rule-4=true\n" + + "custom-rule-4.name=test rule #4\n" + + "custom-rule-4.severity=MINOR\n" + + "custom-rule-4.description=#4 description\n" + + "custom-rule-4.debtFunc=" + DebtRemediationFunction.Type.LINEAR + "\n" + + "custom-rule-4.debtScalar=5min\n" + + "custom-rule-4.debtOffset=2h\n" + + "custom-rule-4.debtType=" + RulesDefinition.SubCharacteristics.EXCEPTION_HANDLING + "\n" + + "\n" + + "custom-rule-5=true\n" + + "custom-rule-5.name=test rule #5\n" + + "custom-rule-5.severity=MAJOR\n" + + "custom-rule-5.description=#5 description\n" + + "custom-rule-5.debtFunc=" + DebtRemediationFunction.Type.LINEAR_OFFSET + "\n" + + "custom-rule-5.debtScalar=30min\n" + + "custom-rule-5.debtOffset=15min\n" + + "custom-rule-5.debtType=" + RulesDefinition.SubCharacteristics.HARDWARE_RELATED_PORTABILITY + "\n" + + "\n" + ); this.definition = new TsRulesDefinition(this.settings); this.context = new Context(); + this.definition.define(context); } @Test @@ -446,10 +498,68 @@ public void ConfiguresWhitespaceRule() { } @Test - public void ConfiguresCustomRule() { - Rule rule = getRule("custom-rule-2"); - assertNotNull(rule); - assertEquals(Severity.MINOR, rule.severity()); + public void ConfiguresAdditionalRules() { + // cfg1 + Rule rule1 = getRule("custom-rule-1"); + assertNull(rule1); + + // cfg2 + Rule rule2 = getRule("custom-rule-2"); + assertNotNull(rule2); + assertEquals("test rule #2", rule2.name()); + assertEquals(Severity.MINOR, rule2.severity()); + assertEquals("#2 description", rule2.htmlDescription()); + assertEquals(null, rule2.debtRemediationFunction()); + assertEquals(null, rule2.debtSubCharacteristic()); + + // cfg3 + Rule rule3 = getRule("custom-rule-3"); + assertNotNull(rule3); + assertEquals("test rule #3", rule3.name()); + assertEquals(Severity.INFO, rule3.severity()); + assertEquals("#3 description", rule3.htmlDescription()); + assertEquals( + DebtRemediationFunction.Type.CONSTANT_ISSUE, + rule3.debtRemediationFunction().type() + ); + assertEquals(null, rule3.debtRemediationFunction().coefficient()); + assertEquals("15min", rule3.debtRemediationFunction().offset()); + assertEquals( + TsRulesDefinition.DEFAULT_RULE_DEBT_TYPE, + rule3.debtSubCharacteristic() + ); + + Rule rule4 = getRule("custom-rule-4"); + assertNotNull(rule4); + assertEquals("test rule #4", rule4.name()); + assertEquals(Severity.MINOR, rule4.severity()); + assertEquals("#4 description", rule4.htmlDescription()); + assertEquals( + DebtRemediationFunction.Type.LINEAR, + rule4.debtRemediationFunction().type() + ); + assertEquals("5min", rule4.debtRemediationFunction().coefficient()); + assertEquals(null, rule4.debtRemediationFunction().offset()); + assertEquals( + RulesDefinition.SubCharacteristics.EXCEPTION_HANDLING, + rule4.debtSubCharacteristic() + ); + + Rule rule5 = getRule("custom-rule-5"); + assertNotNull(rule5); + assertEquals("test rule #5", rule5.name()); + assertEquals(Severity.MAJOR, rule5.severity()); + assertEquals("#5 description", rule5.htmlDescription()); + assertEquals( + DebtRemediationFunction.Type.LINEAR_OFFSET, + rule5.debtRemediationFunction().type() + ); + assertEquals("30min", rule5.debtRemediationFunction().coefficient()); + assertEquals("15min", rule5.debtRemediationFunction().offset()); + assertEquals( + RulesDefinition.SubCharacteristics.HARDWARE_RELATED_PORTABILITY, + rule5.debtSubCharacteristic() + ); } @Test @@ -464,8 +574,27 @@ public int read() throws IOException { TsRulesDefinition.loadRules(testStream, rules); } + @Test + public void CheckAdditionalRulesConfigProvided() { + TsRulesDefinition rulesDef = new TsRulesDefinition(this.settings); + List rules = rulesDef.getRules(); + assertNotNull(rules); + assertEquals(4, rules.size()); // 4 enabled rules, 1 disabled rule + } + + @Test + public void CheckCustomRulesConfigNotProvided() { + + Settings settings = mock(Settings.class); + when(settings.getKeysStartingWith(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS)).thenReturn(new ArrayList()); + + TsRulesDefinition rulesDef = new TsRulesDefinition(settings); + List rules = rulesDef.getRules(); + assertNotNull(rules); + assertEquals(0, rules.size()); + } + private Rule getRule(String name) { - this.definition.define(context); return this.context.repository(TsRulesDefinition.REPOSITORY_NAME).rule(name); } diff --git a/src/test/java/com/pablissimo/sonar/TypeScriptPluginTest.java b/src/test/java/com/pablissimo/sonar/TypeScriptPluginTest.java index 42c99cb..91a9437 100644 --- a/src/test/java/com/pablissimo/sonar/TypeScriptPluginTest.java +++ b/src/test/java/com/pablissimo/sonar/TypeScriptPluginTest.java @@ -8,7 +8,6 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.Predicate; -import org.apache.commons.lang.ArrayUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -68,10 +67,10 @@ public void definesExpectedProperties() { TypeScriptPlugin.SETTING_TS_LINT_CONFIG_PATH)); assertNotNull(findPropertyByName(properties, TypeScriptPlugin.SETTING_TS_LINT_TIMEOUT)); - assertNotNull(findPropertyByName(properties, - TypeScriptPlugin.SETTING_TS_LINT_CUSTOM_RULES_CONFIG)); assertNotNull(findPropertyByName(properties, TypeScriptPlugin.SETTING_TS_LINT_RULES_DIR)); + assertNotNull(findPropertyByName(properties, + TypeScriptPlugin.SETTING_TS_RULE_CONFIGS)); } @Test @@ -134,6 +133,26 @@ public void rulesDirSetting_definedAppropriately() { assertEquals(false, property.global()); } + @Test + public void ruleConfigsSetting_definedAppropriately() { + Property property = findPropertyByName(TypeScriptPlugin.SETTING_TS_RULE_CONFIGS); + + assertEquals(PropertyType.STRING, property.type()); + assertEquals("", property.defaultValue()); + assertEquals(false, property.project()); + assertEquals(true, property.global()); + assertEquals(2, property.fields().length); + + // name + assertEquals("name", property.fields()[0].key()); + assertEquals(PropertyType.STRING, property.fields()[0].type()); + + // config + assertEquals("config", property.fields()[1].key()); + assertEquals(PropertyType.TEXT, property.fields()[1].type()); + assertEquals(120, property.fields()[1].indicativeSize()); + } + private Property findPropertyByName(String property) { return findPropertyByName(((Properties) plugin.getClass() .getAnnotations()[0]).value(), property); diff --git a/src/test/java/com/pablissimo/sonar/model/TsLintRuleTest.java b/src/test/java/com/pablissimo/sonar/model/TsLintRuleTest.java new file mode 100644 index 0000000..aa5ee34 --- /dev/null +++ b/src/test/java/com/pablissimo/sonar/model/TsLintRuleTest.java @@ -0,0 +1,54 @@ +package com.pablissimo.sonar.model; + +import org.junit.Test; +import org.sonar.api.rule.Severity; +import org.sonar.api.server.debt.DebtRemediationFunction; +import org.sonar.api.server.rule.RulesDefinition; + +import static org.junit.Assert.assertEquals; + +public class TsLintRuleTest { + @Test + public void ruleWithoutDebtRemediation() { + TsLintRule rule = new TsLintRule( + "key", + Severity.MAJOR, + "name", + "" + ); + + assertEquals("key", rule.key); + assertEquals(Severity.MAJOR, rule.severity); + assertEquals("name", rule.name); + assertEquals("", rule.htmlDescription); + assertEquals(false, rule.hasDebtRemediation); + assertEquals(DebtRemediationFunction.Type.CONSTANT_ISSUE, rule.debtRemediationFunction); + assertEquals("0min", rule.debtRemediationScalar); + assertEquals("0min", rule.debtRemediationOffset); + assertEquals(null, rule.debtCharacteristic); + } + + @Test + public void ruleWithDebtRemediation() { + TsLintRule rule = new TsLintRule( + "key", + Severity.MAJOR, + "name", + "", + DebtRemediationFunction.Type.LINEAR_OFFSET, + "1min", + "2min", + RulesDefinition.SubCharacteristics.COMPILER_RELATED_PORTABILITY + ); + + assertEquals("key", rule.key); + assertEquals(Severity.MAJOR, rule.severity); + assertEquals("name", rule.name); + assertEquals("", rule.htmlDescription); + assertEquals(true, rule.hasDebtRemediation); + assertEquals(DebtRemediationFunction.Type.LINEAR_OFFSET, rule.debtRemediationFunction); + assertEquals("1min", rule.debtRemediationScalar); + assertEquals("2min", rule.debtRemediationOffset); + assertEquals(RulesDefinition.SubCharacteristics.COMPILER_RELATED_PORTABILITY, rule.debtCharacteristic); + } +} From 12350478f32020d676b11bb647c5f2194052c6ed Mon Sep 17 00:00:00 2001 From: Wolfgang Steiner Date: Wed, 6 Jul 2016 12:43:11 +0200 Subject: [PATCH 09/11] - updated README --- README.md | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 59c368a..1124850 100644 --- a/README.md +++ b/README.md @@ -85,25 +85,32 @@ The plugin has so far *only been tested on Windows* and it'll be no surprise if sonar.ts.forceZeroCoverageOptionalForces code coverage percentage to zero when no report is supplied, defaults to false sonar.ts.tslinttimeoutOptionalMax time to wait for TsLint to finish processing a single file (in milliseconds), defaults to 60 seconds sonar.ts.tslintrulesdirOptionalPath to a folder containing custom TsLint rules referenced in tslint.json -sonar.ts.tslint.customrulesOptionalConfiguration to map custom TSLint rules to SonarQube rules & settings sonar.ts.lcov.reportpathOptionalPath to an LCOV code-coverage report to be included in analysis +sonar.ts.ruleconfigsOptionalA list of configurations to map custom TSLint rules to dedicated SonarQube rules & settings ## TSLint Custom Rules -To present custom TSLint rules in SonarQube analysis, you can provide a configuration that maps the TSLint rules from your `sonar.ts.tslintrulesdir` +To present custom TSLint rules in SonarQube analysis, you can provide a configuration that maps the TSLint rules from your `sonar.ts.tslintrulesdir` directory to dedicated Sonar rules for analysis. -The configuration for a TSLint Sonar rule consists of a line declaring the TSLint rule id, a boolean switch to enable or disable the rule if needed +The configuration for a TSLint Sonar rule consists of a line declaring the TSLint rule id, a boolean switch to enable or disable the rule if needed and some attached properties that are used by Sonar for analysis and reporting. -For example taking the `no-constant-condition` rule from the [tslint-eslint-rules](https://github.com/buzinas/tslint-eslint-rules) package, -a configuration in Sonar could look as follows: +For example taking the `export-name` rule from the [tslint-microsoft-contrib](https://github.com/Microsoft/tslint-microsoft-contrib) package, +a configuration for that rule in SonarTsPlugin could look as follows: + + export-name=true + export-name.name=The name of the exported module must match the filename of the source file. + export-name.severity=MAJOR + export-name.description=This is case-sensitive but ignores file extension. Since version 1.0, this rule takes a list of regular expressions as a parameter. Any export name matching that regular expression will be ignored. + export-name.debtFunc=LINEAR_OFFSET + export-name.debtScalar=15min + export-name.debtOffset=1h + export-name.debtType=HARDWARE_RELATED_PORTABILITY - no-constant-condition=true - no-constant-condition.name=disallow use of constant expressions in conditions - no-constant-condition.severity=MAJOR - no-constant-condition.description=Comparing a literal expression in a condition is usually a typo or development trigger for a specific behavior. +* for documentation about the `technical debt` parameters look [here](http://docs.sonarqube.org/display/PLUG/Rule+Remediation+Costs) and [here](http://javadocs.sonarsource.org/5.2/apidocs/org/sonar/api/server/debt/DebtRemediationFunction.html) +* for possible values for `debtType` go [here](http://javadocs.sonarsource.org/5.2/apidocs/org/sonar/api/server/rule/RulesDefinition.SubCharacteristics.html) ##Licence MIT From cd8014eacdae0551e6c938d917fee27799375965 Mon Sep 17 00:00:00 2001 From: Pablissimo Date: Sat, 23 Jul 2016 14:49:11 +0100 Subject: [PATCH 10/11] Changing default rule name to be a variant of the rule key for custom rules, rather than 'Unnamed TsLint Rule' --- src/main/java/com/pablissimo/sonar/TsRulesDefinition.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java index 0c0fae0..0649251 100644 --- a/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java +++ b/src/main/java/com/pablissimo/sonar/TsRulesDefinition.java @@ -18,7 +18,6 @@ public class TsRulesDefinition implements RulesDefinition { public static final String REPOSITORY_NAME = "tslint"; - public static final String DEFAULT_RULE_NAME = "Unnamed TsLint rule"; public static final String DEFAULT_RULE_SEVERITY = Severity.defaultSeverity(); public static final String DEFAULT_RULE_DESCRIPTION = "No description provided for this TsLint rule"; public static final String DEFAULT_RULE_DEBT_SCALAR = "0min"; @@ -92,7 +91,7 @@ public static void loadRules(InputStream stream, List rulesCollectio continue; String ruleId = propKey; - String ruleName = properties.getProperty(propKey + ".name", DEFAULT_RULE_NAME); + String ruleName = properties.getProperty(propKey + ".name", ruleId.replace("-", " ")); String ruleSeverity = properties.getProperty(propKey + ".severity", DEFAULT_RULE_SEVERITY); String ruleDescription = properties.getProperty(propKey + ".description", DEFAULT_RULE_DESCRIPTION); From edec5774a7e0f9a5eb032f48ffa27b478c0fcc83 Mon Sep 17 00:00:00 2001 From: Pablissimo Date: Sat, 23 Jul 2016 14:56:27 +0100 Subject: [PATCH 11/11] Tweak readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1124850..7b54521 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ The plugin has so far *only been tested on Windows* and it'll be no surprise if sonar.ts.tslintpathMandatoryPath to the installed copy of TsLint to use sonar.ts.tslint.customrulesOptionalConfiguration to map custom TSLint rules to SonarQube rules & settings +sonar.ts.ruleconfigsOptionalA list of configurations to map custom TSLint rules to dedicated SonarQube rules & settings - see TsLint Custom Rules section below @@ -86,13 +87,12 @@ The plugin has so far *only been tested on Windows* and it'll be no surprise if sonar.ts.tslinttimeoutOptionalMax time to wait for TsLint to finish processing a single file (in milliseconds), defaults to 60 seconds sonar.ts.tslintrulesdirOptionalPath to a folder containing custom TsLint rules referenced in tslint.json sonar.ts.lcov.reportpathOptionalPath to an LCOV code-coverage report to be included in analysis -sonar.ts.ruleconfigsOptionalA list of configurations to map custom TSLint rules to dedicated SonarQube rules & settings -## TSLint Custom Rules +## TsLint Custom Rules -To present custom TSLint rules in SonarQube analysis, you can provide a configuration that maps the TSLint rules from your `sonar.ts.tslintrulesdir` +To present custom TSLint rules in SonarQube analysis, you can provide a configuration that maps the TsLint rules from your `sonar.ts.tslintrulesdir` directory to dedicated Sonar rules for analysis. The configuration for a TSLint Sonar rule consists of a line declaring the TSLint rule id, a boolean switch to enable or disable the rule if needed and some attached properties that are used by Sonar for analysis and reporting. @@ -119,7 +119,7 @@ MIT Thanks to the following for contributions to the plugin: * [Alex Krauss](https://github.com/alexkrauss) and [Manuel Huber](https://github.com/nelo112) for work on improving compatibility with *nix OSes * [schnee3](https://github.com/schnee3) for giving us some NCLOC support -* [drywolf](https://github.com/drywolf) for TSX support +* [drywolf](https://github.com/drywolf) for TSX file and better custom rule mapping ##With thanks * The LCOV parser is directly copied from the community JavaScript SonarQube plug-in, which is LGPL'd.