@@ -19,24 +19,30 @@ | |||
*/ | |||
package org.sonar.education; | |||
import java.nio.charset.StandardCharsets; | |||
import org.sonar.api.internal.apachecommons.io.IOUtils; | |||
import org.sonar.api.server.rule.RuleDescriptionSection; | |||
import org.sonar.api.server.rule.RulesDefinition; | |||
import org.sonar.education.sensors.EducationPrinciplesSensor; | |||
import org.sonar.education.sensors.EducationWithContextsSensor; | |||
import org.sonar.education.sensors.EducationWithDetectedContextSensor; | |||
import static org.sonar.education.sensors.EducationWithSingleContextSensor.EDUCATION_WITH_SINGLE_CONTEXT_RULE_KEY; | |||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY; | |||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; | |||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.INTRODUCTION_SECTION_KEY; | |||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY; | |||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY; | |||
import static org.sonar.education.sensors.EducationWith2LinkedCodeSnippetsSensor.EDUCATION_WITH_2_LINKED_CODE_SNIPPETS_RULE_KEY; | |||
import static org.sonar.education.sensors.EducationWith4LinkedCodeSnippetsSensor.EDUCATION_WITH_4_LINKED_CODE_SNIPPETS_RULE_KEY; | |||
import static org.sonar.education.sensors.EducationWithSingleContextSensor.EDUCATION_WITH_SINGLE_CONTEXT_RULE_KEY; | |||
public class EducationRulesDefinition implements RulesDefinition { | |||
public static final String EDUCATION_RULE_REPOSITORY_KEY = "edu"; | |||
public static final String EDUCATION_KEY = "education"; | |||
private static final String[] ALL_SECTIONS = {INTRODUCTION_SECTION_KEY, ROOT_CAUSE_SECTION_KEY, ASSESS_THE_PROBLEM_SECTION_KEY, | |||
RESOURCES_SECTION_KEY, HOW_TO_FIX_SECTION_KEY}; | |||
private static final String IGNORED_FAKE_SECTION = "fake_section_to_be_ignored"; | |||
public static final String[] CONTEXTS = {"spring", "hibernate", "apache-commons", "vaadin", "mybatis"}; | |||
@@ -55,6 +61,10 @@ public class EducationRulesDefinition implements RulesDefinition { | |||
createRuleWithDescriptionSectionsAndMultipleContexts(repo); | |||
createRuleWithDescriptionSectionsAndMultipleContextsIncludingOneDetected(repo); | |||
createRuleWith2LinkedCodeSnippetsInTwoSections(repo); | |||
createRuleWith4LinkedCodeSnippetsInOneSection(repo); | |||
repo.done(); | |||
} | |||
@@ -66,7 +76,7 @@ public class EducationRulesDefinition implements RulesDefinition { | |||
ruleWithSingleContext | |||
.setHtmlDescription(String.format("This rule contains description sections. To trigger an issue using this rule you need to " + | |||
"scan any text file with a line containing %s key word. This rule also contains 2 education principles - %s and %s", | |||
"scan any text file with a line containing %s key word. This rule also contains 2 education principles - %s and %s", | |||
ruleKey, educationPrinciples[0], educationPrinciples[1])) | |||
.addEducationPrincipleKeys(educationPrinciples); | |||
@@ -77,7 +87,7 @@ public class EducationRulesDefinition implements RulesDefinition { | |||
private void createRuleWithDescriptionSectionsAndSingleContext(NewRepository repo) { | |||
String ruleKey = EDUCATION_WITH_SINGLE_CONTEXT_RULE_KEY; | |||
NewRule ruleWithSingleContext = repo.createRule(ruleKey).setName("Rule with description sections and single " + | |||
"contexts for 'how to fix'") | |||
"contexts for 'how to fix'") | |||
.addTags(EDUCATION_KEY); | |||
ruleWithSingleContext | |||
@@ -96,7 +106,7 @@ public class EducationRulesDefinition implements RulesDefinition { | |||
ruleWithMultipleContexts | |||
.setHtmlDescription(String.format("This rule contains description sections and multiple contexts for 'how to fix' section. To trigger " + | |||
"an issue using this rule you need to scan any text file with a line containing %s keyword.", | |||
"an issue using this rule you need to scan any text file with a line containing %s keyword.", | |||
EducationWithContextsSensor.EDUCATION_WITH_CONTEXTS_RULE_KEY)); | |||
addNonContextualizedDescriptionSections(ruleWithMultipleContexts); | |||
@@ -113,7 +123,7 @@ public class EducationRulesDefinition implements RulesDefinition { | |||
ruleWithMultipleContexts | |||
.setHtmlDescription(String.format("This rule contains description sections and multiple contexts (including one detected) for " + | |||
"'how to fix' section. To trigger an issue using this rule you need to scan any text file with a line containing %s keyword.", | |||
"'how to fix' section. To trigger an issue using this rule you need to scan any text file with a line containing %s keyword.", | |||
EducationWithDetectedContextSensor.EDUCATION_WITH_DETECTED_CONTEXT_RULE_KEY)); | |||
addNonContextualizedDescriptionSections(ruleWithMultipleContexts); | |||
@@ -123,6 +133,46 @@ public class EducationRulesDefinition implements RulesDefinition { | |||
} | |||
} | |||
private void createRuleWith2LinkedCodeSnippetsInTwoSections(NewRepository repo) { | |||
String ruleKey = EDUCATION_WITH_2_LINKED_CODE_SNIPPETS_RULE_KEY; | |||
NewRule rule2CodeSnippetsIn2Sections = repo.createRule(ruleKey).setName("Rule with description sections and code snippets " + | |||
"linked to each other in 2 different sections in order to display diff between them.") | |||
.addTags(EDUCATION_KEY); | |||
rule2CodeSnippetsIn2Sections | |||
.setHtmlDescription(String.format("This rule contains description sections and 2 linked code snippets in 2 sections. To " + | |||
"trigger an issue using this rule you need to scan any text file with a line containing %s key word.", ruleKey)); | |||
String completelyDifferentSnippetsHtml = readResource("2completelyDifferentSnippets.html"); | |||
String codeSnippetsHtml = readResource("2codeSnippets.html"); | |||
rule2CodeSnippetsIn2Sections.addDescriptionSection(descriptionSection(INTRODUCTION_SECTION_KEY)) | |||
.addDescriptionSection(descriptionSection(ROOT_CAUSE_SECTION_KEY, completelyDifferentSnippetsHtml)) | |||
.addDescriptionSection(descriptionSection(ASSESS_THE_PROBLEM_SECTION_KEY)) | |||
.addDescriptionSection(descriptionSection(HOW_TO_FIX_SECTION_KEY, codeSnippetsHtml)) | |||
.addDescriptionSection(descriptionSection(RESOURCES_SECTION_KEY)); | |||
} | |||
private void createRuleWith4LinkedCodeSnippetsInOneSection(NewRepository repo) { | |||
String ruleKey = EDUCATION_WITH_4_LINKED_CODE_SNIPPETS_RULE_KEY; | |||
NewRule rule2CodeSnippetsInTheSameSection = repo.createRule(ruleKey).setName("Rule with description sections and code snippets " + | |||
"linked to each other in the same section in order to display diff between them.") | |||
.addTags(EDUCATION_KEY); | |||
rule2CodeSnippetsInTheSameSection | |||
.setHtmlDescription(String.format("This rule contains description sections and 4 linked code snippets in the same section. To " + | |||
"trigger an issue using this rule you need to scan any text file with a line containing %s key word.", ruleKey)); | |||
String codeSnippetsHtml = readResource("4codeSnippets.html"); | |||
rule2CodeSnippetsInTheSameSection.addDescriptionSection(descriptionSection(INTRODUCTION_SECTION_KEY)) | |||
.addDescriptionSection(descriptionSection(ROOT_CAUSE_SECTION_KEY, codeSnippetsHtml)) | |||
.addDescriptionSection(descriptionSection(ASSESS_THE_PROBLEM_SECTION_KEY)) | |||
.addDescriptionSection(descriptionSection(HOW_TO_FIX_SECTION_KEY)) | |||
.addDescriptionSection(descriptionSection(RESOURCES_SECTION_KEY)); | |||
} | |||
private static void addNonContextualizedDescriptionSections(NewRule newRule) { | |||
newRule.addDescriptionSection(descriptionSection(INTRODUCTION_SECTION_KEY)) | |||
.addDescriptionSection(descriptionSection(ROOT_CAUSE_SECTION_KEY)) | |||
@@ -141,9 +191,21 @@ public class EducationRulesDefinition implements RulesDefinition { | |||
} | |||
private static RuleDescriptionSection descriptionSection(String sectionKey) { | |||
return descriptionSection(sectionKey, HTML_LOREM_IPSUM); | |||
} | |||
private static RuleDescriptionSection descriptionSection(String sectionKey, String htmlContent) { | |||
return RuleDescriptionSection.builder() | |||
.sectionKey(sectionKey) | |||
.htmlContent(String.format("%s: %s", sectionKey, HTML_LOREM_IPSUM)) | |||
.htmlContent(htmlContent) | |||
.build(); | |||
} | |||
private String readResource(String file) { | |||
try { | |||
return IOUtils.toString(getClass().getResource(file), StandardCharsets.UTF_8); | |||
} catch (Exception e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.education.sensors; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
public class EducationWith2LinkedCodeSnippetsSensor extends EducationRuleSensor { | |||
public static final String EDUCATION_WITH_2_LINKED_CODE_SNIPPETS_RULE_KEY = "2_LINKED_CODE_SNIPPETS"; | |||
public EducationWith2LinkedCodeSnippetsSensor(FileSystem fs, ActiveRules activeRules) { | |||
super(fs, activeRules, EDUCATION_WITH_2_LINKED_CODE_SNIPPETS_RULE_KEY); | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.education.sensors; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
public class EducationWith4LinkedCodeSnippetsSensor extends EducationRuleSensor { | |||
public static final String EDUCATION_WITH_4_LINKED_CODE_SNIPPETS_RULE_KEY = "4_LINKED_CODE_SNIPPETS"; | |||
public EducationWith4LinkedCodeSnippetsSensor(FileSystem fs, ActiveRules activeRules) { | |||
super(fs, activeRules, EDUCATION_WITH_4_LINKED_CODE_SNIPPETS_RULE_KEY); | |||
} | |||
} |
@@ -0,0 +1,45 @@ | |||
<p>An infinite loop is one that will never end while the program is running, i.e., you have to kill the program to get out of the loop. Whether it is | |||
by meeting the loop’s end condition or via a <code>break</code>, every loop should have an end condition.</p> | |||
<h3>Known Limitations</h3> | |||
<ul> | |||
<li> False positives: when <code>yield</code> is used - <a href="https://github.com/SonarSource/SonarJS/issues/674">Issue #674</a>. </li> | |||
<li> False positives: when an exception is raised by a function invoked within the loop. </li> | |||
<li> False negatives: when a loop condition is based on an element of an array or object. </li> | |||
</ul> | |||
<h2>Noncompliant Code Example</h2> | |||
<pre class="diff-id-1 diff-noncompliant">for (;;) { // Noncompliant; end condition omitted | |||
// ... | |||
} | |||
var j = 0; | |||
while (true) { // Noncompliant; constant end condition | |||
j++; | |||
} | |||
var k; | |||
var b = true; | |||
while (b) { // Noncompliant; constant end condition | |||
k++; | |||
} | |||
</pre> | |||
<h2>Compliant Solution</h2> | |||
<pre class="diff-id-1 diff-noncompliant">while (true) { // break will potentially allow leaving the loop | |||
if (someCondition) { | |||
break; | |||
} | |||
} | |||
var k; | |||
var b = true; | |||
while (b) { | |||
k++; | |||
b = k < 10; | |||
} | |||
outer: | |||
while(true) { | |||
while(true) { | |||
break outer; | |||
} | |||
} | |||
</pre> |
@@ -0,0 +1,23 @@ | |||
Example with 2 completely different code snippets | |||
<h2>Noncompliant Code Example</h2> | |||
<pre class="diff-id-1 diff-noncompliant"> | |||
while (true) { // Noncompliant; constant end condition | |||
j++; | |||
} | |||
var k; | |||
var b = true; | |||
while (b) { // Noncompliant; constant end condition | |||
k++; | |||
} | |||
</pre> | |||
<h2>Compliant Solution</h2> | |||
<pre class="diff-id-1 diff-compliant">while (true) { // break will potentially allow leaving the loop | |||
var x = 3; | |||
for(let i=2; i<4; i++) { | |||
console.log("Hello there"); | |||
} | |||
alert('Click here'); | |||
thisIsNiceFunction(); | |||
</pre> |
@@ -0,0 +1,34 @@ | |||
<p>A <code>catch</code> clause that only rethrows the caught exception has the same effect as omitting the <code>catch</code> altogether and letting | |||
it bubble up automatically, but with more code and the additional detriment of leaving maintainers scratching their heads.</p> | |||
<p>Such clauses should either be eliminated or populated with the appropriate logic.</p> | |||
<h2>Noncompliant Code Example</h2> | |||
<pre class="diff-id-1 diff-noncompliant">try { | |||
doSomething(); | |||
} catch (ex) { // Noncompliant | |||
throw ex; | |||
} | |||
</pre> | |||
<h2>Compliant Solution</h2> | |||
<pre class="diff-id-1 diff-compliant">try { | |||
doSomething(); | |||
} catch (ex) { | |||
console.err(ex); | |||
throw ex; | |||
} | |||
</pre> | |||
<h2>Noncompliant Code Example</h2> | |||
<pre class="diff-id-2 diff-noncompliant">try { | |||
doSomethingElse(); | |||
} catch (ex) { // Noncompliant | |||
throw ex; | |||
} | |||
</pre> | |||
<h2>Compliant Solution</h2> | |||
<pre class="diff-id-2 diff-compliant">try { | |||
doSomethingElse(); | |||
} catch (ex) { | |||
console.err(ex); | |||
throw ex; | |||
} | |||
</pre> |
@@ -0,0 +1,46 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.education.sensors; | |||
import java.io.IOException; | |||
import org.junit.Test; | |||
import org.sonar.api.batch.fs.internal.DefaultFileSystem; | |||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | |||
import org.sonar.api.batch.sensor.internal.SensorContextTester; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.education.sensors.EducationWith2LinkedCodeSnippetsSensor.EDUCATION_WITH_2_LINKED_CODE_SNIPPETS_RULE_KEY; | |||
public class EducationWith2LinkedCodeSnippetsSensorTest extends EducationSensorTest { | |||
@Test | |||
public void processFile_givenCorrectTagPassed_oneSecurityHotspotWithContextsIsRaised() throws IOException { | |||
DefaultInputFile inputFile = newTestFile(EDUCATION_WITH_2_LINKED_CODE_SNIPPETS_RULE_KEY); | |||
DefaultFileSystem fs = new DefaultFileSystem(temp.newFolder()); | |||
fs.add(inputFile); | |||
SensorContextTester sensorContextTester = SensorContextTester.create(temp.newFolder().toPath()); | |||
var sensor = new EducationWith2LinkedCodeSnippetsSensor(fs, activeRules); | |||
sensor.execute(sensorContextTester); | |||
assertThat(sensorContextTester.allIssues()).hasSize(1); | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.education.sensors; | |||
import java.io.IOException; | |||
import org.junit.Test; | |||
import org.sonar.api.batch.fs.internal.DefaultFileSystem; | |||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | |||
import org.sonar.api.batch.sensor.internal.SensorContextTester; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.education.sensors.EducationWith4LinkedCodeSnippetsSensor.EDUCATION_WITH_4_LINKED_CODE_SNIPPETS_RULE_KEY; | |||
public class EducationWith4LinkedCodeSnippetsSensorTest extends EducationSensorTest { | |||
@Test | |||
public void processFile_givenCorrectTagPassed_oneSecurityHotspotWithContextsIsRaised() throws IOException { | |||
DefaultInputFile inputFile = newTestFile(EDUCATION_WITH_4_LINKED_CODE_SNIPPETS_RULE_KEY); | |||
DefaultFileSystem fs = new DefaultFileSystem(temp.newFolder()); | |||
fs.add(inputFile); | |||
SensorContextTester sensorContextTester = SensorContextTester.create(temp.newFolder().toPath()); | |||
var sensor = new EducationWith4LinkedCodeSnippetsSensor(fs, activeRules); | |||
sensor.execute(sensorContextTester); | |||
assertThat(sensorContextTester.allIssues()).hasSize(1); | |||
} | |||
} |