diff options
107 files changed, 2864 insertions, 422 deletions
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb index 3e9a2b3a64a..6d587793407 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb @@ -45,7 +45,7 @@ end %> <% - unless @resource.qualifier == 'DEV' or @resource.qualifier == 'SVW' + unless @resource.qualifier == 'DEV' or @resource.qualifier == 'VW' or @resource.qualifier == 'SVW' using_default=false quality_gate=Property.value('sonar.qualitygate', @resource && @resource.id, nil) unless quality_gate diff --git a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java index 8b4a9f23054..ba9163afecd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectReferentialsAction.java @@ -28,7 +28,6 @@ import org.sonar.api.server.ws.RequestHandler; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.batch.protocol.input.ProjectReferentials; -import org.sonar.batch.protocol.input.QProfile; import org.sonar.core.UtcDateUtils; import org.sonar.core.component.ComponentDto; import org.sonar.core.permission.GlobalPermissions; @@ -39,7 +38,11 @@ import org.sonar.core.properties.PropertyDto; import org.sonar.core.qualityprofile.db.QualityProfileDto; import org.sonar.server.db.DbClient; import org.sonar.server.plugins.MimeTypes; +import org.sonar.server.qualityprofile.ActiveRule; import org.sonar.server.qualityprofile.QProfileFactory; +import org.sonar.server.qualityprofile.QProfileLoader; +import org.sonar.server.rule.Rule; +import org.sonar.server.rule.RuleService; import org.sonar.server.user.UserSession; import java.util.List; @@ -54,12 +57,17 @@ public class ProjectReferentialsAction implements RequestHandler { private final DbClient dbClient; private final PropertiesDao propertiesDao; private final QProfileFactory qProfileFactory; + private final QProfileLoader qProfileLoader; + private final RuleService ruleService; private final Languages languages; - public ProjectReferentialsAction(DbClient dbClient, PropertiesDao propertiesDao, QProfileFactory qProfileFactory, Languages languages) { + public ProjectReferentialsAction(DbClient dbClient, PropertiesDao propertiesDao, QProfileFactory qProfileFactory, QProfileLoader qProfileLoader, + RuleService ruleService, Languages languages) { this.dbClient = dbClient; this.propertiesDao = propertiesDao; this.qProfileFactory = qProfileFactory; + this.qProfileLoader = qProfileLoader; + this.ruleService = ruleService; this.languages = languages; } @@ -88,6 +96,7 @@ public class ProjectReferentialsAction implements RequestHandler { String projectKey = request.mandatoryParam(PARAM_KEY); addSettings(ref, projectKey, hasScanPerm, session); addProfiles(ref, projectKey, session); + addActiveRules(ref); response.stream().setMediaType(MimeTypes.JSON); IOUtils.write(ref.toJson(), response.stream().output()); @@ -127,9 +136,31 @@ public class ProjectReferentialsAction implements RequestHandler { QualityProfileDto qualityProfileDto = qProfileFactory.getByProjectAndLanguage(session, projectKey, languageKey); qualityProfileDto = qualityProfileDto != null ? qualityProfileDto : qProfileFactory.getDefault(session, languageKey); if (qualityProfileDto != null) { - QProfile profile = new QProfile(qualityProfileDto.getKey(), qualityProfileDto.getName(), qualityProfileDto.getLanguage(), - UtcDateUtils.parseDateTime(qualityProfileDto.getRulesUpdatedAt())); - ref.addQProfile(profile); + ref.addQProfile(new org.sonar.batch.protocol.input.QProfile( + qualityProfileDto.getKey(), + qualityProfileDto.getName(), + qualityProfileDto.getLanguage(), + UtcDateUtils.parseDateTime(qualityProfileDto.getRulesUpdatedAt()))); + } + } + } + + private void addActiveRules(ProjectReferentials ref) { + for (org.sonar.batch.protocol.input.QProfile qProfile : ref.qProfiles()) { + for (ActiveRule activeRule : qProfileLoader.findActiveRulesByProfile(qProfile.key())) { + Rule rule = ruleService.getByKey(activeRule.key().ruleKey()); + org.sonar.batch.protocol.input.ActiveRule inputActiveRule = new org.sonar.batch.protocol.input.ActiveRule( + activeRule.key().ruleKey().repository(), + activeRule.key().ruleKey().rule(), + rule.name(), + activeRule.severity(), + rule.internalKey(), + qProfile.language() + ); + for (Map.Entry<String, String> entry : activeRule.params().entrySet()) { + inputActiveRule.addParam(entry.getKey(), entry.getValue()); + } + ref.addActiveRule(inputActiveRule); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java index fecade74cd9..474d7f42f47 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java @@ -58,7 +58,7 @@ public class RegisterQualityGates implements Startable { private void createBuiltinQualityGate() { QualityGateDto builtin = qualityGates.create(BUILTIN_QUALITY_GATE); qualityGates.createCondition(builtin.getId(), CoreMetrics.BLOCKER_VIOLATIONS_KEY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "0", null); - qualityGates.createCondition(builtin.getId(), CoreMetrics.CRITICAL_VIOLATIONS_KEY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "0", null); + qualityGates.createCondition(builtin.getId(), CoreMetrics.CRITICAL_VIOLATIONS_KEY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "0", 3); qualityGates.createCondition(builtin.getId(), CoreMetrics.TEST_ERRORS_KEY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "0", null); qualityGates.createCondition(builtin.getId(), CoreMetrics.TEST_FAILURES_KEY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "0", null); qualityGates.createCondition(builtin.getId(), CoreMetrics.NEW_COVERAGE_KEY, QualityGateConditionDto.OPERATOR_LESS_THAN, null, "80", 3); diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java index 0e7b1042e12..03af506f7b5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileFactory.java @@ -63,7 +63,7 @@ public class QProfileFactory implements ServerComponent { return profile; } - QualityProfileDto create(DbSession dbSession, QProfileName name) { + public QualityProfileDto create(DbSession dbSession, QProfileName name) { QualityProfileDto dto = db.qualityProfileDao().getByNameAndLanguage(name.getName(), name.getLanguage(), dbSession); if (dto != null) { throw new BadRequestException("Quality profile already exists: " + name); diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java index 6ce3e100b32..ae6bf7a2154 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java @@ -25,9 +25,15 @@ import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.index.query.*; +import org.elasticsearch.index.query.BoolFilterBuilder; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.FilterBuilder; +import org.elasticsearch.index.query.FilterBuilders; +import org.elasticsearch.index.query.MatchQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.query.SimpleQueryStringBuilder; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.bucket.terms.Terms; @@ -43,11 +49,22 @@ import org.sonar.core.profiling.StopWatch; import org.sonar.core.rule.RuleDto; import org.sonar.server.qualityprofile.index.ActiveRuleNormalizer; import org.sonar.server.rule.Rule; -import org.sonar.server.search.*; +import org.sonar.server.search.BaseIndex; +import org.sonar.server.search.ESNode; +import org.sonar.server.search.IndexDefinition; +import org.sonar.server.search.IndexField; +import org.sonar.server.search.QueryOptions; +import org.sonar.server.search.Result; import javax.annotation.CheckForNull; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import static com.google.common.collect.Lists.newArrayList; @@ -166,16 +183,11 @@ public class RuleIndex extends BaseIndex<Rule, RuleDto, RuleKey> { String queryString = query.getQueryText(); // Human readable type of querying - qb.should(QueryBuilders.queryString(query.getQueryText()) - .field(RuleNormalizer.RuleField.NAME.field() + "." + IndexField.SEARCH_WORDS_SUFFIX, 20f) - .field(RuleNormalizer.RuleField.HTML_DESCRIPTION.field() + "." + IndexField.SEARCH_WORDS_SUFFIX, 3f) - .enablePositionIncrements(true) - .defaultOperator(QueryStringQueryBuilder.Operator.AND) - .fuzziness(Fuzziness.ONE) - .autoGeneratePhraseQueries(true) - .lenient(false) - .useDisMax(true) - .boost(20f)); + qb.should(QueryBuilders.simpleQueryString(query.getQueryText()) + .field(RuleNormalizer.RuleField.NAME.field() + "." + IndexField.SEARCH_WORDS_SUFFIX, 20f) + .field(RuleNormalizer.RuleField.HTML_DESCRIPTION.field() + "." + IndexField.SEARCH_WORDS_SUFFIX, 3f) + .defaultOperator(SimpleQueryStringBuilder.Operator.AND) + ).boost(20f); // Match and partial Match queries qb.should(this.termQuery(RuleNormalizer.RuleField.KEY, queryString, 15f)); diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java index b118a340140..dba6cd180a7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleMapping.java @@ -105,23 +105,44 @@ public class RuleMapping extends BaseMapping<RuleDoc, RuleMappingContext> { private void mapDebtFields() { map("defaultDebtChar", new IndexStringMapper("defaultDebtChar", RuleNormalizer.RuleField.DEFAULT_CHARACTERISTIC.field())); map("defaultDebtSubChar", new IndexStringMapper("defaultDebtSubChar", RuleNormalizer.RuleField.DEFAULT_SUB_CHARACTERISTIC.field())); + map("debtChar", new IndexStringMapper("debtChar", RuleNormalizer.RuleField.CHARACTERISTIC.field(), RuleNormalizer.RuleField.DEFAULT_CHARACTERISTIC.field())); map("debtSubChar", new IndexStringMapper("debtSubChar", RuleNormalizer.RuleField.SUB_CHARACTERISTIC.field(), RuleNormalizer.RuleField.DEFAULT_SUB_CHARACTERISTIC.field())); + map("debtCharName", new CharacteristicNameMapper()); map("debtSubCharName", new SubCharacteristicNameMapper()); - map("debtRemFn", new IndexStringMapper("debtRemFnType", RuleNormalizer.RuleField.DEBT_FUNCTION_TYPE.field(), - RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_TYPE.field())); - map("debtRemFn", new IndexStringMapper("debtRemFnCoeff", RuleNormalizer.RuleField.DEBT_FUNCTION_COEFFICIENT.field(), - RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_COEFFICIENT.field())); - map("debtRemFn", new IndexStringMapper("debtRemFnOffset", RuleNormalizer.RuleField.DEBT_FUNCTION_OFFSET.field(), - RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_OFFSET.field())); + map("defaultDebtRemFn", new IndexStringMapper("defaultDebtRemFnType", RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_TYPE.field())); map("defaultDebtRemFn", new IndexStringMapper("defaultDebtRemFnCoeff", RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_COEFFICIENT.field())); map("defaultDebtRemFn", new IndexStringMapper("defaultDebtRemFnOffset", RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_OFFSET.field())); map("effortToFixDescription", RuleNormalizer.RuleField.FIX_DESCRIPTION.field()); map("debtOverloaded", new OverriddenMapper()); + + map("debtRemFn", new EffectiveDebtRemFn("debtRemFnType", RuleNormalizer.RuleField.DEBT_FUNCTION_TYPE.field(), + RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_TYPE.field())); + map("debtRemFn", new EffectiveDebtRemFn("debtRemFnCoeff", RuleNormalizer.RuleField.DEBT_FUNCTION_COEFFICIENT.field(), + RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_COEFFICIENT.field())); + map("debtRemFn", new EffectiveDebtRemFn("debtRemFnOffset", RuleNormalizer.RuleField.DEBT_FUNCTION_OFFSET.field(), + RuleNormalizer.RuleField.DEFAULT_DEBT_FUNCTION_OFFSET.field())); + } + + public static class EffectiveDebtRemFn extends IndexStringMapper<RuleDoc,RuleMappingContext> { + + public EffectiveDebtRemFn(String key, String indexKey, String defaultIndexKey) { + super(key, indexKey, defaultIndexKey); + } + + @Override + public void write(JsonWriter json, RuleDoc doc, RuleMappingContext context) { + if(doc.debtOverloaded()){ + Object val = doc.getNullableField(indexFields[0]); + json.prop(key, val != null ? val.toString() : null); + } else { + super.write(json,doc,context); + } + } } private void mapParamFields() { @@ -144,14 +165,12 @@ public class RuleMapping extends BaseMapping<RuleDoc, RuleMappingContext> { public void write(Rule rule, JsonWriter json, @Nullable SearchOptions options) { RuleMappingContext context = new RuleMappingContext(); - String characteristicKey; - if (needDebtCharacteristicNames(options) && (characteristicKey = rule.debtCharacteristicKey()) != null) { + if (needDebtCharacteristicNames(options) && rule.debtCharacteristicKey() != null) { // load debt characteristics if requested - context.add(debtModel.characteristicByKey(characteristicKey)); + context.add(debtModel.characteristicByKey(rule.debtCharacteristicKey())); } - String subCharacteristicKey; - if (needDebtSubCharacteristicNames(options) && (subCharacteristicKey = rule.debtSubCharacteristicKey()) != null) { - context.add(debtModel.characteristicByKey(subCharacteristicKey)); + if (needDebtSubCharacteristicNames(options) && rule.debtSubCharacteristicKey() != null) { + context.add(debtModel.characteristicByKey(rule.debtSubCharacteristicKey())); } doWrite((RuleDoc) rule, context, json, options); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/ws/BaseMapping.java b/server/sonar-server/src/main/java/org/sonar/server/search/ws/BaseMapping.java index 69a73937c6a..8a4096a47a8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/ws/BaseMapping.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/ws/BaseMapping.java @@ -133,7 +133,7 @@ public abstract class BaseMapping<DOC extends BaseDoc, CTX> implements ServerCom * String field */ public static class IndexStringMapper<DOC extends BaseDoc, CTX> extends IndexMapper<DOC,CTX> { - private final String key; + protected final String key; public IndexStringMapper(String key, String indexKey, String defaultIndexKey) { super(indexKey, defaultIndexKey); diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/BatchWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/BatchWsTest.java index 50a7c340705..1a497bb014a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/BatchWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/BatchWsTest.java @@ -32,6 +32,8 @@ import org.sonar.api.resources.Languages; import org.sonar.core.properties.PropertiesDao; import org.sonar.server.db.DbClient; import org.sonar.server.qualityprofile.QProfileFactory; +import org.sonar.server.qualityprofile.QProfileLoader; +import org.sonar.server.rule.RuleService; import org.sonar.server.ws.WsTester; import java.io.File; @@ -59,7 +61,7 @@ public class BatchWsTest { public void before() throws IOException { tester = new WsTester(new BatchWs(batchIndex, new GlobalReferentialsAction(mock(DbClient.class), mock(PropertiesDao.class)), - new ProjectReferentialsAction(mock(DbClient.class), mock(PropertiesDao.class), mock(QProfileFactory.class), mock(Languages.class)))); + new ProjectReferentialsAction(mock(DbClient.class), mock(PropertiesDao.class), mock(QProfileFactory.class), mock(QProfileLoader.class), mock(RuleService.class), mock(Languages.class)))); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java index a04fcc58cf6..1c5a6aa7bb5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectReferentialsActionTest.java @@ -20,6 +20,7 @@ package org.sonar.server.batch; +import com.google.common.collect.ImmutableMap; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,15 +28,22 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.resources.Language; import org.sonar.api.resources.Languages; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rule.Severity; import org.sonar.core.component.ComponentDto; import org.sonar.core.permission.GlobalPermissions; import org.sonar.core.persistence.DbSession; import org.sonar.core.properties.PropertiesDao; import org.sonar.core.properties.PropertyDto; +import org.sonar.core.qualityprofile.db.ActiveRuleKey; import org.sonar.core.qualityprofile.db.QualityProfileDto; import org.sonar.server.component.persistence.ComponentDao; import org.sonar.server.db.DbClient; +import org.sonar.server.qualityprofile.ActiveRule; import org.sonar.server.qualityprofile.QProfileFactory; +import org.sonar.server.qualityprofile.QProfileLoader; +import org.sonar.server.rule.Rule; +import org.sonar.server.rule.RuleService; import org.sonar.server.user.MockUserSession; import org.sonar.server.ws.WsTester; @@ -59,6 +67,12 @@ public class ProjectReferentialsActionTest { QProfileFactory qProfileFactory; @Mock + QProfileLoader qProfileLoader; + + @Mock + RuleService ruleService; + + @Mock Languages languages; @Mock @@ -75,7 +89,8 @@ public class ProjectReferentialsActionTest { when(language.getKey()).thenReturn("java"); when(languages.all()).thenReturn(new Language[]{language}); - tester = new WsTester(new BatchWs(mock(BatchIndex.class), mock(GlobalReferentialsAction.class), new ProjectReferentialsAction(dbClient, propertiesDao, qProfileFactory, languages))); + tester = new WsTester(new BatchWs(mock(BatchIndex.class), mock(GlobalReferentialsAction.class), + new ProjectReferentialsAction(dbClient, propertiesDao, qProfileFactory, qProfileLoader, ruleService, languages))); } @Test @@ -126,7 +141,6 @@ public class ProjectReferentialsActionTest { @Test public void return_quality_profiles() throws Exception { MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); - String projectKey = "org.codehaus.sonar:sonar"; when(qProfileFactory.getByProjectAndLanguage(session, projectKey, "java")).thenReturn( @@ -140,7 +154,6 @@ public class ProjectReferentialsActionTest { @Test public void return_quality_profile_from_default_profile() throws Exception { MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); - String projectKey = "org.codehaus.sonar:sonar"; when(qProfileFactory.getDefault(session, "java")).thenReturn( @@ -151,4 +164,29 @@ public class ProjectReferentialsActionTest { request.execute().assertJson(getClass(), "return_quality_profile_from_default_profile.json"); } + @Test + public void return_active_rules() throws Exception { + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION, GlobalPermissions.DRY_RUN_EXECUTION); + String projectKey = "org.codehaus.sonar:sonar"; + + when(qProfileFactory.getByProjectAndLanguage(session, projectKey, "java")).thenReturn( + QualityProfileDto.createFor("abcd").setName("Default").setLanguage("java").setRulesUpdatedAt("2014-01-14T14:00:00+0200") + ); + + RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle"); + ActiveRule activeRule = mock(ActiveRule.class); + when(activeRule.key()).thenReturn(ActiveRuleKey.of("abcd", ruleKey)); + when(activeRule.severity()).thenReturn(Severity.MINOR); + when(activeRule.params()).thenReturn(ImmutableMap.of("max", "2")); + when(qProfileLoader.findActiveRulesByProfile("abcd")).thenReturn(newArrayList(activeRule)); + + Rule rule = mock(Rule.class); + when(rule.name()).thenReturn("Avoid Cycle"); + when(rule.internalKey()).thenReturn("squid-1"); + when(ruleService.getByKey(ruleKey)).thenReturn(rule); + + WsTester.TestRequest request = tester.newGetRequest("batch", "project").setParam("key", projectKey); + request.execute().assertJson(getClass(), "return_active_rules.json"); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/RuleUpdaterMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/RuleUpdaterMediumTest.java index 35eabc87859..18fb0f0f6f7 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/RuleUpdaterMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/RuleUpdaterMediumTest.java @@ -244,7 +244,13 @@ public class RuleUpdaterMediumTest { assertThat(indexedRule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.CONSTANT_ISSUE); assertThat(indexedRule.debtRemediationFunction().coefficient()).isNull(); assertThat(indexedRule.debtRemediationFunction().offset()).isEqualTo("1min"); + assertThat(indexedRule.debtOverloaded()).isTrue(); + assertThat(indexedRule.defaultDebtCharacteristicKey()).isEqualTo("RELIABILITY"); + assertThat(indexedRule.defaultDebtSubCharacteristicKey()).isEqualTo("HARD_RELIABILITY"); + assertThat(indexedRule.defaultDebtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET); + assertThat(indexedRule.defaultDebtRemediationFunction().coefficient()).isEqualTo("1d"); + assertThat(indexedRule.defaultDebtRemediationFunction().offset()).isEqualTo("5min"); } @Test @@ -272,7 +278,47 @@ public class RuleUpdaterMediumTest { assertThat(indexedRule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR); assertThat(indexedRule.debtRemediationFunction().coefficient()).isEqualTo("2d"); assertThat(indexedRule.debtRemediationFunction().offset()).isNull(); + assertThat(indexedRule.debtOverloaded()).isTrue(); + assertThat(indexedRule.defaultDebtCharacteristicKey()).isEqualTo("RELIABILITY"); + assertThat(indexedRule.defaultDebtSubCharacteristicKey()).isEqualTo("HARD_RELIABILITY"); + assertThat(indexedRule.defaultDebtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR); + assertThat(indexedRule.defaultDebtRemediationFunction().coefficient()).isEqualTo("1d"); + assertThat(indexedRule.defaultDebtRemediationFunction().offset()).isNull(); + } + + @Test + public void override_debt_from_linear_with_offset_to_constant() throws Exception { + insertDebtCharacteristics(dbSession); + ruleDao.insert(dbSession, RuleTesting.newDto(RULE_KEY) + .setDefaultSubCharacteristicId(hardReliabilityId) + .setDefaultRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name()) + .setDefaultRemediationCoefficient("1d") + .setDefaultRemediationOffset("5min") + .setRemediationFunction(null) + .setRemediationCoefficient(null) + .setRemediationOffset(null)); + dbSession.commit(); + + RuleUpdate update = RuleUpdate.createForPluginRule(RULE_KEY) + .setDebtRemediationFunction(new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, null, "10min")); + updater.update(update, UserSession.get()); + dbSession.clearCache(); + + // verify debt is overridden + Rule indexedRule = tester.get(RuleIndex.class).getByKey(RULE_KEY); + assertThat(indexedRule.debtCharacteristicKey()).isEqualTo("RELIABILITY"); + assertThat(indexedRule.debtSubCharacteristicKey()).isEqualTo("HARD_RELIABILITY"); + assertThat(indexedRule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.CONSTANT_ISSUE); + assertThat(indexedRule.debtRemediationFunction().coefficient()).isNull(); + assertThat(indexedRule.debtRemediationFunction().offset()).isEqualTo("10min"); + + assertThat(indexedRule.debtOverloaded()).isTrue(); + assertThat(indexedRule.defaultDebtCharacteristicKey()).isEqualTo("RELIABILITY"); + assertThat(indexedRule.defaultDebtSubCharacteristicKey()).isEqualTo("HARD_RELIABILITY"); + assertThat(indexedRule.defaultDebtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET); + assertThat(indexedRule.defaultDebtRemediationFunction().coefficient()).isEqualTo("1d"); + assertThat(indexedRule.defaultDebtRemediationFunction().offset()).isEqualTo("5min"); } @Test @@ -301,7 +347,13 @@ public class RuleUpdaterMediumTest { assertThat(indexedRule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR); assertThat(indexedRule.debtRemediationFunction().coefficient()).isEqualTo("1d"); assertThat(indexedRule.debtRemediationFunction().offset()).isEqualTo("5min"); + assertThat(indexedRule.debtOverloaded()).isFalse(); + assertThat(indexedRule.defaultDebtCharacteristicKey()).isEqualTo("RELIABILITY"); + assertThat(indexedRule.defaultDebtSubCharacteristicKey()).isEqualTo("HARD_RELIABILITY"); + assertThat(indexedRule.defaultDebtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR); + assertThat(indexedRule.defaultDebtRemediationFunction().coefficient()).isEqualTo("1d"); + assertThat(indexedRule.defaultDebtRemediationFunction().offset()).isEqualTo("5min"); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java index 8d763d52e3e..3b7fdb0ed29 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java @@ -50,8 +50,12 @@ import org.sonar.server.search.Result; import org.sonar.server.tester.ServerTester; import javax.annotation.Nullable; - -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; @@ -609,7 +613,7 @@ public class RuleIndexMediumTest { // 4. get all active rules on profile result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()), + .setQProfileKey(qualityProfileDto2.getKey()), new QueryOptions()); assertThat(result.getHits()).hasSize(1); assertThat(result.getHits().get(0).name()).isEqualTo(rule1.getName()); @@ -646,7 +650,7 @@ public class RuleIndexMediumTest { ActiveRuleDto.createFor(qualityProfileDto2, rule3) .setSeverity("BLOCKER") .setInheritance(ActiveRule.Inheritance.INHERITED.name()) - ); + ); dbSession.commit(); @@ -668,77 +672,77 @@ public class RuleIndexMediumTest { // 3. get Inherited Rules on profile1 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto1.getKey()) - .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())), + .setQProfileKey(qualityProfileDto1.getKey()) + .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(0); // 4. get Inherited Rules on profile2 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()) - .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())), + .setQProfileKey(qualityProfileDto2.getKey()) + .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(2); // 5. get Overridden Rules on profile1 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto1.getKey()) - .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())), + .setQProfileKey(qualityProfileDto1.getKey()) + .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(0); // 6. get Overridden Rules on profile2 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()) - .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())), + .setQProfileKey(qualityProfileDto2.getKey()) + .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(1); // 7. get Inherited AND Overridden Rules on profile1 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto1.getKey()) - .setInheritance(ImmutableSet.of( - ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())), + .setQProfileKey(qualityProfileDto1.getKey()) + .setInheritance(ImmutableSet.of( + ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(0); // 8. get Inherited AND Overridden Rules on profile2 result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()) - .setInheritance(ImmutableSet.of( - ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())), + .setQProfileKey(qualityProfileDto2.getKey()) + .setInheritance(ImmutableSet.of( + ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(3); // 9. get rules active on profile1 with active severity BLOCKER result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto1.getKey()) - .setActiveSeverities(ImmutableSet.of( - Severity.BLOCKER.toString())), + .setQProfileKey(qualityProfileDto1.getKey()) + .setActiveSeverities(ImmutableSet.of( + Severity.BLOCKER.toString())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(3); // 10. get rules active on profile2 with active severity MINOR, then BLOCKER + MINOR result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()) - .setActiveSeverities(ImmutableSet.of( - Severity.MINOR.toString())), + .setQProfileKey(qualityProfileDto2.getKey()) + .setActiveSeverities(ImmutableSet.of( + Severity.MINOR.toString())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(1); result = index.search(new RuleQuery().setActivation(true) - .setQProfileKey(qualityProfileDto2.getKey()) - .setActiveSeverities(ImmutableSet.of( - Severity.BLOCKER.toString(), Severity.MINOR.toString())), + .setQProfileKey(qualityProfileDto2.getKey()) + .setActiveSeverities(ImmutableSet.of( + Severity.BLOCKER.toString(), Severity.MINOR.toString())), new QueryOptions() - ); + ); assertThat(result.getHits()).hasSize(3); } @@ -951,7 +955,24 @@ public class RuleIndexMediumTest { assertThat(index.search(availableSinceNowQuery, new QueryOptions()).getHits()).hasSize(0); } - private static List<String> ruleKeys(List<Rule> rules){ + @Test + public void search_protected_chars() throws InterruptedException { + String nameWithProtectedChars = "ja#va&sc\"r:ipt"; + + RuleDto ruleDto = RuleTesting.newXooX1().setName(nameWithProtectedChars); + dao.insert(dbSession, ruleDto); + dbSession.commit(); + + Rule rule = index.getByKey(RuleTesting.XOO_X1); + assertThat(rule.name()).isEqualTo(nameWithProtectedChars); + + RuleQuery protectedCharsQuery = new RuleQuery().setQueryText(nameWithProtectedChars); + List<Rule> results = index.search(protectedCharsQuery, new QueryOptions()).getHits(); + assertThat(results).hasSize(1); + assertThat(results.get(0).key()).isEqualTo(RuleTesting.XOO_X1); + } + + private static List<String> ruleKeys(List<Rule> rules) { return newArrayList(Iterables.transform(rules, new Function<Rule, String>() { @Override public String apply(@Nullable Rule input) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWebServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWebServiceMediumTest.java index 1fadc233225..fe2cd94eeaa 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWebServiceMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWebServiceMediumTest.java @@ -25,6 +25,7 @@ import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import org.sonar.api.rule.RuleKey; +import org.sonar.api.server.debt.DebtRemediationFunction; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.DateUtils; import org.sonar.core.persistence.DbSession; @@ -34,7 +35,9 @@ import org.sonar.core.qualityprofile.db.QualityProfileDao; import org.sonar.core.qualityprofile.db.QualityProfileDto; import org.sonar.core.rule.RuleDto; import org.sonar.core.rule.RuleParamDto; +import org.sonar.core.technicaldebt.db.CharacteristicDto; import org.sonar.server.db.DbClient; +import org.sonar.server.debt.DebtTesting; import org.sonar.server.qualityprofile.QProfileTesting; import org.sonar.server.qualityprofile.db.ActiveRuleDao; import org.sonar.server.rule.RuleTesting; @@ -61,13 +64,16 @@ public class RulesWebServiceMediumTest { private static final String API_SHOW_METHOD = "show"; private static final String API_TAGS_METHOD = "tags"; + DbClient db; RulesWebService ws; RuleDao ruleDao; DbSession session; + int reliabilityId, softReliabilityId, hardReliabilityId; @Before public void setUp() throws Exception { tester.clearDbAndIndexes(); + db = tester.get(DbClient.class); ruleDao = tester.get(RuleDao.class); ws = tester.get(RulesWebService.class); session = tester.get(DbClient.class).openSession(false); @@ -170,21 +176,101 @@ public class RulesWebServiceMediumTest { @Test public void search_debt_rules() throws Exception { + insertDebtCharacteristics(session); + ruleDao.insert(session, RuleTesting.newXooX1() - .setDefaultRemediationCoefficient("DefaultCoef") - .setDefaultRemediationFunction("DefaultFunction") - .setDefaultRemediationCoefficient("DefaultCoef") - .setDefaultSubCharacteristicId(1)); + .setDefaultSubCharacteristicId(hardReliabilityId) + .setDefaultRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name()) + .setDefaultRemediationCoefficient("1h") + .setDefaultRemediationOffset("15min") + + .setSubCharacteristicId(null) + .setRemediationFunction(null) + .setRemediationCoefficient(null) + .setRemediationOffset(null) + ); session.commit(); MockUserSession.set(); WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD); - request.setParam(SearchOptions.PARAM_FIELDS, "debtRemFn,debtChar,debtCharName"); + request.setParam(SearchOptions.PARAM_FIELDS, "debtChar,debtCharName,debtSubChar,debtSubCharName,debtRemFn,debtOverloaded,defaultDebtChar,defaultDebtSubChar,defaultDebtRemFn"); WsTester.Result result = request.execute(); result.assertJson(this.getClass(), "search_debt_rule.json"); } @Test + public void search_debt_rules_with_default_and_overridden_debt_values() throws Exception { + insertDebtCharacteristics(session); + + ruleDao.insert(session, RuleTesting.newXooX1() + .setDefaultSubCharacteristicId(hardReliabilityId) + .setDefaultRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name()) + .setDefaultRemediationCoefficient("1h") + .setDefaultRemediationOffset("15min") + + .setSubCharacteristicId(softReliabilityId) + .setRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name()) + .setRemediationCoefficient("2h") + .setRemediationOffset("25min") + ); + session.commit(); + + MockUserSession.set(); + WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD); + request.setParam(SearchOptions.PARAM_FIELDS, "debtChar,debtCharName,debtSubChar,debtSubCharName,debtRemFn,debtOverloaded,defaultDebtChar,defaultDebtSubChar,defaultDebtRemFn"); + WsTester.Result result = request.execute(); + result.assertJson(this.getClass(), "search_debt_rules_with_default_and_overridden_debt_values.json"); + } + + @Test + public void search_debt_rules_with_default_linear_offset_and_overridden_constant_debt() throws Exception { + insertDebtCharacteristics(session); + + ruleDao.insert(session, RuleTesting.newXooX1() + .setDefaultSubCharacteristicId(hardReliabilityId) + .setDefaultRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name()) + .setDefaultRemediationCoefficient("1h") + .setDefaultRemediationOffset("15min") + + .setSubCharacteristicId(softReliabilityId) + .setRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE.name()) + .setRemediationCoefficient(null) + .setRemediationOffset("5min") + ); + session.commit(); + + MockUserSession.set(); + WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD); + request.setParam(SearchOptions.PARAM_FIELDS, "debtChar,debtCharName,debtSubChar,debtSubCharName,debtRemFn,debtOverloaded,defaultDebtChar,defaultDebtSubChar,defaultDebtRemFn"); + WsTester.Result result = request.execute(); + result.assertJson(this.getClass(), "search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json"); + } + + @Test + public void search_debt_rules_with_default_linear_offset_and_overridden_linear_debt() throws Exception { + insertDebtCharacteristics(session); + + ruleDao.insert(session, RuleTesting.newXooX1() + .setDefaultSubCharacteristicId(hardReliabilityId) + .setDefaultRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name()) + .setDefaultRemediationCoefficient("1h") + .setDefaultRemediationOffset("15min") + + .setSubCharacteristicId(softReliabilityId) + .setRemediationFunction(DebtRemediationFunction.Type.LINEAR.name()) + .setRemediationCoefficient("1h") + .setRemediationOffset(null) + ); + session.commit(); + + MockUserSession.set(); + WsTester.TestRequest request = tester.wsTester().newGetRequest(API_ENDPOINT, API_SEARCH_METHOD); + request.setParam(SearchOptions.PARAM_FIELDS, "debtChar,debtCharName,debtSubChar,debtSubCharName,debtRemFn,debtOverloaded,defaultDebtChar,defaultDebtSubChar,defaultDebtRemFn"); + WsTester.Result result = request.execute(); + result.assertJson(this.getClass(), "search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json"); + } + + @Test public void search_template_rules() throws Exception { RuleDto templateRule = RuleTesting.newXooX1().setIsTemplate(true); ruleDao.insert(session, templateRule); @@ -430,4 +516,20 @@ public class RulesWebServiceMediumTest { .setInheritance(null) .setSeverity("BLOCKER"); } + + private void insertDebtCharacteristics(DbSession dbSession) { + CharacteristicDto reliability = DebtTesting.newCharacteristicDto("RELIABILITY").setName("Reliability"); + db.debtCharacteristicDao().insert(reliability, dbSession); + reliabilityId = reliability.getId(); + + CharacteristicDto softReliability = DebtTesting.newCharacteristicDto("SOFT_RELIABILITY").setName("Soft Reliability") + .setParentId(reliability.getId()); + db.debtCharacteristicDao().insert(softReliability, dbSession); + softReliabilityId = softReliability.getId(); + + CharacteristicDto hardReliability = DebtTesting.newCharacteristicDto("HARD_RELIABILITY").setName("Hard Reliability") + .setParentId(reliability.getId()); + db.debtCharacteristicDao().insert(hardReliability, dbSession); + hardReliabilityId = hardReliability.getId(); + } } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_active_rules.json b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_active_rules.json new file mode 100644 index 00000000000..f7f8faf299b --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/batch/ProjectReferentialsActionTest/return_active_rules.json @@ -0,0 +1,25 @@ +{ + "timestamp": 0, + "qprofilesByLanguage": { + "java": { + "key": "abcd", + "name": "Default", + "language": "java", + "rulesUpdatedAt": "Jan 14, 2014 1:00:00 PM" + } + }, + "activeRules": [ + { + "repositoryKey": "squid", + "ruleKey": "AvoidCycle", + "name": "Avoid Cycle", + "severity": "MINOR", + "internalKey": "squid-1", + "language": "java", + "params": { + "max" : "2" + } + } + ], + "settingsByModule": {} +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rule.json b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rule.json index 1505da0346b..3f83f0921a3 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rule.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rule.json @@ -1,8 +1,18 @@ {"total": 1, "p": 1, "ps": 10, "rules": [ { "key": "xoo:x1", - "debtRemFnType": "LINEAR", + "debtChar": "RELIABILITY", + "debtCharName": "Reliability", + "debtSubChar": "HARD_RELIABILITY", + "debtSubCharName": "Hard Reliability", + "debtRemFnType": "LINEAR_OFFSET", "debtRemFnCoeff": "1h", - "debtRemFnOffset": "5min" + "debtRemFnOffset": "15min", + "debtOverloaded": false, + "defaultDebtChar": "RELIABILITY", + "defaultDebtSubChar": "HARD_RELIABILITY", + "defaultDebtRemFnType": "LINEAR_OFFSET", + "defaultDebtRemFnCoeff": "1h", + "defaultDebtRemFnOffset": "15min" } ]} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_and_overridden_debt_values.json b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_and_overridden_debt_values.json new file mode 100644 index 00000000000..3aa6211c764 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_and_overridden_debt_values.json @@ -0,0 +1,18 @@ +{"total": 1, "p": 1, "ps": 10, "rules": [ + { + "key": "xoo:x1", + "debtChar": "RELIABILITY", + "debtCharName": "Reliability", + "debtSubChar": "SOFT_RELIABILITY", + "debtSubCharName": "Soft Reliability", + "debtRemFnType": "LINEAR_OFFSET", + "debtRemFnCoeff": "2h", + "debtRemFnOffset": "25min", + "debtOverloaded": true, + "defaultDebtChar": "RELIABILITY", + "defaultDebtSubChar": "HARD_RELIABILITY", + "defaultDebtRemFnType": "LINEAR_OFFSET", + "defaultDebtRemFnCoeff": "1h", + "defaultDebtRemFnOffset": "15min" + } +]} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json new file mode 100644 index 00000000000..6ae75abecb5 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json @@ -0,0 +1,17 @@ +{"total": 1, "p": 1, "ps": 10, "rules": [ + { + "key": "xoo:x1", + "debtChar": "RELIABILITY", + "debtCharName": "Reliability", + "debtSubChar": "SOFT_RELIABILITY", + "debtSubCharName": "Soft Reliability", + "debtRemFnType": "CONSTANT_ISSUE", + "debtRemFnOffset": "5min", + "debtOverloaded": true, + "defaultDebtChar": "RELIABILITY", + "defaultDebtSubChar": "HARD_RELIABILITY", + "defaultDebtRemFnType": "LINEAR_OFFSET", + "defaultDebtRemFnCoeff": "1h", + "defaultDebtRemFnOffset": "15min" + } +]} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json new file mode 100644 index 00000000000..b1c457e13c0 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json @@ -0,0 +1,17 @@ +{"total": 1, "p": 1, "ps": 10, "rules": [ + { + "key": "xoo:x1", + "debtChar": "RELIABILITY", + "debtCharName": "Reliability", + "debtSubChar": "SOFT_RELIABILITY", + "debtSubCharName": "Soft Reliability", + "debtRemFnType": "LINEAR", + "debtRemFnCoeff": "1h", + "debtOverloaded": true, + "defaultDebtChar": "RELIABILITY", + "defaultDebtSubChar": "HARD_RELIABILITY", + "defaultDebtRemFnType": "LINEAR_OFFSET", + "defaultDebtRemFnCoeff": "1h", + "defaultDebtRemFnOffset": "15min" + } +]} diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee index d1975327285..e222dcb4112 100644 --- a/server/sonar-web/Gruntfile.coffee +++ b/server/sonar-web/Gruntfile.coffee @@ -1,8 +1,4 @@ module.exports = (grunt) -> - grunt.loadNpmTasks('grunt-karma') - grunt.loadNpmTasks('grunt-express-server') - grunt.loadNpmTasks('grunt-casper') - pkg = grunt.file.readJSON('package.json') grunt.initConfig @@ -273,11 +269,20 @@ module.exports = (grunt) -> options: test: true 'no-colors': true + concise: true src: ['<%= pkg.sources %>js/tests/e2e/tests/**/*.js'] cw: options: test: true + parallel: true, + concurrency: 5 src: ['<%= pkg.sources %>js/tests/e2e/tests/component-viewer-spec.js'] + single: + options: + test: true + parallel: true, + concurrency: 5 + src: ['<%= pkg.sources %>js/tests/e2e/tests/<%= grunt.option("spec") %>-spec.js'] watch: @@ -313,6 +318,8 @@ module.exports = (grunt) -> grunt.loadNpmTasks 'grunt-contrib-clean' grunt.loadNpmTasks 'grunt-contrib-copy' grunt.loadNpmTasks 'grunt-contrib-concat' + grunt.loadNpmTasks 'grunt-express-server' + grunt.loadNpmTasks 'grunt-casper' # Define tasks @@ -333,3 +340,6 @@ module.exports = (grunt) -> grunt.registerTask 'cw', ['clean:js', 'coffee:build', 'handlebars:build', 'copy:js', 'concat:dev', 'express:test', 'casper:cw'] + + grunt.registerTask 'single', ['clean:js', 'coffee:build', 'handlebars:build', 'copy:js', 'concat:dev', + 'express:test', 'casper:single'] diff --git a/server/sonar-web/pom.xml b/server/sonar-web/pom.xml index 881f84e83f1..a90aad2498f 100644 --- a/server/sonar-web/pom.xml +++ b/server/sonar-web/pom.xml @@ -351,6 +351,7 @@ <sonar.log.console>true</sonar.log.console> <sonar.log.profilingLevel>BASIC</sonar.log.profilingLevel> <sonar.web.context>/dev</sonar.web.context> + <sonar.updatecenter.activate>false</sonar.updatecenter.activate> </systemProperties> </configuration> </plugin> diff --git a/server/sonar-web/src/main/coffee/component-viewer/app.coffee b/server/sonar-web/src/main/coffee/component-viewer/app.coffee index d091a396aa6..30d4c76ffc5 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/app.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/app.coffee @@ -5,7 +5,6 @@ requirejs.config 'backbone': 'third-party/backbone' 'backbone.marionette': 'third-party/backbone.marionette' 'handlebars': 'third-party/handlebars' - 'jquery.mockjax': 'third-party/jquery.mockjax' shim: 'backbone.marionette': diff --git a/server/sonar-web/src/main/coffee/component-viewer/header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header.coffee index 44fdef9b4e6..49d80413125 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/header.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/header.coffee @@ -155,16 +155,14 @@ define [ main: @options.main, state: @state, component: @component, settings: @settings, source: @model, header: @ @ui.expandedBar.addClass 'active' @ui.expandLinks.filter("[data-scope=#{scope}]").addClass 'active' - activeHeaderItem = @state.get 'activeHeaderItem' - if activeHeaderItem - @$(activeHeaderItem).addClass 'active' @options.main.fitIntoElement() enableBarItem: (item, silent = false) -> $item = @$(item) + console.log item, $item if $item.length > 0 - if silent then @$(item).addClass('active') else @$(item).click() + if silent then @$(item).addClass('active') else @$(item).click() else @options.main.hideAllLines() @@ -206,6 +204,14 @@ define [ @$('.component-viewer-header-time-changes').html '<i class="spinner spinner-margin"></i>' + unsetFilter: -> + @options.main.resetIssues() + @options.main.showAllLines() + @state.unset 'activeHeaderItem' + @$('.item.active').removeClass 'active' + @render() + + filterLines: (e, methodName, extra) -> @$('.component-viewer-header-expanded-bar-section-list .active').removeClass 'active' $(e.currentTarget).addClass 'active' diff --git a/server/sonar-web/src/main/coffee/component-viewer/header/basic-header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header/basic-header.coffee index e39ebeca502..09190ce58a2 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/header/basic-header.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/header/basic-header.coffee @@ -19,10 +19,12 @@ define [ filterByLines: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByLines' @state.set 'activeHeaderItem', '.js-filter-lines' filterByNcloc: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByNcloc' @state.set 'activeHeaderItem', '.js-filter-ncloc' diff --git a/server/sonar-web/src/main/coffee/component-viewer/header/coverage-header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header/coverage-header.coffee index 330f8b0f71a..b0258f1ac85 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/header/coverage-header.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/header/coverage-header.coffee @@ -48,61 +48,73 @@ define [ filterByLinesToCover: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByLinesToCover' @state.set 'activeHeaderItem', '.js-filter-lines-to-cover' filterByUncoveredLines: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByUncoveredLines' @state.set 'activeHeaderItem', '.js-filter-uncovered-lines' filterByBranchesToCover: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByBranchesToCover' @state.set 'activeHeaderItem', '.js-filter-branches-to-cover' filterByUncoveredBranches: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByUncoveredBranches' @state.set 'activeHeaderItem', '.js-filter-uncovered-branches' filterByLinesToCoverIT: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByLinesToCoverIT' @state.set 'activeHeaderItem', '.js-filter-lines-to-cover-it' filterByUncoveredLinesIT: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByUncoveredLinesIT' @state.set 'activeHeaderItem', '.js-filter-uncovered-lines-it' filterByBranchesToCoverIT: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByBranchesToCoverIT' @state.set 'activeHeaderItem', '.js-filter-branches-to-cover-it' filterByUncoveredBranchesIT: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByUncoveredBranchesIT' @state.set 'activeHeaderItem', '.js-filter-uncovered-branches-it' filterByLinesToCoverOverall: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByLinesToCoverOverall' @state.set 'activeHeaderItem', '.js-filter-lines-to-cover-overall' filterByUncoveredLinesOverall: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByUncoveredLinesOverall' @state.set 'activeHeaderItem', '.js-filter-uncovered-lines-overall' filterByBranchesToCoverOverall: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByBranchesToCoverOverall' @state.set 'activeHeaderItem', '.js-filter-branches-to-cover-overall' filterByUncoveredBranchesOverall: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByUncoveredBranchesOverall' @state.set 'activeHeaderItem', '.js-filter-uncovered-branches-overall' diff --git a/server/sonar-web/src/main/coffee/component-viewer/header/duplications-header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header/duplications-header.coffee index 8cb931878af..f0f4f3bba20 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/header/duplications-header.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/header/duplications-header.coffee @@ -8,6 +8,8 @@ define [ BaseHeaderView ) -> + $ = jQuery + class extends BaseHeaderView template: Templates['cw-duplications-header'] @@ -18,5 +20,6 @@ define [ filterByDuplications: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByDuplications' @state.set 'activeHeaderItem', '.js-filter-duplications' diff --git a/server/sonar-web/src/main/coffee/component-viewer/header/issues-header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header/issues-header.coffee index f7d24a90601..b765c2f3cc4 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/header/issues-header.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/header/issues-header.coffee @@ -63,62 +63,74 @@ define [ filterByCurrentIssue: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByCurrentIssue' @state.set 'activeHeaderItem', '.js-filter-current-issues' filterByAllIssues: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByAllIssues' @state.set 'activeHeaderItem', '.js-filter-all-issues' filterByFixedIssues: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByFixedIssues' @state.set 'activeHeaderItem', '.js-filter-fixed-issues' filterByUnresolvedIssues: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByUnresolvedIssues' @state.set 'activeHeaderItem', '.js-filter-unresolved-issues' filterByFalsePositiveIssues: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByFalsePositiveIssues' @state.set 'activeHeaderItem', '.js-filter-false-positive-issues' filterByOpenIssues: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByOpenIssues' @state.set 'activeHeaderItem', '.js-filter-open-issues' filterByRule: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') rule = $(e.currentTarget).data 'rule' @header.filterLines e, 'filterByRule', rule @state.set 'activeHeaderItem', ".js-filter-rule[data-rule='#{rule}']" filterByBlockerIssues: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByBlockerIssues' @state.set 'activeHeaderItem', '.js-filter-BLOCKER-issues' filterByCriticalIssues: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByCriticalIssues' @state.set 'activeHeaderItem', '.js-filter-CRITICAL-issues' filterByMajorIssues: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByMajorIssues' @state.set 'activeHeaderItem', '.js-filter-MAJOR-issues' filterByMinorIssues: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByMinorIssues' @state.set 'activeHeaderItem', '.js-filter-MINOR-issues' filterByInfoIssues: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') @header.filterLines e, 'filterByInfoIssues' @state.set 'activeHeaderItem', '.js-filter-INFO-issues' diff --git a/server/sonar-web/src/main/coffee/component-viewer/header/scm-header.coffee b/server/sonar-web/src/main/coffee/component-viewer/header/scm-header.coffee index 66b43636a47..a3f9c2f68a1 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/header/scm-header.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/header/scm-header.coffee @@ -19,6 +19,7 @@ define [ events: 'click .js-scm-time-changes': 'scmTimeChanges' + 'click .js-filter-modified-lines': 'filterBySCM' scmTimeChanges: (e) -> @@ -29,7 +30,13 @@ define [ main: @options.main bottom: true popup.render() - popup.on 'change', (period) => @main.enablePeriod period + popup.on 'change', (period) => @main.enablePeriod period, '.js-filter-modified-lines' + + + filterBySCM: (e) -> + return @header.unsetFilter() if $(e.currentTarget).is('.active') + @header.filterLines e, 'filterBySCM' + @state.set 'activeHeaderItem', '.js-filter-modified-lines' serializeData: -> diff --git a/server/sonar-web/src/main/coffee/component-viewer/main.coffee b/server/sonar-web/src/main/coffee/component-viewer/main.coffee index a7f4c7415a2..6dd645d853b 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/main.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/main.coffee @@ -16,8 +16,6 @@ define [ 'component-viewer/source' 'component-viewer/header' 'component-viewer/utils' - - 'component-viewer/mockjax' ], ( Backbone Marionette @@ -352,8 +350,7 @@ define [ $.when(@requestMeasures(@key, period?.get('key')), @requestIssuesPeriod(@key, period?.get('key')), @requestSCM(@key)).done => if activeHeaderItem? @state.set 'activeHeaderItem', activeHeaderItem - @headerView.render() - else @filterBySCM() + @headerView.render() addTransition: (transition, options) -> @@ -391,8 +388,10 @@ define [ filterByLines: -> + @resetIssues() @showAllLines() filterByNcloc: -> + @resetIssues() @_filterByLines (line) -> line?.executable diff --git a/server/sonar-web/src/main/coffee/component-viewer/mixins/main-coverage.coffee b/server/sonar-web/src/main/coffee/component-viewer/mixins/main-coverage.coffee index 63a4bc5f78a..971074c918f 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/mixins/main-coverage.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/mixins/main-coverage.coffee @@ -51,6 +51,7 @@ define [], () -> if @settings.get('issues') && !@state.get('hasIssues') requests.push @requestIssues @key $.when.apply($, requests).done => + @resetIssues() @_filterByCoverage(predicate) @@ -59,6 +60,7 @@ define [], () -> if @settings.get('issues') && !@state.get('hasIssues') requests.push @requestIssues @key $.when.apply($, requests).done => + @resetIssues() @_filterByCoverage(predicate) @@ -67,6 +69,7 @@ define [], () -> if @settings.get('issues') && !@state.get('hasIssues') requests.push @requestIssues @key $.when.apply($, requests).done => + @resetIssues() @_filterByCoverage(predicate) diff --git a/server/sonar-web/src/main/coffee/component-viewer/mixins/main-issues.coffee b/server/sonar-web/src/main/coffee/component-viewer/mixins/main-issues.coffee index 9feefc4770d..477420b5d29 100644 --- a/server/sonar-web/src/main/coffee/component-viewer/mixins/main-issues.coffee +++ b/server/sonar-web/src/main/coffee/component-viewer/mixins/main-issues.coffee @@ -20,8 +20,14 @@ define [ $.get API_ISSUES, options, (data) => @state.set 'hasIssues', true issues = _.sortBy data.issues, (issue) -> "#{issue.rule}_____#{issue.message}" + @source.set issues: issues + @resetIssues() + + + resetIssues: -> + issues = @source.get('issues') + if _.isArray issues @source.set - issues: issues activeIssues: issues.filter (issue) -> !issue.resolution diff --git a/server/sonar-web/src/main/coffee/component-viewer/mockjax.coffee b/server/sonar-web/src/main/coffee/component-viewer/mockjax.coffee deleted file mode 100644 index 65137174334..00000000000 --- a/server/sonar-web/src/main/coffee/component-viewer/mockjax.coffee +++ /dev/null @@ -1,27 +0,0 @@ -define ['third-party/jquery.mockjax'], -> - - jQuery.mockjaxSettings.contentType = 'text/json'; - jQuery.mockjaxSettings.responseTime = 250; - - jQuery.mockjax - url: "#{baseUrl}/api/sources/app" - responseText: JSON.stringify - key: 'org.codehaus.sonar:sonar-dev-maven-plugin:src/main/java/org/sonar/dev/UploadMojo.java' - path: 'src/main/java/org/sonar/dev/UploadMojo.java' - name: 'UploadMojo.java' - q: 'FIL' - fav: false - project: 'org.codehaus.sonar:sonar-dev-maven-plugin' - projectName: 'SonarQube Development Maven Plugin' - periods: [] - measures: - 'ncloc': 69 - 'coverage': '30%' - 'duplication_density': '7.4%' - 'debt': '3d 2h' - 'issues': 4 - 'blocker_issues': 1 - 'critical_issues': 2 - 'major_issues': 0 - 'minor_issues': 1 - 'info_issues': 0
\ No newline at end of file diff --git a/server/sonar-web/src/main/coffee/dashboard/file-app.coffee b/server/sonar-web/src/main/coffee/dashboard/file-app.coffee index 661562cef33..0e35c084710 100644 --- a/server/sonar-web/src/main/coffee/dashboard/file-app.coffee +++ b/server/sonar-web/src/main/coffee/dashboard/file-app.coffee @@ -5,7 +5,6 @@ requirejs.config 'backbone': 'third-party/backbone' 'backbone.marionette': 'third-party/backbone.marionette' 'handlebars': 'third-party/handlebars' - 'jquery.mockjax': 'third-party/jquery.mockjax' shim: 'backbone.marionette': diff --git a/server/sonar-web/src/main/coffee/drilldown/app.coffee b/server/sonar-web/src/main/coffee/drilldown/app.coffee index fc62830b711..c1179e3e05d 100644 --- a/server/sonar-web/src/main/coffee/drilldown/app.coffee +++ b/server/sonar-web/src/main/coffee/drilldown/app.coffee @@ -5,7 +5,6 @@ requirejs.config 'backbone': 'third-party/backbone' 'backbone.marionette': 'third-party/backbone.marionette' 'handlebars': 'third-party/handlebars' - 'jquery.mockjax': 'third-party/jquery.mockjax' shim: 'backbone.marionette': diff --git a/server/sonar-web/src/main/coffee/drilldown/conf.coffee b/server/sonar-web/src/main/coffee/drilldown/conf.coffee index b5f31642c18..db5ee65a97f 100644 --- a/server/sonar-web/src/main/coffee/drilldown/conf.coffee +++ b/server/sonar-web/src/main/coffee/drilldown/conf.coffee @@ -34,20 +34,20 @@ define 'new_it_uncovered_conditions': { tab: 'coverage', item: '.js-filter-uncovered-branches-it' } # Overall - 'overall_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover' } - 'overall_lines_to_cover': { tab: 'coverage', item: '.js-filter-lines-to-cover' } - 'overall_uncovered_lines': { tab: 'coverage', item: '.js-filter-uncovered-lines' } - 'overall_line_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover' } - 'overall_conditions_to_cover': { tab: 'coverage', item: '.js-filter-branches-to-cover' } - 'overall_uncovered_conditions': { tab: 'coverage', item: '.js-filter-uncovered-branches' } - 'overall_branch_coverage': { tab: 'coverage', item: '.js-filter-branches-to-cover' } - 'new_overall_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover' } - 'new_overall_uncovered_lines': { tab: 'coverage', item: '.js-filter-uncovered-lines' } - 'new_overall_line_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover' } - 'new_overall_lines_to_cover': { tab: 'coverage', item: '.js-filter-lines-to-cover' } - 'new_overall_branch_coverage': { tab: 'coverage', item: '.js-filter-branches-to-cover' } - 'new_overall_conditions_to_cover': { tab: 'coverage', item: '.js-filter-branches-to-cover' } - 'new_overall_uncovered_conditions': { tab: 'coverage', item: '.js-filter-uncovered-branches' } + 'overall_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' } + 'overall_lines_to_cover': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' } + 'overall_uncovered_lines': { tab: 'coverage', item: '.js-filter-uncovered-lines-overall' } + 'overall_line_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' } + 'overall_conditions_to_cover': { tab: 'coverage', item: '.js-filter-branches-to-cover-overall' } + 'overall_uncovered_conditions': { tab: 'coverage', item: '.js-filter-uncovered-branches-overall' } + 'overall_branch_coverage': { tab: 'coverage', item: '.js-filter-branches-to-cover-overall' } + 'new_overall_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' } + 'new_overall_uncovered_lines': { tab: 'coverage', item: '.js-filter-uncovered-lines-overall' } + 'new_overall_line_coverage': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' } + 'new_overall_lines_to_cover': { tab: 'coverage', item: '.js-filter-lines-to-cover-overall' } + 'new_overall_branch_coverage': { tab: 'coverage', item: '.js-filter-branches-to-cover-overall' } + 'new_overall_conditions_to_cover': { tab: 'coverage', item: '.js-filter-branches-to-cover-overall' } + 'new_overall_uncovered_conditions': { tab: 'coverage', item: '.js-filter-uncovered-branches-overall' } # Issues diff --git a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-detail.hbs b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-detail.hbs index f839bb8963c..b64e0c037ef 100644 --- a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-detail.hbs +++ b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-detail.hbs @@ -49,31 +49,6 @@ {{/if}} </ul> -{{#if subcharacteristic}} -<div class="coding-rules-subcharacteristic-more inline-help"> - <ul> - {{#if debtRemFnType}} - <li> - <h3>{{t 'coding_rules.remediation_function'}}</h3> - {{t 'coding_rules.remediation_function' debtRemFnType}} - </li> - {{/if}} - {{#if debtRemFnCoeff}} - <li> - <h3>{{t 'coding_rules.remediation_function.coeff'}}</h3> - {{debtRemFnCoeff}} - </li> - {{/if}} - {{#if debtRemFnOffset}} - <li> - <h3>{{t 'coding_rules.remediation_function.offset'}}</h3> - {{debtRemFnOffset}} - </li> - {{/if}} - </ul> -</div> -{{/if}} - <div class="coding-rules-detail-description rule-desc markdown">{{{htmlDesc}}}</div> {{#if isEditable}} diff --git a/server/sonar-web/src/main/hbs/component-viewer/cw-source.hbs b/server/sonar-web/src/main/hbs/component-viewer/cw-source.hbs index 79c44c4460f..e8f68051bf9 100644 --- a/server/sonar-web/src/main/hbs/component-viewer/cw-source.hbs +++ b/server/sonar-web/src/main/hbs/component-viewer/cw-source.hbs @@ -5,7 +5,7 @@ {{else}} {{#if state.duplicationsInDeletedFiles}} - <p class="marginbottom10">{{t 'duplications.dups_found_on_deleted_resource'}}</p> + <p class="marginbottom10 js-duplications-in-deleted-files">{{t 'duplications.dups_found_on_deleted_resource'}}</p> {{/if}} {{#if issuesLimitReached}} diff --git a/server/sonar-web/src/main/hbs/component-viewer/cw-workspace.hbs b/server/sonar-web/src/main/hbs/component-viewer/cw-workspace.hbs index 1c0137f9f12..781b1ceba55 100644 --- a/server/sonar-web/src/main/hbs/component-viewer/cw-workspace.hbs +++ b/server/sonar-web/src/main/hbs/component-viewer/cw-workspace.hbs @@ -14,28 +14,24 @@ <ul class="component-viewer-workspace-list"> {{#eachReverse workspace}} <li class="component-viewer-workspace-item {{#if active}}active{{/if}}"> - {{qualifierIcon component.q}} - <a class="link-action" data-key="{{component.key}}">{{component.name}}</a> <div class="text-ellipsis subtitle" title="{{component.projectName}} / {{component.subProjectName}}"> {{component.projectName}} / {{component.subProjectName}} </div> - {{#if component.dir}} - <div class="text-ellipsis subtitle">{{component.dir}}</div> - {{/if}} + {{#if component.dir}}<div class="text-ellipsis subtitle">{{component.dir}}</div>{{/if}} + {{qualifierIcon component.q}} <a class="link-action" data-key="{{component.key}}">{{component.name}}</a> + {{#if options}} <div class="component-viewer-workspace-transition">{{t 'component_viewer.transition' ../transition}}</div> <ul class="component-viewer-workspace-options"> {{#each options}} <li class="component-viewer-workspace-option text-ellipsis {{#if active}}active{{/if}}" title="{{name}}"> - <a class="link-action" data-workspace-key="{{../component.key}}" data-key="{{key}}">{{name}}</a> {{#if component}} <div class="text-ellipsis subtitle" title="{{component.projectName}} / {{component.subProjectName}}"> {{component.projectName}} / {{component.subProjectName}} </div> {{/if}} - {{#if subname}} - <div class="text-ellipsis subtitle" title="{{subname}}">{{subname}}</div> - {{/if}} + {{#if subname}}<div class="text-ellipsis subtitle" title="{{subname}}">{{subname}}</div>{{/if}} + <a class="link-action" data-workspace-key="{{../component.key}}" data-key="{{key}}">{{name}}</a> </li> {{/each}} </ul> diff --git a/server/sonar-web/src/main/js/navigator/filters/choice-filters.js b/server/sonar-web/src/main/js/navigator/filters/choice-filters.js index 3dec5f94708..b4553847159 100644 --- a/server/sonar-web/src/main/js/navigator/filters/choice-filters.js +++ b/server/sonar-web/src/main/js/navigator/filters/choice-filters.js @@ -31,7 +31,7 @@ define([ collection.each(function (item) { container.append( that.itemTemplate(_.extend(item.toJSON(), { - multiple: that.model.get('multiple') + multiple: that.model.get('multiple') && item.get('id')[0] !== '!' })) ); }); diff --git a/server/sonar-web/src/main/js/tests/e2e/lib.js b/server/sonar-web/src/main/js/tests/e2e/lib.js index 20ba1903c6c..6650aa509de 100644 --- a/server/sonar-web/src/main/js/tests/e2e/lib.js +++ b/server/sonar-web/src/main/js/tests/e2e/lib.js @@ -22,8 +22,10 @@ exports.initMessages = function () { exports.changeWorkingDirectory = function (dir) { + var commandLineArgs = require('system').args; // Since Casper has control, the invoked script is deep in the argument stack - var currentFile = require('system').args[4]; + // commandLineArgs = casper/bin/bootstrap.js,--casper-path=.../casperjs,--cli,--test,[file(s) under test],[options] + var currentFile = commandLineArgs[4]; var curFilePath = fs.absolute(currentFile).split(fs.separator); if (curFilePath.length > 1) { curFilePath.pop(); // PhantomJS does not have an equivalent path.baseName()-like method @@ -33,6 +35,16 @@ exports.changeWorkingDirectory = function (dir) { }; +exports.testName = function () { + var head = Array.prototype.slice.call(arguments, 0); + return function () { + var tail = Array.prototype.slice.call(arguments, 0), + body = head.concat(tail); + return body.join(' :: '); + }; +}; + + var mockRequest = function (url, response) { return casper.evaluate(function (url, response) { return jQuery.mockjax({ url: url, responseText: response}); @@ -74,3 +86,12 @@ exports.setDefaultViewport = function () { exports.capture = function (fileName) { casper.capture(fileName, { top: 0, left: 0, width: WINDOW_WIDTH, height: WINDOW_HEIGHT }); }; + + +exports.waitForElementCount = function (selector, count, callback) { + return casper.waitFor(function () { + return casper.evaluate(function (selector, count) { + return document.querySelectorAll(selector).length === count; + }, selector, count) + }, callback); +}; diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js index e2cf097fab7..bc7a0cb2fd6 100644 --- a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec.js @@ -1,11 +1,12 @@ -var lib = require('../lib'); +var lib = require('../lib'), + testName = lib.testName('Coding Rules'); lib.initMessages(); lib.changeWorkingDirectory('coding-rules-spec'); -casper.test.begin('Coding Rules - Readonly Tests', function suite(test) { +casper.test.begin(testName('Readonly Tests'), function suite(test) { var appId = null; var showId = null; @@ -44,6 +45,7 @@ casper.test.begin('Coding Rules - Readonly Tests', function suite(test) { test.assertSelectorHasText('ol.navigator-results-list li.active', 'BETA'); }); + casper.waitForSelector('h3.coding-rules-detail-header', function showFirstRule() { test.assertSelectorHasText('h3.coding-rules-detail-header', 'No empty line'); test.assertSelectorHasText('.navigator-details .subtitle', 'squid-xoo:x1'); @@ -53,7 +55,28 @@ casper.test.begin('Coding Rules - Readonly Tests', function suite(test) { test.assertSelectorHasText('.coding-rules-detail-property:nth-child(4)', 'Testability > Integration level testability'); test.assertSelectorHasText('.coding-rules-detail-property:nth-child(6)', 'SonarQube (Xoo)'); + + casper.click('.coding-rules-subcharacteristic'); + casper.waitForSelector('.coding-rules-debt-popup', function checkDebtPopup() { + test.assertElementCount('ul.bubble-popup-list li', 3); + test.assertSelectorHasText('.bubble-popup-list li:nth-child(1)', 'LINEAR_OFFSET'); + test.assertSelectorHasText('.bubble-popup-list li:nth-child(2)', '1h'); + test.assertSelectorHasText('.bubble-popup-list li:nth-child(3)', '30min'); + }); + + test.assertDoesntExist('button#coding-rules-detail-extend-description'); + + + casper.then(function checkParameters() { + test.assertElementCount('.coding-rules-detail-parameter', 3); + test.assertVisible('.coding-rules-detail-parameter-description[data-key=acceptWhitespace]'); + test.assertSelectorHasText('.coding-rules-detail-parameter-description[data-key=acceptWhitespace]', 'Accept whitespace'); + casper.click('.coding-rules-detail-parameter:nth-child(1) .coding-rules-detail-parameter-name'); + test.assertNotVisible('.coding-rules-detail-parameter-description[data-key=acceptWhitespace]'); + casper.click('.coding-rules-detail-parameter:nth-child(1) .coding-rules-detail-parameter-name'); + test.assertVisible('.coding-rules-detail-parameter-description[data-key=acceptWhitespace]'); + }); }); }); @@ -75,7 +98,7 @@ casper.test.begin('Coding Rules - Readonly Tests', function suite(test) { }); }); -casper.test.begin('Coding Rules - Admin Tests', function suite(test) { +casper.test.begin(testName('Admin Tests'), function suite(test) { var showId = null; var updateId = null; diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec/show_x1.json b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec/show_x1.json index e3c47578ff2..d360d7b4730 100644 --- a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec/show_x1.json +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-spec/show_x1.json @@ -38,7 +38,20 @@ { "key": "acceptWhitespace", "htmlDesc": "<h1>Accept whitespace (<code>\\s|\\t</code>) on the line\n</h1>This property is available so that a line containing only whitespace is not considered empty.<br/><h2>Example with property set to ``false``\n</h2><pre lang=\"xoo\"><code> <- One issue here\n<- And one here</code></pre><br/><br/><h2>Example with property set to ``true``\n</h2><pre lang=\"xoo\"><code> <- No issue here\n<- But one here</code></pre><br/>", + "type": "BOOLEAN", "defaultValue": "false" + }, + { + "key": "textParameter", + "htmlDesc": "This is a parameter of type TEXT", + "type": "TEXT", + "defaultValue": "false" + }, + { + "key": "skipLines", + "htmlDesc": "Skip a number of lines at the beginning of the file", + "type": "INTEGER", + "defaultValue": "0" } ] }, diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec.js b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec.js index 1058b8a1838..d01f7992644 100644 --- a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec.js +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec.js @@ -1,10 +1,11 @@ -var lib = require('../lib'); +var lib = require('../lib'), + testName = lib.testName('Component Viewer'); lib.initMessages(); lib.changeWorkingDirectory('component-viewer-spec'); -casper.test.begin('Component Viewer Base Tests', function (test) { +casper.test.begin(testName('Base'), function (test) { casper .start(lib.buildUrl('component-viewer#component=component'), function () { lib.setDefaultViewport(); @@ -12,24 +13,18 @@ casper.test.begin('Component Viewer Base Tests', function (test) { lib.mockRequest('/api/l10n/index', '{}'); lib.mockRequestFromFile('/api/components/app', 'app.json'); lib.mockRequestFromFile('/api/sources/show', 'source.json'); - lib.mockRequestFromFile('/api/issues/search', 'issues.json'); - lib.mockRequestFromFile('/api/coverage/show', 'coverage.json'); - lib.mockRequestFromFile('/api/duplications/show', 'duplications.json'); - lib.mockRequestFromFile('/api/sources/scm', 'scm.json'); - lib.mockRequest('*', '{}'); // Trick to see all ajax requests }) .then(function () { casper.waitForSelector('.component-viewer-source .row', function () { - // Check header elements - test.assertElementCount('.component-viewer-header', 1); + test.assertExists('.component-viewer-header'); test.assertSelectorContains('.component-viewer-header-component-project', 'SonarQube'); test.assertSelectorContains('.component-viewer-header-component-project', 'SonarQube :: Batch'); test.assertSelectorContains('.component-viewer-header-component-name', 'src/main/java/org/sonar/batch/index/Cache.java'); - test.assertElementCount('.component-viewer-header-favorite', 1); - test.assertElementCount('.component-viewer-header-actions', 1); + test.assertExists('.component-viewer-header-favorite'); + test.assertExists('.component-viewer-header-actions'); // Check main measures test.assertSelectorContains('.js-header-tab-basic', '379'); @@ -37,18 +32,42 @@ casper.test.begin('Component Viewer Base Tests', function (test) { test.assertSelectorContains('.js-header-tab-issues', '3h 30min'); test.assertSelectorContains('.js-header-tab-issues', '6'); test.assertSelectorContains('.js-header-tab-coverage', '74.3%'); - test.assertElementCount('.js-header-tab-scm', 1); + test.assertExists('.js-header-tab-scm'); // Check source test.assertElementCount('.component-viewer-source .row', 520); test.assertSelectorContains('.component-viewer-source', 'public class Cache'); // Check workspace - test.assertElementCount('.component-viewer-workspace', 1); + test.assertExists('.component-viewer-workspace'); }); }) - .then(function() { + .run(function () { + test.done(); + }); +}); + + +casper.test.begin(testName('Decoration'), function (test) { + casper + .start(lib.buildUrl('component-viewer#component=component'), function () { + lib.setDefaultViewport(); + + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/components/app', 'app.json'); + lib.mockRequestFromFile('/api/sources/show', 'source.json'); + lib.mockRequestFromFile('/api/issues/search', 'issues.json'); + lib.mockRequestFromFile('/api/coverage/show', 'coverage.json'); + lib.mockRequestFromFile('/api/duplications/show', 'duplications.json'); + lib.mockRequestFromFile('/api/sources/scm', 'scm.json'); + }) + + .then(function () { + casper.waitForSelector('.component-viewer-source .row'); + }) + + .then(function () { // Check issues decoration casper.click('.js-toggle-issues'); casper.waitForSelector('.code-issue', function () { @@ -57,7 +76,7 @@ casper.test.begin('Component Viewer Base Tests', function (test) { casper.click('.js-toggle-issues'); casper.waitWhileSelector('.code-issue', function () { - test.assertElementCount('.code-issue', 0); + test.assertDoesntExist('.code-issue'); }); }); }) @@ -74,7 +93,7 @@ casper.test.begin('Component Viewer Base Tests', function (test) { casper.click('.js-toggle-coverage'); casper.waitWhileSelector('.coverage-green', function () { - test.assertElementCount('.coverage-green', 0); + test.assertDoesntExist('.coverage-green'); }); }); }) @@ -87,7 +106,7 @@ casper.test.begin('Component Viewer Base Tests', function (test) { casper.click('.js-toggle-duplications'); casper.waitWhileSelector('.duplication-exists', function () { - test.assertElementCount('.duplication-exists', 0); + test.assertDoesntExist('.duplication-exists'); }); }); }) @@ -104,8 +123,148 @@ casper.test.begin('Component Viewer Base Tests', function (test) { casper.click('.js-toggle-scm'); casper.waitWhileSelector('.scm-author', function () { - test.assertElementCount('.scm-author', 0); - test.assertElementCount('.scm-date', 0); + test.assertDoesntExist('.scm-author'); + test.assertDoesntExist('.scm-date'); + }); + }); + }) + + .run(function () { + test.done(); + }); +}); + + +casper.test.begin(testName('Header'), function (test) { + casper + .start(lib.buildUrl('component-viewer#component=component'), function () { + lib.setDefaultViewport(); + + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/components/app', 'app.json'); + lib.mockRequestFromFile('/api/sources/show', 'source.json'); + lib.mockRequestFromFile('/api/resources', 'resources.json'); + }) + + .then(function () { + casper.waitForSelector('.component-viewer-source .row'); + }) + + .then(function () { + // Check issues header and filters + casper.click('.js-header-tab-issues'); + casper.waitForSelector('.js-filter-unresolved-issues', function () { + test.assertExists('.js-filter-open-issues'); + test.assertExists('.js-filter-fixed-issues'); + test.assertExists('.js-filter-false-positive-issues'); + test.assertSelectorContains('.js-filter-MAJOR-issues', '1'); + test.assertSelectorContains('.js-filter-MINOR-issues', '1'); + test.assertSelectorContains('.js-filter-INFO-issues', '4'); + test.assertSelectorContains('.js-filter-rule[data-rule="common-java:DuplicatedBlocks"]', '1'); + test.assertSelectorContains('.js-filter-rule[data-rule="squid:S1192"]', '1'); + test.assertSelectorContains('.js-filter-rule[data-rule="squid:S1135"]', '4'); + test.assertExists('.js-issues-time-changes'); + + casper.click('.js-header-tab-issues'); + casper.waitWhileSelector('.js-filter-unresolved-issues', function () { + test.assertDoesntExist('.js-filter-open-issues'); + test.assertDoesntExist('.js-filter-MAJOR-issues'); + test.assertDoesntExist('.js-filter-rule'); + }); + }); + }) + + .then(function () { + // Check coverage header and filters + casper.click('.js-header-tab-coverage'); + casper.waitForSelector('.js-filter-lines-to-cover', function () { + test.assertExists('.js-filter-uncovered-lines'); + test.assertExists('.js-filter-branches-to-cover'); + test.assertExists('.js-filter-uncovered-branches'); + test.assertSelectorContains('[data-metric="coverage"]', '74.3%'); + test.assertSelectorContains('[data-metric="line_coverage"]', '74.2%'); + test.assertSelectorContains('[data-metric="lines_to_cover"]', '194'); + test.assertSelectorContains('[data-metric="uncovered_lines"]', '50'); + test.assertSelectorContains('[data-metric="branch_coverage"]', '75.0%'); + test.assertSelectorContains('[data-metric="conditions_to_cover"]', '16'); + test.assertSelectorContains('[data-metric="uncovered_conditions"]', '4'); + test.assertExists('.js-coverage-time-changes'); + + casper.click('.js-header-tab-coverage'); + casper.waitWhileSelector('.js-filter-lines-to-cover', function () { + test.assertDoesntExist('.js-filter-uncovered-lines'); + test.assertDoesntExist('[data-metric="coverage"]'); + test.assertDoesntExist('[data-metric="branch_coverage"]'); + }); + }); + }) + + .then(function () { + // Check duplications header and filters + casper.click('.js-header-tab-duplications'); + casper.waitForSelector('.js-filter-duplications', function () { + test.assertSelectorContains('[data-metric="duplicated_blocks"]', '2'); + test.assertSelectorContains('[data-metric="duplicated_lines"]', '30'); + + casper.click('.js-header-tab-duplications'); + casper.waitWhileSelector('.js-filter-duplications', function () { + test.assertDoesntExist('[data-metric="duplicated_blocks"]'); + test.assertDoesntExist('[data-metric="duplicated_lines"]'); + }); + }); + }) + + .then(function () { + // Check scm header and filters + casper.click('.js-header-tab-scm'); + casper.waitForSelector('.js-filter-modified-lines', function () { + test.assertExists('.js-scm-time-changes'); + + casper.click('.js-header-tab-scm'); + casper.waitWhileSelector('.js-filter-modified-lines', function () { + test.assertDoesntExist('.js-scm-time-changes'); + }); + }); + }) + + .run(function () { + test.done(); + }); +}); + + +casper.test.begin(testName('Test File'), function (test) { + casper + .start(lib.buildUrl('component-viewer#component=component'), function () { + lib.setDefaultViewport(); + + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/components/app', 'tests/app.json'); + lib.mockRequestFromFile('/api/sources/show', 'tests/source.json'); + lib.mockRequestFromFile('/api/resources', 'tests/resources.json'); + lib.mockRequestFromFile('/api/tests/show', 'tests/tests.json'); + lib.mockRequestFromFile('/api/tests/covered_files', 'tests/covered-files.json'); + }) + + .then(function () { + casper.waitForSelector('.component-viewer-source .row'); + }) + + .then(function () { + // Check coverage header and filters + casper.click('.js-header-tab-tests'); + casper.waitForSelector('.js-unit-test', function () { + test.assertSelectorContains('[data-metric="test_execution_time"]', '12'); + test.assertElementCount('.js-unit-test', 2); + test.assertSelectorContains('.js-unit-test[data-name="should_return_i"]', 'should_return_i'); + test.assertSelectorContains('.js-unit-test[data-name="should_return_i"]', '5'); + test.assertSelectorContains('.js-unit-test[data-name="should_return_to_string"]', 'should_return_to_string'); + test.assertSelectorContains('.js-unit-test[data-name="should_return_to_string"]', '4'); + + casper.click('.js-unit-test[data-name="should_return_to_string"]'); + casper.waitForSelector('.bubble-popup', function () { + test.assertSelectorContains('.bubble-popup', 'Sample.java'); + test.assertSelectorContains('.bubble-popup', 'src/main/java/sample'); }); }); }) @@ -114,3 +273,261 @@ casper.test.begin('Component Viewer Base Tests', function (test) { test.done(); }); }); + + +casper.test.begin(testName('Go From Coverage to Test File'), function (test) { + casper + .start(lib.buildUrl('component-viewer#component=component'), function () { + lib.setDefaultViewport(); + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/components/app', 'app.json'); + lib.mockRequestFromFile('/api/sources/show', 'source.json'); + lib.mockRequestFromFile('/api/coverage/show', 'coverage.json'); + lib.mockRequestFromFile('/api/tests/test_cases', 'test-cases.json'); + }) + + .then(function () { + casper.waitForSelector('.component-viewer-source .row'); + }) + + .then(function () { + casper.click('.js-toggle-coverage'); + casper.waitForSelector('.coverage-green', function () { + casper.click('.coverage-green .coverage-tests'); + casper.waitForSelector('.bubble-popup', function () { + test.assertSelectorContains('.bubble-popup', 'src/test/java/org/sonar/batch/issue/IssueCacheTest.java'); + test.assertSelectorContains('.bubble-popup', 'should_update_existing_issue'); + test.assertSelectorContains('.bubble-popup li[title="should_update_existing_issue"]', '293'); + + lib.clearRequestMocks(); + lib.mockRequestFromFile('/api/components/app', 'tests/app.json'); + lib.mockRequestFromFile('/api/sources/show', 'tests/source.json'); + lib.mockRequestFromFile('/api/resources', 'tests/resources.json'); + lib.mockRequest('/api/coverage/show', '{}'); + lib.mockRequestFromFile('/api/tests/show', 'tests/tests.json'); + casper.click('.component-viewer-popup-test-file[data-key]'); + + casper.waitForSelector('.js-unit-test', function () { + test.assertElementCount('.js-unit-test', 2); + }); + }); + }); + }) + + .run(function () { + test.done(); + }); +}); + + +casper.test.begin(testName('Coverage Filters'), function (test) { + casper + .start(lib.buildUrl('component-viewer#component=component'), function () { + lib.setDefaultViewport(); + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/components/app', 'app.json'); + lib.mockRequestFromFile('/api/sources/show', 'source.json'); + lib.mockRequestFromFile('/api/resources', 'resources.json'); + lib.mockRequestFromFile('/api/coverage/show', 'coverage.json'); + }) + + .then(function () { + casper.waitForSelector('.component-viewer-source .row'); + }) + + .then(function () { + casper.click('.js-header-tab-coverage'); + casper.waitForSelector('.js-filter-lines-to-cover'); + }) + + .then(function () { + casper.click('.js-filter-lines-to-cover'); + casper.waitForSelector('.coverage-green', function () { + test.assertElementCount('.coverage-green', 149); + test.assertElementCount('.coverage-red', 51); + test.assertElementCount('.coverage-orange', 2); + test.assertElementCount('.component-viewer-source .row', 369); + }); + }) + + .then(function () { + casper.click('.js-filter-uncovered-lines'); + casper.waitForSelector('.coverage-green', function () { + test.assertElementCount('.coverage-green', 18); + test.assertElementCount('.coverage-red', 51); + test.assertElementCount('.coverage-orange', 0); + test.assertElementCount('.component-viewer-source .row', 136); + }); + }) + + .then(function () { + casper.click('.js-filter-branches-to-cover'); + casper.waitForSelector('.coverage-green', function () { + test.assertElementCount('.coverage-green', 26); + test.assertElementCount('.coverage-red', 4); + test.assertElementCount('.coverage-orange', 2); + test.assertElementCount('.component-viewer-source .row', 33); + }); + }) + + .then(function () { + casper.click('.js-filter-uncovered-branches'); + casper.waitForSelector('.coverage-green', function () { + test.assertElementCount('.coverage-green', 6); + test.assertElementCount('.coverage-red', 4); + test.assertElementCount('.coverage-orange', 2); + test.assertElementCount('.component-viewer-source .row', 13); + }); + }) + + .run(function () { + test.done(); + }); +}); + + +casper.test.begin(testName('Ability to Deselect Filters'), function (test) { + casper + .start(lib.buildUrl('component-viewer#component=component'), function () { + lib.setDefaultViewport(); + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/components/app', 'app.json'); + lib.mockRequestFromFile('/api/sources/show', 'source.json'); + lib.mockRequestFromFile('/api/resources', 'resources.json'); + lib.mockRequestFromFile('/api/issues/search', 'issues.json'); + lib.mockRequestFromFile('/api/coverage/show', 'coverage.json'); + lib.mockRequestFromFile('/api/duplications/show', 'duplications.json'); + }) + + .then(function () { + casper.waitForSelector('.component-viewer-source .row'); + }) + + .then(function () { + casper.click('.js-header-tab-issues'); + var testFilter = '.js-filter-unresolved-issues'; + casper.waitForSelector(testFilter, function () { + casper.click(testFilter); + lib.waitForElementCount('.component-viewer-source .row', 56, function () { + test.assertExists(testFilter + '.active'); + casper.click(testFilter); + lib.waitForElementCount('.component-viewer-source .row', 520, function () { + test.assertDoesntExist(testFilter + '.active'); + casper.click(testFilter); + lib.waitForElementCount('.component-viewer-source .row', 56, function () { + test.assertExists(testFilter + '.active'); + }); + }); + }) + }); + }) + + .then(function () { + casper.click('.js-header-tab-coverage'); + var testFilter = '.js-filter-lines-to-cover'; + casper.waitForSelector(testFilter, function () { + casper.click(testFilter); + lib.waitForElementCount('.component-viewer-source .row', 369, function () { + test.assertExists(testFilter + '.active'); + casper.click(testFilter); + lib.waitForElementCount('.component-viewer-source .row', 520, function () { + test.assertDoesntExist(testFilter + '.active'); + casper.click(testFilter); + lib.waitForElementCount('.component-viewer-source .row', 369, function () { + test.assertExists(testFilter + '.active'); + }); + }); + }) + }); + }) + + .then(function () { + casper.click('.js-header-tab-duplications'); + var testFilter = '.js-filter-duplications'; + casper.waitForSelector(testFilter, function () { + casper.click(testFilter); + lib.waitForElementCount('.component-viewer-source .row', 39, function () { + test.assertExists(testFilter + '.active'); + casper.click(testFilter); + lib.waitForElementCount('.component-viewer-source .row', 520, function () { + test.assertDoesntExist(testFilter + '.active'); + casper.click(testFilter); + lib.waitForElementCount('.component-viewer-source .row', 39, function () { + test.assertExists(testFilter + '.active'); + }); + }); + }) + }); + }) + + .run(function () { + test.done(); + }); +}); + + +casper.test.begin(testName('Cross-Project Duplications'), function (test) { + casper + .start(lib.buildUrl('component-viewer#component=component'), function () { + lib.setDefaultViewport(); + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/components/app', 'app.json'); + lib.mockRequestFromFile('/api/sources/show', 'source.json'); + lib.mockRequestFromFile('/api/resources', 'resources.json'); + lib.mockRequestFromFile('/api/duplications/show', 'cross-project-duplications.json'); + }) + + .then(function () { + casper.waitForSelector('.component-viewer-source .row'); + }) + + .then(function () { + casper.click('.js-header-tab-duplications'); + casper.waitForSelector('.js-filter-duplications', function () { + casper.click('.js-filter-duplications'); + casper.waitForSelector('.duplication-exists', function () { + casper.click('.duplication-exists'); + casper.waitForSelector('.bubble-popup', function () { + test.assertSelectorContains('.bubble-popup', 'JavaScript'); + test.assertSelectorContains('.bubble-popup', 'JavaScript :: Sonar Plugin'); + test.assertExists('a[data-key="org.codehaus.sonar-plugins.javascript:sonar-javascript-plugin:src/main/java/org/sonar/plugins/javascript/core/JavaScript.java"]'); + test.assertSelectorContains('.bubble-popup', 'src/main/java/org/sonar/plugins/javascript/core/JavaScript.java'); + test.assertSelectorContains('.bubble-popup', '455'); // Line from + test.assertSelectorContains('.bubble-popup', '470'); // Line to + }); + }); + }); + }) + + .run(function () { + test.done(); + }); +}); + + +casper.test.begin(testName('Duplications in Deleted Files'), function (test) { + casper + .start(lib.buildUrl('component-viewer#component=component'), function () { + lib.setDefaultViewport(); + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/components/app', 'app.json'); + lib.mockRequestFromFile('/api/sources/show', 'source.json'); + lib.mockRequestFromFile('/api/resources', 'resources.json'); + lib.mockRequestFromFile('/api/duplications/show', 'duplications-in-deleted-files.json'); + }) + + .then(function () { + casper.waitForSelector('.component-viewer-source .row'); + }) + + .then(function () { + casper.click('.js-toggle-duplications'); + casper.waitForSelector('.duplication-exists', function () { + test.assertExists('.js-duplications-in-deleted-files'); + }); + }) + + .run(function () { + test.done(); + }); +}); diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/cross-project-duplications.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/cross-project-duplications.json new file mode 100644 index 00000000000..cd445d5f5b2 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/cross-project-duplications.json @@ -0,0 +1,33 @@ +{"duplications": [ + { + "blocks": [ + { + "from": 404, + "size": 15, + "_ref": "1" + }, + { + "from": 455, + "size": 15, + "_ref": "2" + } + ] + } +], "files": { + "1": { + "key": "org.codehaus.sonar:sonar-batch:src/main/java/org/sonar/batch/index/Cache.java", + "name": "src/main/java/org/sonar/batch/index/Cache.java", + "project": "org.codehaus.sonar:sonar", + "projectName": "SonarQube", + "subProject": "org.codehaus.sonar:sonar-batch", + "subProjectName": "SonarQube :: Batch" + }, + "2": { + "key": "org.codehaus.sonar-plugins.javascript:sonar-javascript-plugin:src/main/java/org/sonar/plugins/javascript/core/JavaScript.java", + "name": "src/main/java/org/sonar/plugins/javascript/core/JavaScript.java", + "project": "org.codehaus.sonar-plugins.javascript:javascript", + "projectName": "JavaScript", + "subProject": "org.codehaus.sonar-plugins.javascript:sonar-javascript-plugin", + "subProjectName": "JavaScript :: Sonar Plugin" + } +}} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/duplications-in-deleted-files.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/duplications-in-deleted-files.json new file mode 100644 index 00000000000..9de23d1ce7d --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/duplications-in-deleted-files.json @@ -0,0 +1,24 @@ +{"duplications": [ + { + "blocks": [ + { + "from": 404, + "size": 15, + "_ref": "1" + }, + { + "from": 455, + "size": 15 + } + ] + } +], "files": { + "1": { + "key": "org.codehaus.sonar:sonar-batch:src/main/java/org/sonar/batch/index/Cache.java", + "name": "src/main/java/org/sonar/batch/index/Cache.java", + "project": "org.codehaus.sonar:sonar", + "projectName": "SonarQube", + "subProject": "org.codehaus.sonar:sonar-batch", + "subProjectName": "SonarQube :: Batch" + } +}} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/resources.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/resources.json new file mode 100644 index 00000000000..415718c7cd5 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/resources.json @@ -0,0 +1,154 @@ +[ + { + "id": 19983, + "key": "org.codehaus.sonar:sonar-batch:src/main/java/org/sonar/batch/index/Cache.java", + "name": "Cache.java", + "scope": "FIL", + "qualifier": "FIL", + "date": "2014-07-21T23:18:51+0200", + "creationDate": "2013-04-17T04:06:45+0200", + "lname": "src/main/java/org/sonar/batch/index/Cache.java", + "lang": "java", + "msr": [ + { + "key": "lines", + "val": 519.0, + "frmt_val": "519" + }, + { + "key": "ncloc", + "val": 379.0, + "frmt_val": "379" + }, + { + "key": "classes", + "val": 6.0, + "frmt_val": "6" + }, + { + "key": "functions", + "val": 56.0, + "frmt_val": "56" + }, + { + "key": "accessors", + "val": 0.0, + "frmt_val": "0" + }, + { + "key": "statements", + "val": 174.0, + "frmt_val": "174" + }, + { + "key": "public_api", + "val": 33.0, + "frmt_val": "33" + }, + { + "key": "comment_lines", + "val": 23.0, + "frmt_val": "23" + }, + { + "key": "comment_lines_density", + "val": 5.7, + "frmt_val": "5.7%" + }, + { + "key": "public_documented_api_density", + "val": 36.4, + "frmt_val": "36.4%" + }, + { + "key": "public_undocumented_api", + "val": 21.0, + "frmt_val": "21" + }, + { + "key": "complexity", + "val": 116.0, + "frmt_val": "116" + }, + { + "key": "function_complexity", + "val": 2.1, + "frmt_val": "2.1" + }, + { + "key": "coverage", + "val": 74.3, + "frmt_val": "74.3%" + }, + { + "key": "lines_to_cover", + "val": 194.0, + "frmt_val": "194" + }, + { + "key": "uncovered_lines", + "val": 50.0, + "frmt_val": "50" + }, + { + "key": "line_coverage", + "val": 74.2, + "frmt_val": "74.2%" + }, + { + "key": "conditions_to_cover", + "val": 16.0, + "frmt_val": "16" + }, + { + "key": "uncovered_conditions", + "val": 4.0, + "frmt_val": "4" + }, + { + "key": "branch_coverage", + "val": 75.0, + "frmt_val": "75.0%" + }, + { + "key": "duplicated_lines", + "val": 30.0, + "frmt_val": "30" + }, + { + "key": "duplicated_blocks", + "val": 2.0, + "frmt_val": "2" + }, + { + "key": "duplicated_files", + "val": 1.0, + "frmt_val": "1" + }, + { + "key": "duplicated_lines_density", + "val": 5.8, + "frmt_val": "5.8%" + }, + { + "key": "major_violations", + "val": 1.0, + "frmt_val": "1" + }, + { + "key": "minor_violations", + "val": 1.0, + "frmt_val": "1" + }, + { + "key": "info_violations", + "val": 4.0, + "frmt_val": "4" + }, + { + "key": "ncloc_data", + "data": "1=0;2=0;3=0;4=0;5=0;6=0;7=0;8=0;9=0;10=0;11=0;12=0;13=0;14=0;15=0;17=0;16=0;19=0;18=0;21=0;20=1;23=1;22=1;25=1;24=1;27=1;26=1;29=1;28=0;31=1;30=0;34=1;35=0;32=1;33=1;38=0;39=0;36=0;37=0;42=0;43=1;40=0;41=1;46=1;47=1;44=1;45=0;51=1;50=0;49=1;48=1;55=0;54=1;53=1;52=1;59=1;58=1;57=1;56=1;63=1;62=1;61=1;60=0;68=1;69=1;70=0;71=1;64=1;65=0;66=1;67=1;76=1;77=1;78=1;79=1;72=1;73=1;74=1;75=1;85=1;84=1;87=1;86=1;81=0;80=0;83=0;82=0;93=1;92=1;95=1;94=1;89=0;88=0;91=0;90=0;102=1;103=1;100=0;101=1;98=0;99=0;96=1;97=0;110=1;111=1;108=0;109=0;106=0;107=0;104=1;105=1;119=1;118=1;117=1;116=1;115=0;114=1;113=1;112=1;127=1;126=0;125=1;124=1;123=1;122=1;121=1;120=1;137=1;136=1;139=1;138=1;141=1;140=0;143=1;142=1;129=1;128=1;131=1;130=0;133=1;132=1;135=0;134=1;152=1;153=1;154=1;155=1;156=0;157=1;158=1;159=1;144=1;145=0;146=1;147=1;148=1;149=1;150=0;151=1;171=1;170=0;169=1;168=1;175=0;174=1;173=1;172=1;163=1;162=1;161=1;160=0;167=1;166=1;165=0;164=1;186=1;187=1;184=1;185=0;190=0;191=0;188=1;189=0;178=1;179=1;176=1;177=1;182=1;183=1;180=0;181=1;205=1;204=0;207=1;206=1;201=1;200=1;203=1;202=1;197=1;196=1;199=0;198=1;193=0;192=0;195=1;194=0;220=1;221=1;222=1;223=1;216=1;217=1;218=1;219=1;212=1;213=1;214=0;215=1;208=1;209=0;210=1;211=1;239=0;238=0;237=0;236=1;235=1;234=1;233=1;232=1;231=1;230=1;229=1;228=0;227=0;226=0;225=0;224=1;254=1;255=1;252=1;253=1;250=1;251=1;248=1;249=1;246=1;247=1;244=0;245=1;242=0;243=0;240=0;241=0;275=1;274=1;273=1;272=1;279=0;278=1;277=1;276=1;283=0;282=0;281=0;280=0;287=1;286=1;285=1;284=0;258=1;259=1;256=1;257=1;262=1;263=1;260=1;261=0;266=1;267=1;264=1;265=1;270=1;271=1;268=1;269=1;305=1;304=1;307=1;306=1;309=1;308=1;311=1;310=1;313=1;312=1;315=1;314=0;317=1;316=1;319=0;318=0;288=1;289=1;290=1;291=1;292=1;293=1;294=1;295=1;296=1;297=1;298=1;299=0;300=0;301=0;302=0;303=1;343=1;342=1;341=1;340=1;339=1;338=1;337=1;336=0;351=1;350=1;349=1;348=1;347=0;346=1;345=1;344=1;326=1;327=1;324=1;325=1;322=1;323=1;320=0;321=0;334=0;335=0;332=1;333=0;330=1;331=1;328=1;329=1;373=1;372=1;375=1;374=0;369=0;368=1;371=1;370=1;381=0;380=1;383=0;382=0;377=1;376=1;379=1;378=1;356=1;357=1;358=1;359=0;352=1;353=0;354=1;355=1;364=0;365=1;366=1;367=1;360=1;361=1;362=1;363=1;410=1;411=1;408=1;409=1;414=1;415=1;412=1;413=1;402=0;403=1;400=1;401=1;406=1;407=0;404=1;405=1;395=1;394=1;393=1;392=0;399=1;398=0;397=1;396=1;387=1;386=1;385=0;384=0;391=1;390=1;389=1;388=0;440=1;441=1;442=1;443=0;444=1;445=1;446=1;447=1;432=1;433=1;434=1;435=1;436=0;437=1;438=1;439=0;425=1;424=1;427=1;426=1;429=1;428=1;431=1;430=0;417=1;416=0;419=1;418=1;421=1;420=1;423=1;422=1;478=1;479=1;476=1;477=1;474=1;475=1;472=1;473=1;470=1;471=1;468=1;469=1;466=1;467=0;464=1;465=1;463=1;462=1;461=1;460=1;459=1;458=0;457=1;456=1;455=1;454=1;453=0;452=1;451=1;450=1;449=0;448=1;508=1;509=1;510=1;511=0;504=1;505=1;506=0;507=1;500=1;501=1;502=0;503=1;496=1;497=0;498=1;499=1;493=0;492=1;495=1;494=1;489=1;488=1;491=1;490=1;485=1;484=1;487=0;486=1;481=1;480=1;483=1;482=1;516=1;517=0;518=1;512=1;513=1;514=1;515=1" + } + ] + } +] diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/test-cases.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/test-cases.json new file mode 100644 index 00000000000..233229a803b --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/test-cases.json @@ -0,0 +1,235 @@ +{"tests": [ + { + "name": "should_update_existing_issue", + "status": "OK", + "durationInMs": 293, + "_ref": "1" + }, + { + "name": "testDistributionMeasure", + "status": "OK", + "durationInMs": 148, + "_ref": "2" + }, + { + "name": "one_part_key", + "status": "OK", + "durationInMs": 96, + "_ref": "3" + }, + { + "name": "testIssueExclusion", + "status": "OK", + "durationInMs": 305, + "_ref": "4" + }, + { + "name": "should_add_measure_with_same_metric", + "status": "OK", + "durationInMs": 141, + "_ref": "5" + }, + { + "name": "should_create_cache", + "status": "OK", + "durationInMs": 81, + "_ref": "6" + }, + { + "name": "scanProjectWithMixedSourcesAndTests", + "status": "OK", + "durationInMs": 239, + "_ref": "7" + }, + { + "name": "remove_versus_clear", + "status": "OK", + "durationInMs": 111, + "_ref": "3" + }, + { + "name": "should_persist_component_data", + "status": "OK", + "durationInMs": 132, + "_ref": "8" + }, + { + "name": "scanProjectWithSourceDir", + "status": "OK", + "durationInMs": 212, + "_ref": "7" + }, + { + "name": "should_add_measure_with_too_big_data_for_persistit", + "status": "OK", + "durationInMs": 336, + "_ref": "5" + }, + { + "name": "failForDuplicateInputFile", + "status": "OK", + "durationInMs": 127, + "_ref": "7" + }, + { + "name": "should_not_create_cache_twice", + "status": "OK", + "durationInMs": 101, + "_ref": "6" + }, + { + "name": "should_get_and_set_data", + "status": "OK", + "durationInMs": 190, + "_ref": "9" + }, + { + "name": "should_add_input_file", + "status": "OK", + "durationInMs": 122, + "_ref": "10" + }, + { + "name": "scanTempProject", + "status": "OK", + "durationInMs": 345, + "_ref": "4" + }, + { + "name": "should_get_measures", + "status": "OK", + "durationInMs": 114, + "_ref": "5" + }, + { + "name": "test_key_being_prefix_of_another_key", + "status": "OK", + "durationInMs": 94, + "_ref": "3" + }, + { + "name": "should_add_measure_with_big_data", + "status": "OK", + "durationInMs": 242, + "_ref": "5" + }, + { + "name": "should_get_all_issues", + "status": "OK", + "durationInMs": 104, + "_ref": "1" + }, + { + "name": "should_add_measure", + "status": "OK", + "durationInMs": 117, + "_ref": "5" + }, + { + "name": "computeMeasuresOnSampleProject", + "status": "OK", + "durationInMs": 315, + "_ref": "2" + }, + { + "name": "test_measure_coder", + "status": "OK", + "durationInMs": 110, + "_ref": "5" + }, + { + "name": "should_add_measure_with_too_big_data_for_persistit_pre_patch", + "status": "OK", + "durationInMs": 821, + "_ref": "5" + }, + { + "name": "three_parts_key", + "status": "OK", + "durationInMs": 122, + "_ref": "3" + }, + { + "name": "two_parts_key", + "status": "OK", + "durationInMs": 118, + "_ref": "3" + }, + { + "name": "should_add_new_issue", + "status": "OK", + "durationInMs": 95, + "_ref": "1" + }, + { + "name": "empty_cache", + "status": "OK", + "durationInMs": 104, + "_ref": "3" + }, + { + "name": "scanProjectWithTestDir", + "status": "OK", + "durationInMs": 322, + "_ref": "7" + }, + { + "name": "scanSampleProject", + "status": "OK", + "durationInMs": 184, + "_ref": "4" + }, + { + "name": "computeMeasuresOnTempProject", + "status": "OK", + "durationInMs": 274, + "_ref": "2" + }, + { + "name": "should_get_and_set_string_data", + "status": "OK", + "durationInMs": 95, + "_ref": "9" + } +], "files": { + "3": { + "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/index/CacheTest.java", + "longName": "src/test/java/org/sonar/batch/index/CacheTest.java" + }, + "1": { + "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/issue/IssueCacheTest.java", + "longName": "src/test/java/org/sonar/batch/issue/IssueCacheTest.java" + }, + "4": { + "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java", + "longName": "src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java" + }, + "2": { + "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java", + "longName": "src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java" + }, + "8": { + "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/index/ComponentDataPersisterTest.java", + "longName": "src/test/java/org/sonar/batch/index/ComponentDataPersisterTest.java" + }, + "9": { + "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/index/ComponentDataCacheTest.java", + "longName": "src/test/java/org/sonar/batch/index/ComponentDataCacheTest.java" + }, + "10": { + "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/scan/filesystem/InputFileCacheTest.java", + "longName": "src/test/java/org/sonar/batch/scan/filesystem/InputFileCacheTest.java" + }, + "7": { + "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java", + "longName": "src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java" + }, + "5": { + "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java", + "longName": "src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java" + }, + "6": { + "key": "org.codehaus.sonar:sonar-batch:src/test/java/org/sonar/batch/index/CachesTest.java", + "longName": "src/test/java/org/sonar/batch/index/CachesTest.java" + } +}} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/app.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/app.json new file mode 100644 index 00000000000..d2793968cb3 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/app.json @@ -0,0 +1,4 @@ +{"key": "com.sonarsource.it.samples:sample-with-tests:src/test/java/sample/SampleTest.java", "path": "src/test/java/sample/SampleTest.java", "name": "SampleTest.java", "longName": "src/test/java/sample/SampleTest.java", "q": "UTS", "project": "com.sonarsource.it.samples:sample-with-tests", "projectName": "Sonar :: Integration Tests :: Sample with tests", "fav": false, "canMarkAsFavourite": false, "canBulkChange": false, "canCreateManualIssue": false, "periods": [], "severities": [], "rules": [], "measures": { + "fTests": "2", + "fIssues": "0" +}} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/covered-files.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/covered-files.json new file mode 100644 index 00000000000..73f2e044078 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/covered-files.json @@ -0,0 +1,7 @@ +{"files": [ + { + "key": "com.sonarsource.it.samples:sample-with-tests:src/main/java/sample/Sample.java", + "longName": "src/main/java/sample/Sample.java", + "coveredLines": 5 + } +]} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/resources.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/resources.json new file mode 100644 index 00000000000..3490bce6f94 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/resources.json @@ -0,0 +1,25 @@ +[ + { + "id": 5, + "key": "com.sonarsource.it.samples:sample-with-tests:src/test/java/sample/SampleTest.java", + "name": "SampleTest.java", + "scope": "FIL", + "qualifier": "UTS", + "date": "2014-07-24T10:57:16+0200", + "creationDate": "2014-07-24T10:57:17+0200", + "lname": "src/test/java/sample/SampleTest.java", + "lang": "java", + "msr": [ + { + "key": "tests", + "val": 2.0, + "frmt_val": "2" + }, + { + "key": "test_execution_time", + "val": 12.0, + "frmt_val": "12 ms" + } + ] + } +] diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/source.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/source.json new file mode 100644 index 00000000000..02e9da776b2 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/source.json @@ -0,0 +1,24 @@ +{"sources": [ + [1, "<span class=\"k\">package</span> sample;"], + [2, ""], + [3, "<span class=\"k\">import</span> org.hamcrest.CoreMatchers;"], + [4, "<span class=\"k\">import</span> org.junit.Test;"], + [5, ""], + [6, "<span class=\"k\">import</span> <span class=\"k\">static</span> org.junit.Assert.assertThat;"], + [7, ""], + [8, "<span class=\"k\">public</span> <span class=\"k\">class</span> SampleTest {"], + [9, ""], + [10, " <span class=\"a\">@Test</span>"], + [11, " <span class=\"k\">public</span> <span class=\"k\">void</span> should_return_i() {"], + [12, " Sample sample = <span class=\"k\">new</span> Sample(1);"], + [13, " assertThat(sample.getI(), CoreMatchers.is(1));"], + [14, " }"], + [15, ""], + [16, " <span class=\"a\">@Test</span>"], + [17, " <span class=\"k\">public</span> <span class=\"k\">void</span> should_return_to_string() {"], + [18, " assertThat(<span class=\"k\">new</span> Sample(1).toString(), CoreMatchers.is(<span class=\"s\">\"1\"</span>));"], + [19, " }"], + [20, ""], + [21, "}"], + [22, ""] +]} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/tests.json b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/tests.json new file mode 100644 index 00000000000..f1ef789035c --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/component-viewer-spec/tests/tests.json @@ -0,0 +1,14 @@ +{"tests": [ + { + "name": "should_return_to_string", + "status": "OK", + "durationInMs": 1, + "coveredLines": 4 + }, + { + "name": "should_return_i", + "status": "OK", + "durationInMs": 11, + "coveredLines": 5 + } +]} diff --git a/server/sonar-web/src/main/js/tests/e2e/views/layouts/main.jade b/server/sonar-web/src/main/js/tests/e2e/views/layouts/main.jade index f00db74124a..6da4815d09a 100644 --- a/server/sonar-web/src/main/js/tests/e2e/views/layouts/main.jade +++ b/server/sonar-web/src/main/js/tests/e2e/views/layouts/main.jade @@ -9,7 +9,8 @@ html var $j = jQuery.noConflict(); window.suppressTranslationWarnings = true; jQuery.mockjaxSettings.contentType = 'text/json'; - jQuery.mockjaxSettings.responseTime = 250; + jQuery.mockjaxSettings.responseTime = 50; + jQuery.mockjaxSettings.throwUnmocked = true; $j(document).ready(function () { $j('.open-modal').modal(); }); block header body diff --git a/server/sonar-web/src/main/less/component-viewer.less b/server/sonar-web/src/main/less/component-viewer.less index 8ba6499c025..84ce9a28f63 100644 --- a/server/sonar-web/src/main/less/component-viewer.less +++ b/server/sonar-web/src/main/less/component-viewer.less @@ -70,6 +70,7 @@ white-space: nowrap; &.active > a { font-weight: bold; } + .subtitle { line-height: 16px; } } .component-viewer-workspace-item + .component-viewer-workspace-item { diff --git a/server/sonar-web/src/main/less/style.less b/server/sonar-web/src/main/less/style.less index e7b6275252d..8a607cfa136 100644 --- a/server/sonar-web/src/main/less/style.less +++ b/server/sonar-web/src/main/less/style.less @@ -565,7 +565,7 @@ table.form td img { height: 1px; } -table#project-history > tbody > tr > td { +table#project-history tr > td { vertical-align: top; } diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb index 5cf89173487..934cb04c518 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb @@ -155,9 +155,9 @@ if update_center_activated %> <li class="<%= 'active' if controller.controller_path=='updatecenter' -%>"> <a href="<%= ApplicationController.root_context -%>/updatecenter"><%= message('update_center.page') -%></a></li> - <li class="<%= 'active' if controller.controller_path=='system' -%>"> - <a href="<%= ApplicationController.root_context -%>/system"><%= message('system_info.page') -%></a></li> <% end %> + <li class="<%= 'active' if controller.controller_path=='system' -%>"> + <a href="<%= ApplicationController.root_context -%>/system"><%= message('system_info.page') -%></a></li> <% end #of admin part %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/history.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/history.html.erb index 1815d10ee92..e582b6e2e12 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/history.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/history.html.erb @@ -50,46 +50,56 @@ %> <tr class="<%= cycle 'even','odd' -%> hoverable snapshot"> + <%# Year %> <td class="thin nowrap"><b><%= time.year unless time.year == current_year -%></b></td> + <%# Month %> <td class="thin nowrap"><b><%= l(time, :format => '%B').capitalize unless time.month == current_month -%></b></td> + <%# Day %> <td class="thin nowrap"><%= l(time, :format => '%d') -%></td> + <%# Time %> <td class="thin nowrap"><%= l(time, :format => '%H:%M') -%></td> + <%# Version %> <td class="thin nowrap" style="padding-left: 20px;"> - <table class="width100"> - <tr id="version_<%= index -%>"> - <% if version_event %> - <td class="width100"><%= version_event.name if version_event -%></td> - <td class="small edit_actions" style="padding-left:20px"> - <a id="version_<%= index -%>_change" href="#" onclick="$j('#version_<%= index -%>').hide();$j('#version_<%= index -%>_form').show();$j('#version_name_<%= index -%>').focus();return false;"><%= message('project_history.rename_version') -%></a> - <% if version_event && !snapshot.islast? %> - <%= link_to( message('project_history.remove_version'), - { :action => 'delete_version', :sid => snapshot.id}, - :confirm => message('project_history.do_you_want_to_remove_version', :params => version_event.name) ) -%> - <% end %> - </td> - <% else %> - <td class="small edit_actions" colspan="2"> - <a id="version_<%= index -%>_change" href="#" onclick="$j('#version_<%= index -%>').hide();$j('#version_<%= index -%>_form').show();$j('#version_name_<%= index -%>').focus();return false;"><%= message('project_history.create_version') -%></a> - </td> + <table class="width100"> + <tr id="version_<%= index -%>"> + + <% if version_event %> + <td class="width100"><%= version_event.name if version_event -%></td> + <td class="small edit_actions" style="padding-left:20px"> + <a id="version_<%= index -%>_change" href="#" onclick="$j('#version_<%= index -%>').hide();$j('#version_<%= index -%>_form').show();$j('#version_name_<%= index -%>').focus();return false;"><%= message('project_history.rename_version') -%></a> + + <% if version_event && !snapshot.islast? %> + <%= link_to( message('project_history.remove_version'), + { :action => 'delete_version', :sid => snapshot.id}, + :confirm => message('project_history.do_you_want_to_remove_version', :params => version_event.name) ) -%> <% end %> - </tr> - <tr id="version_<%= index -%>_form" style="display:none;"> - <td coslpan="2" class="admin"> - <% form_tag( {:action => 'update_version', :sid => snapshot.id }) do -%> - <input id="version_name_<%= index -%>" name="version_name" type="text" value="<%= version_event ? version_event.name : '' -%>" - onKeyUp="if (this.value=='') $j('#save_version_<%= index -%>').disabled='true'; else $j('#save_version_<%= index -%>').disabled='';"/> - <%= submit_tag message('save'), :id => 'save_version_' + index.to_s %> - <a href="#" onclick="$j('#version_<%= index -%>').show();$j('#version_<%= index -%>_form').hide();"><%= message('cancel') -%></a> - <% end %> - </td> - </tr> - </table> + </td> + + <% else %> + <td class="small edit_actions" colspan="2"> + <a id="version_<%= index -%>_change" href="#" onclick="$j('#version_<%= index -%>').hide();$j('#version_<%= index -%>_form').show();$j('#version_name_<%= index -%>').focus();return false;"><%= message('project_history.create_version') -%></a> + </td> + + <% end %> + </tr> + <tr id="version_<%= index -%>_form" style="display:none;"> + <td coslpan="2" class="admin"> + <% form_tag( {:action => 'update_version', :sid => snapshot.id }) do -%> + <input id="version_name_<%= index -%>" name="version_name" type="text" value="<%= version_event ? version_event.name : '' -%>" + onKeyUp="if (this.value=='') $j('#save_version_<%= index -%>').disabled='true'; else $j('#save_version_<%= index -%>').disabled='';"/> + <%= submit_tag message('save'), :id => 'save_version_' + index.to_s %> + <a href="#" onclick="$j('#version_<%= index -%>').show();$j('#version_<%= index -%>_form').hide();"><%= message('cancel') -%></a> + <% end %> + </td> + </tr> + </table> </td> + <%# Events %> <td class="thin nowrap" style="padding-left: 20px;"> <table class="width100"> <% @@ -99,28 +109,26 @@ <tr id="event_<%= event_index -%>"> <td class="width100"><%= event.name -%></td> <td class="small edit_actions" style="padding-left:20px"> - <a id="event_<%= event_index -%>_change" href="#" onclick="$j('#event_<%= event_index -%>').hide();$j('#event_<%= event_index -%>_form').show();$j('#event_name_<%= event_index -%>').focus();return false;"><%= message('project_history.rename_event') -%></a> - <%= link_to( message('project_history.remove_version'), - { :action => 'delete_event', :id => event.id}, - :confirm => message('project_history.do_you_want_to_remove_version', :params => event.name) ) -%> + <a id="event_<%= event_index -%>_change" href="#" onclick="$j('#event_<%= event_index -%>').hide();$j('#event_<%= event_index -%>_form').show();$j('#event_name_<%= event_index -%>').focus();return false;"><%= message('project_history.rename_event') -%></a> + <%= link_to( message('project_history.remove_version'), + { :action => 'delete_event', :id => event.id}, + :confirm => message('project_history.do_you_want_to_remove_version', :params => event.name) ) -%> </td> </tr> <tr id="event_<%= event_index -%>_form" style="display:none;"> - <td colspan="2" class="admin"> - <% form_tag( {:action => 'update_event', :id => event.id }) do -%> - <input id="event_name_<%= event_index -%>" name="event_name" type="text" value="<%= event.name -%>" - onKeyUp="if (this.value=='') $j('#save_event_<%= event_index -%>').disabled='true'; else $j('#save_event_<%= event_index -%>').disabled='';"/> - <%= submit_tag message('save'), :id => 'save_event_' + event_index %> - <a href="#" onclick="$j('#event_<%= event_index -%>').show();$j('#event_<%= event_index -%>_form').hide();"><%= message('cancel') -%></a> - <% end %> - </td> + <td colspan="2" class="admin"> + <% form_tag( {:action => 'update_event', :id => event.id }) do -%> + <input id="event_name_<%= event_index -%>" name="event_name" type="text" value="<%= event.name -%>" + onKeyUp="if (this.value=='') $j('#save_event_<%= event_index -%>').disabled='true'; else $j('#save_event_<%= event_index -%>').disabled='';"/> + <%= submit_tag message('save'), :id => 'save_event_' + event_index %> + <a href="#" onclick="$j('#event_<%= event_index -%>').show();$j('#event_<%= event_index -%>_form').hide();"><%= message('cancel') -%></a> + <% end %> + </td> </tr> <% end %> <tr id="create_event_<%= index -%>"> - <td colspan="2" class="create_actions"> - <span class="small"> - <a id="create_event_<%= index -%>_change" href="#" onclick="$j('#create_event_<%= index -%>').hide();$j('#create_event_<%= index -%>_form').show();$j('#create_event_name_<%= index -%>').focus();return false;"><%= message('project_history.create_event') -%></a> - </span> + <td colspan="2" class="small create_actions"> + <a id="create_event_<%= index -%>_change" href="#" onclick="$j('#create_event_<%= index -%>').hide();$j('#create_event_<%= index -%>_form').show();$j('#create_event_name_<%= index -%>').focus();return false;"><%= message('project_history.create_event') -%></a> </td> </tr> <tr id="create_event_<%= index -%>_form" style="display:none;"> diff --git a/sonar-batch/src/main/java/org/sonar/batch/highlighting/DefaultHighlightingBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/highlighting/DefaultHighlightingBuilder.java new file mode 100644 index 00000000000..786ad0cca68 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/highlighting/DefaultHighlightingBuilder.java @@ -0,0 +1,52 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.highlighting; + +import com.google.common.base.Preconditions; +import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; +import org.sonar.batch.index.ComponentDataCache; +import org.sonar.core.source.SnapshotDataTypes; + +public class DefaultHighlightingBuilder implements HighlightingBuilder { + + private final SyntaxHighlightingDataBuilder builder; + private String componentKey; + private ComponentDataCache cache; + private boolean done = false; + + public DefaultHighlightingBuilder(String componentKey, ComponentDataCache cache) { + this.componentKey = componentKey; + this.cache = cache; + this.builder = new SyntaxHighlightingDataBuilder(); + } + + @Override + public HighlightingBuilder highlight(int startOffset, int endOffset, TypeOfText typeOfText) { + Preconditions.checkState(!done, "done() already called"); + builder.registerHighlightingRule(startOffset, endOffset, typeOfText.cssClass()); + return this; + } + + @Override + public void done() { + Preconditions.checkState(!done, "done() already called"); + cache.setData(componentKey, SnapshotDataTypes.SYNTAX_HIGHLIGHTING, builder.build()); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/source/SyntaxHighlightingData.java b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingData.java index cf0dde3580f..56c9b795ac9 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/source/SyntaxHighlightingData.java +++ b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingData.java @@ -17,7 +17,7 @@ * 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.batch.source; +package org.sonar.batch.highlighting; import org.sonar.batch.index.Data; @@ -34,6 +34,10 @@ public class SyntaxHighlightingData implements Data { this.syntaxHighlightingRuleSet = syntaxHighlightingRuleSet; } + public List<SyntaxHighlightingRule> syntaxHighlightingRuleSet() { + return syntaxHighlightingRuleSet; + } + @Override public String writeString() { StringBuilder sb = new StringBuilder(); @@ -51,8 +55,4 @@ public class SyntaxHighlightingData implements Data { return sb.toString(); } - @Override - public void readString(String s) { - throw new UnsupportedOperationException(); - } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/source/SyntaxHighlightingDataBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilder.java index 58dbd717a58..38ae875af98 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/source/SyntaxHighlightingDataBuilder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilder.java @@ -17,7 +17,7 @@ * 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.batch.source; +package org.sonar.batch.highlighting; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Predicate; @@ -26,6 +26,7 @@ import com.google.common.collect.Ordering; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; + import java.util.Collection; import java.util.List; @@ -83,11 +84,11 @@ public class SyntaxHighlightingDataBuilder { } @VisibleForTesting - protected List<SyntaxHighlightingRule> getSortedRules() { + public List<SyntaxHighlightingRule> getSortedRules() { Ordering<SyntaxHighlightingRule> ruleOrdering = new Ordering<SyntaxHighlightingRule>() { @Override public int compare(@Nullable SyntaxHighlightingRule left, - @Nullable SyntaxHighlightingRule right) { + @Nullable SyntaxHighlightingRule right) { int result; if (left != null && right != null) { result = left.getStartPosition() - right.getStartPosition(); @@ -100,6 +101,6 @@ public class SyntaxHighlightingDataBuilder { } }; - return ruleOrdering.immutableSortedCopy(syntaxHighlightingRuleSet); + return ruleOrdering.sortedCopy(syntaxHighlightingRuleSet); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/source/SyntaxHighlightingRule.java b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingRule.java index d8232c1ba9d..08985752d60 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/source/SyntaxHighlightingRule.java +++ b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingRule.java @@ -17,7 +17,7 @@ * 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.batch.source; +package org.sonar.batch.highlighting; import java.io.Serializable; diff --git a/sonar-batch/src/main/java/org/sonar/batch/highlighting/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/highlighting/package-info.java new file mode 100644 index 00000000000..93b92f3e9ae --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/highlighting/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.highlighting; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/ComponentDataCache.java b/sonar-batch/src/main/java/org/sonar/batch/index/ComponentDataCache.java index 16be9501117..0523212bf75 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/ComponentDataCache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/ComponentDataCache.java @@ -21,6 +21,8 @@ package org.sonar.batch.index; import org.sonar.api.BatchComponent; +import javax.annotation.CheckForNull; + public class ComponentDataCache implements BatchComponent { private final Cache cache; @@ -37,10 +39,12 @@ public class ComponentDataCache implements BatchComponent { return setData(componentKey, dataType, new StringData(data)); } + @CheckForNull public <D extends Data> D getData(String componentKey, String dataType) { return (D) cache.get(componentKey, dataType); } + @CheckForNull public String getStringData(String componentKey, String dataType) { Data data = (Data) cache.get(componentKey, dataType); return data == null ? null : ((StringData) data).data(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/Data.java b/sonar-batch/src/main/java/org/sonar/batch/index/Data.java index 66e5b596fc4..aa47c04e799 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/Data.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/Data.java @@ -25,6 +25,4 @@ public interface Data extends Serializable { String writeString(); - void readString(String s); - } diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/StringData.java b/sonar-batch/src/main/java/org/sonar/batch/index/StringData.java index 17605657112..6a88b5979b2 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/StringData.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/StringData.java @@ -37,9 +37,4 @@ public class StringData implements Data { public String writeString() { return data; } - - @Override - public void readString(String s) { - this.data = s; - } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java index c3aa2646c6c..c305474b7f2 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java @@ -20,14 +20,19 @@ package org.sonar.batch.mediumtest; import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.SonarPlugin; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.batch.debt.internal.DefaultDebtModel; import org.sonar.api.batch.fs.InputDir; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.InputPath; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.measure.Measure; +import org.sonar.api.batch.sensor.symbol.Symbol; import org.sonar.api.config.Settings; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Metric; @@ -36,6 +41,9 @@ import org.sonar.api.resources.Languages; import org.sonar.batch.bootstrap.PluginsReferential; import org.sonar.batch.bootstrapper.Batch; import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.batch.highlighting.SyntaxHighlightingData; +import org.sonar.batch.highlighting.SyntaxHighlightingRule; +import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.protocol.input.ActiveRule; import org.sonar.batch.protocol.input.GlobalReferentials; import org.sonar.batch.protocol.input.ProjectReferentials; @@ -47,8 +55,12 @@ import org.sonar.batch.scan2.AnalyzerMeasureCache; import org.sonar.batch.scan2.ProjectScanContainer; import org.sonar.batch.scan2.ScanTaskObserver; import org.sonar.batch.settings.SettingsReferential; +import org.sonar.batch.symbol.SymbolData; import org.sonar.core.plugins.DefaultPluginMetadata; import org.sonar.core.plugins.RemotePlugin; +import org.sonar.core.source.SnapshotDataTypes; + +import javax.annotation.CheckForNull; import java.io.File; import java.io.FileReader; @@ -59,6 +71,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; public class BatchMediumTester { @@ -200,13 +213,19 @@ public class BatchMediumTester { } public static class TaskResult implements ScanTaskObserver { + + private static final Logger LOG = LoggerFactory.getLogger(BatchMediumTester.TaskResult.class); + private List<Issue> issues = new ArrayList<Issue>(); private List<Measure> measures = new ArrayList<Measure>(); private List<InputFile> inputFiles = new ArrayList<InputFile>(); private List<InputDir> inputDirs = new ArrayList<InputDir>(); + private Map<InputFile, SyntaxHighlightingData> highlightingPerFile = new HashMap<InputFile, SyntaxHighlightingData>(); + private Map<InputFile, SymbolData> symbolTablePerFile = new HashMap<InputFile, SymbolData>(); @Override public void scanTaskCompleted(ProjectScanContainer container) { + LOG.info("Store analysis results in memory for later assertions in medium test"); for (Issue issue : container.getComponentByType(AnalyzerIssueCache.class).all()) { issues.add(issue); } @@ -223,6 +242,19 @@ public class BatchMediumTester { inputDirs.add((InputDir) inputPath); } } + + ComponentDataCache componentDataCache = container.getComponentByType(ComponentDataCache.class); + for (InputFile file : inputFiles) { + SyntaxHighlightingData highlighting = componentDataCache.getData(((DefaultInputFile) file).key(), SnapshotDataTypes.SYNTAX_HIGHLIGHTING); + if (highlighting != null) { + highlightingPerFile.put(file, highlighting); + } + SymbolData symbolTable = componentDataCache.getData(((DefaultInputFile) file).key(), SnapshotDataTypes.SYMBOL_HIGHLIGHTING); + if (symbolTable != null) { + symbolTablePerFile.put(file, symbolTable); + } + } + } public List<Issue> issues() { @@ -240,6 +272,43 @@ public class BatchMediumTester { public List<InputDir> inputDirs() { return inputDirs; } + + /** + * Get highlighting type at a given position in an inputfile + * @param charIndex 0-based offset in file + */ + @CheckForNull + public HighlightingBuilder.TypeOfText highlightingTypeFor(InputFile file, int charIndex) { + SyntaxHighlightingData syntaxHighlightingData = highlightingPerFile.get(file); + if (syntaxHighlightingData == null) { + return null; + } + for (SyntaxHighlightingRule sortedRule : syntaxHighlightingData.syntaxHighlightingRuleSet()) { + if (sortedRule.getStartPosition() <= charIndex && sortedRule.getEndPosition() > charIndex) { + return HighlightingBuilder.TypeOfText.forCssClass(sortedRule.getTextType()); + } + } + return null; + } + + /** + * Get list of all positions of a symbol in an inputfile + * @param symbolStartOffset 0-based start offset for the symbol in file + * @param symbolEndOffset 0-based end offset for the symbol in file + */ + @CheckForNull + public Set<Integer> symbolReferencesFor(InputFile file, int symbolStartOffset, int symbolEndOffset) { + SymbolData data = symbolTablePerFile.get(file); + if (data == null) { + return null; + } + for (Symbol symbol : data.referencesBySymbol().keySet()) { + if (symbol.getDeclarationStartOffset() == symbolStartOffset && symbol.getDeclarationEndOffset() == symbolEndOffset) { + return data.referencesBySymbol().get(symbol); + } + } + return null; + } } private static class FakeGlobalReferentialsLoader implements GlobalReferentialsLoader { diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java index a0c99598d31..1de8fd7201c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java @@ -23,15 +23,18 @@ import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputDir; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.InputPath; +import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.measure.Metric; import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.issue.IssueBuilder; import org.sonar.api.batch.sensor.issue.internal.DefaultIssueBuilder; import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.batch.sensor.measure.MeasureBuilder; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder; +import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder; import org.sonar.api.component.ResourcePerspectives; import org.sonar.api.config.Settings; import org.sonar.api.issue.Issuable; @@ -46,6 +49,9 @@ import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.Scopes; import org.sonar.api.rule.RuleKey; +import org.sonar.batch.highlighting.DefaultHighlightingBuilder; +import org.sonar.batch.index.ComponentDataCache; +import org.sonar.batch.symbol.DefaultSymbolTableBuilder; import java.io.Serializable; @@ -62,9 +68,10 @@ public class SensorContextAdaptor implements SensorContext { private Settings settings; private FileSystem fs; private ActiveRules activeRules; + private ComponentDataCache componentDataCache; public SensorContextAdaptor(org.sonar.api.batch.SensorContext sensorContext, MetricFinder metricFinder, Project project, ResourcePerspectives perspectives, - Settings settings, FileSystem fs, ActiveRules activeRules) { + Settings settings, FileSystem fs, ActiveRules activeRules, ComponentDataCache componentDataCache) { this.sensorContext = sensorContext; this.metricFinder = metricFinder; this.project = project; @@ -72,6 +79,7 @@ public class SensorContextAdaptor implements SensorContext { this.settings = settings; this.fs = fs; this.activeRules = activeRules; + this.componentDataCache = componentDataCache; } @Override @@ -236,4 +244,14 @@ public class SensorContextAdaptor implements SensorContext { .build(); } + @Override + public HighlightingBuilder highlightingBuilder(InputFile inputFile) { + return new DefaultHighlightingBuilder(((DefaultInputFile) inputFile).key(), componentDataCache); + } + + @Override + public SymbolTableBuilder symbolTableBuilder(InputFile inputFile) { + return new DefaultSymbolTableBuilder(((DefaultInputFile) inputFile).key(), componentDataCache); + } + } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalysisPublisher.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalysisPublisher.java index 757f320f559..7fb7dacf375 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalysisPublisher.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalysisPublisher.java @@ -44,9 +44,11 @@ public final class AnalysisPublisher { private final FileSystem fs; private final AnalyzerMeasureCache measureCache; private final ProjectDefinition def; - private AnalyzerIssueCache issueCache; + private final AnalyzerIssueCache issueCache; - public AnalysisPublisher(ProjectDefinition def, Settings settings, FileSystem fs, AnalyzerMeasureCache measureCache, AnalyzerIssueCache analyzerIssueCache) { + public AnalysisPublisher(ProjectDefinition def, Settings settings, FileSystem fs, + AnalyzerMeasureCache measureCache, + AnalyzerIssueCache analyzerIssueCache) { this.def = def; this.settings = settings; this.fs = fs; @@ -132,8 +134,9 @@ public final class AnalysisPublisher { for (Measure<?> measure : measureCache.byModule(def.getKey())) { jsonWriter.beginObject() .prop("metricKey", measure.metric().key()); - if (measure.inputFile() != null) { - jsonWriter.prop("filePath", measure.inputFile().relativePath()); + InputFile inputFile = measure.inputFile(); + if (inputFile != null) { + jsonWriter.prop("filePath", inputFile.relativePath()); } jsonWriter.prop("value", String.valueOf(measure.value())) .endObject(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerOptimizer.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerOptimizer.java index fb461854c69..4ae3ddfce09 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerOptimizer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerOptimizer.java @@ -19,16 +19,19 @@ */ package org.sonar.batch.scan2; -import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.BatchComponent; import org.sonar.api.batch.fs.FilePredicate; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; public class AnalyzerOptimizer implements BatchComponent { + private static final Logger LOG = LoggerFactory.getLogger(AnalyzerOptimizer.class); + private final FileSystem fs; private final ActiveRules activeRules; @@ -41,10 +44,15 @@ public class AnalyzerOptimizer implements BatchComponent { * Decide if the given Analyzer should be executed. */ public boolean shouldExecute(DefaultSensorDescriptor descriptor) { - // FS Conditions - boolean fsCondition = fsCondition(descriptor); - boolean activeRulesCondition = activeRulesCondition(descriptor); - return fsCondition && activeRulesCondition; + if (!fsCondition(descriptor)) { + LOG.debug("'{}' skipped because there is no related file in current project", descriptor.name()); + return false; + } + if (!activeRulesCondition(descriptor)) { + LOG.debug("'{}' skipped because there is no related rule activated in the quality profile", descriptor.name()); + return false; + } + return true; } private boolean activeRulesCondition(DefaultSensorDescriptor descriptor) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java index 6f4b2bdec70..dcd6efd1ca4 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java @@ -23,10 +23,12 @@ import com.google.common.base.Strings; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.measure.Metric; import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.rule.internal.DefaultActiveRule; import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.issue.IssueBuilder; import org.sonar.api.batch.sensor.issue.internal.DefaultIssue; @@ -35,11 +37,15 @@ import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.batch.sensor.measure.MeasureBuilder; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder; +import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder; import org.sonar.api.config.Settings; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.MessageException; +import org.sonar.batch.highlighting.DefaultHighlightingBuilder; +import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.issue.IssueFilters; import org.sonar.batch.scan.SensorContextAdaptor; +import org.sonar.batch.symbol.DefaultSymbolTableBuilder; import org.sonar.core.component.ComponentKeys; import java.io.Serializable; @@ -53,9 +59,10 @@ public class DefaultSensorContext implements SensorContext { private final FileSystem fs; private final ActiveRules activeRules; private final IssueFilters issueFilters; + private final ComponentDataCache componentDataCache; public DefaultSensorContext(ProjectDefinition def, AnalyzerMeasureCache measureCache, AnalyzerIssueCache issueCache, - Settings settings, FileSystem fs, ActiveRules activeRules, IssueFilters issueFilters) { + Settings settings, FileSystem fs, ActiveRules activeRules, IssueFilters issueFilters, ComponentDataCache componentDataCache) { this.def = def; this.measureCache = measureCache; this.issueCache = issueCache; @@ -63,6 +70,7 @@ public class DefaultSensorContext implements SensorContext { this.fs = fs; this.activeRules = activeRules; this.issueFilters = issueFilters; + this.componentDataCache = componentDataCache; } @Override @@ -155,7 +163,16 @@ public class DefaultSensorContext implements SensorContext { if (issue.severity() == null) { issue.setSeverity(activeRule.severity()); } + } + @Override + public HighlightingBuilder highlightingBuilder(InputFile inputFile) { + return new DefaultHighlightingBuilder(((DefaultInputFile) inputFile).key(), componentDataCache); + } + + @Override + public SymbolTableBuilder symbolTableBuilder(InputFile inputFile) { + return new DefaultSymbolTableBuilder(((DefaultInputFile) inputFile).key(), componentDataCache); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java index b0738e420e4..e13fca37d28 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java @@ -34,6 +34,7 @@ import org.sonar.batch.bootstrap.ExtensionInstaller; import org.sonar.batch.bootstrap.ExtensionMatcher; import org.sonar.batch.bootstrap.ExtensionUtils; import org.sonar.batch.index.Caches; +import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.languages.DefaultLanguagesReferential; import org.sonar.batch.profiling.PhasesSumUpTimeProfiler; import org.sonar.batch.referential.DefaultProjectReferentialsLoader; @@ -104,6 +105,8 @@ public class ProjectScanContainer extends ComponentContainer { // issues AnalyzerIssueCache.class, + ComponentDataCache.class, + ScanTaskObservers.class); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java index 14b6326b150..d437eadb6fd 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java @@ -19,13 +19,12 @@ */ package org.sonar.batch.scan2; -import org.sonar.api.batch.sensor.Sensor; -import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.BatchComponent; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; import org.sonar.batch.bootstrap.BatchExtensionDictionnary; import java.util.Collection; @@ -51,7 +50,6 @@ public class SensorsExecutor implements BatchComponent { analyzer.describe(descriptor); if (!optimizer.shouldExecute(descriptor)) { - LOG.debug("Analyzer skipped: " + descriptor.name()); continue; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/source/DefaultHighlightable.java b/sonar-batch/src/main/java/org/sonar/batch/source/DefaultHighlightable.java index 249450fad15..5b15a3abcd8 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/source/DefaultHighlightable.java +++ b/sonar-batch/src/main/java/org/sonar/batch/source/DefaultHighlightable.java @@ -21,12 +21,15 @@ package org.sonar.batch.source; import org.sonar.api.component.Component; import org.sonar.api.source.Highlightable; +import org.sonar.batch.highlighting.SyntaxHighlightingDataBuilder; import org.sonar.batch.index.ComponentDataCache; import org.sonar.core.source.SnapshotDataTypes; /** * @since 3.6 + * @deprecated since 4.5 no more used in batch 2.0 */ +@Deprecated public class DefaultHighlightable implements Highlightable { private final Component component; @@ -41,7 +44,7 @@ public class DefaultHighlightable implements Highlightable { @Override public HighlightingBuilder newHighlighting() { - return new DefaultHighlightingBuilder(); + return new DefaultHighlightingBuilder(component.key(), cache, builder); } @Override @@ -53,7 +56,17 @@ public class DefaultHighlightable implements Highlightable { return builder; } - private class DefaultHighlightingBuilder implements HighlightingBuilder { + private static class DefaultHighlightingBuilder implements HighlightingBuilder { + + private final SyntaxHighlightingDataBuilder builder; + private String componentKey; + private ComponentDataCache cache; + + public DefaultHighlightingBuilder(String componentKey, ComponentDataCache cache, SyntaxHighlightingDataBuilder builder) { + this.componentKey = componentKey; + this.cache = cache; + this.builder = builder; + } @Override public HighlightingBuilder highlight(int startOffset, int endOffset, String typeOfText) { @@ -63,7 +76,7 @@ public class DefaultHighlightable implements Highlightable { @Override public void done() { - cache.setStringData(component().key(), SnapshotDataTypes.SYNTAX_HIGHLIGHTING, builder.build().writeString()); + cache.setData(componentKey, SnapshotDataTypes.SYNTAX_HIGHLIGHTING, builder.build()); } } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/source/DefaultSymbolTable.java b/sonar-batch/src/main/java/org/sonar/batch/source/DefaultSymbolTable.java index 0d66c31b8f1..ee8997c259c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/source/DefaultSymbolTable.java +++ b/sonar-batch/src/main/java/org/sonar/batch/source/DefaultSymbolTable.java @@ -20,35 +20,35 @@ package org.sonar.batch.source; -import com.google.common.collect.Multimap; +import com.google.common.collect.SortedSetMultimap; import com.google.common.collect.TreeMultimap; +import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbol; import org.sonar.api.source.Symbol; import org.sonar.api.source.Symbolizable; +import org.sonar.batch.symbol.DefaultSymbolTableBuilder; -import java.io.Serializable; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; public class DefaultSymbolTable implements Symbolizable.SymbolTable { - private Multimap<Symbol, Integer> referencesBySymbol; + private SortedSetMultimap<org.sonar.api.batch.sensor.symbol.Symbol, Integer> referencesBySymbol; - private DefaultSymbolTable(Multimap<Symbol, Integer> referencesBySymbol) { + private DefaultSymbolTable(SortedSetMultimap<org.sonar.api.batch.sensor.symbol.Symbol, Integer> referencesBySymbol) { this.referencesBySymbol = referencesBySymbol; } - public static Builder builder() { - return new Builder(); - } - - public Multimap<Symbol, Integer> getReferencesBySymbol() { + public SortedSetMultimap<org.sonar.api.batch.sensor.symbol.Symbol, Integer> getReferencesBySymbol() { return referencesBySymbol; } @Override public List<Symbol> symbols() { - return new ArrayList<Symbol>(referencesBySymbol.keySet()); + List<Symbol> result = new ArrayList<Symbol>(); + for (org.sonar.api.batch.sensor.symbol.Symbol symbol : referencesBySymbol.keySet()) { + result.add((Symbol) symbol); + } + return result; } @Override @@ -58,15 +58,17 @@ public class DefaultSymbolTable implements Symbolizable.SymbolTable { public static class Builder implements Symbolizable.SymbolTableBuilder { - private final Multimap<Symbol, Integer> referencesBySymbol; + private final SortedSetMultimap<org.sonar.api.batch.sensor.symbol.Symbol, Integer> referencesBySymbol; + private final String componentKey; - public Builder() { - referencesBySymbol = TreeMultimap.create(new SymbolComparator(), new ReferenceComparator()); + public Builder(String componentKey) { + this.componentKey = componentKey; + referencesBySymbol = TreeMultimap.create(new DefaultSymbolTableBuilder.SymbolComparator(), new DefaultSymbolTableBuilder.ReferenceComparator()); } @Override public Symbol newSymbol(int fromOffset, int toOffset) { - Symbol symbol = new DefaultSymbol(fromOffset, toOffset); + Symbol symbol = new DefaultSymbol(componentKey, fromOffset, toOffset); referencesBySymbol.put(symbol, symbol.getDeclarationStartOffset()); return symbol; } @@ -84,24 +86,5 @@ public class DefaultSymbolTable implements Symbolizable.SymbolTable { return new DefaultSymbolTable(referencesBySymbol); } - private static class SymbolComparator implements Comparator<Symbol>, Serializable { - @Override - public int compare(Symbol left, Symbol right) { - return left.getDeclarationStartOffset() - right.getDeclarationStartOffset(); - } - } - - private static class ReferenceComparator implements Comparator<Integer>, Serializable { - @Override - public int compare(Integer left, Integer right) { - int result; - if (left != null & right != null) { - result = left - right; - } else { - result = left == null ? -1 : 1; - } - return result; - } - } } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/source/DefaultSymbolizable.java b/sonar-batch/src/main/java/org/sonar/batch/source/DefaultSymbolizable.java index 1e3b33d939e..de073e50629 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/source/DefaultSymbolizable.java +++ b/sonar-batch/src/main/java/org/sonar/batch/source/DefaultSymbolizable.java @@ -23,6 +23,7 @@ package org.sonar.batch.source; import org.sonar.api.component.Component; import org.sonar.api.source.Symbolizable; import org.sonar.batch.index.ComponentDataCache; +import org.sonar.batch.symbol.SymbolData; import org.sonar.core.source.SnapshotDataTypes; public class DefaultSymbolizable implements Symbolizable { @@ -42,12 +43,12 @@ public class DefaultSymbolizable implements Symbolizable { @Override public SymbolTableBuilder newSymbolTableBuilder() { - return new DefaultSymbolTable.Builder(); + return new DefaultSymbolTable.Builder(component.key()); } @Override public void setSymbolTable(SymbolTable symbolTable) { - SymbolData symbolData = new SymbolData(symbolTable); + SymbolData symbolData = new SymbolData(((DefaultSymbolTable) symbolTable).getReferencesBySymbol()); cache.setStringData(component().key(), SnapshotDataTypes.SYMBOL_HIGHLIGHTING, symbolData.writeString()); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/source/HighlightableBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/source/HighlightableBuilder.java index 82f5c8dd213..d20fbebb147 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/source/HighlightableBuilder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/source/HighlightableBuilder.java @@ -28,11 +28,14 @@ import org.sonar.core.component.PerspectiveBuilder; import org.sonar.core.component.ResourceComponent; import javax.annotation.CheckForNull; + import java.util.Set; /** * @since 3.6 + * @deprecated since 4.5 no more used in batch 2.0 */ +@Deprecated public class HighlightableBuilder extends PerspectiveBuilder<Highlightable> { private static final Set<String> SUPPORTED_QUALIFIERS = ImmutableSet.of(Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE); diff --git a/sonar-batch/src/main/java/org/sonar/batch/symbol/DefaultSymbolTableBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/symbol/DefaultSymbolTableBuilder.java new file mode 100644 index 00000000000..ed347e60bd0 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/symbol/DefaultSymbolTableBuilder.java @@ -0,0 +1,90 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.symbol; + +import com.google.common.collect.SortedSetMultimap; +import com.google.common.collect.TreeMultimap; +import org.sonar.api.batch.sensor.symbol.Symbol; +import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder; +import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbol; +import org.sonar.batch.index.ComponentDataCache; +import org.sonar.core.source.SnapshotDataTypes; + +import java.io.Serializable; +import java.util.Comparator; + +public class DefaultSymbolTableBuilder implements SymbolTableBuilder { + + private final String componentKey; + private final ComponentDataCache cache; + private final SortedSetMultimap<Symbol, Integer> referencesBySymbol; + + public DefaultSymbolTableBuilder(String componentKey, ComponentDataCache cache) { + this.componentKey = componentKey; + this.cache = cache; + this.referencesBySymbol = TreeMultimap.create(new SymbolComparator(), new ReferenceComparator()); + } + + @Override + public Symbol newSymbol(int fromOffset, int toOffset) { + Symbol symbol = new DefaultSymbol(componentKey, fromOffset, toOffset); + referencesBySymbol.put(symbol, symbol.getDeclarationStartOffset()); + return symbol; + } + + @Override + public void newReference(Symbol symbol, int fromOffset) { + String otherComponentKey = ((DefaultSymbol) symbol).componentKey(); + if (!otherComponentKey.equals(componentKey)) { + throw new UnsupportedOperationException("Cannot add reference from (" + componentKey + ") to another file (" + otherComponentKey + ")"); + } + if (fromOffset >= symbol.getDeclarationStartOffset() && fromOffset < symbol.getDeclarationEndOffset()) { + throw new UnsupportedOperationException("Cannot add reference (" + fromOffset + ") overlapping " + symbol); + } + referencesBySymbol.put(symbol, fromOffset); + } + + @Override + public void done() { + SymbolData symbolData = new SymbolData(referencesBySymbol); + cache.setData(componentKey, SnapshotDataTypes.SYMBOL_HIGHLIGHTING, symbolData); + } + + public static class SymbolComparator implements Comparator<Symbol>, Serializable { + @Override + public int compare(Symbol left, Symbol right) { + return left.getDeclarationStartOffset() - right.getDeclarationStartOffset(); + } + } + + public static class ReferenceComparator implements Comparator<Integer>, Serializable { + @Override + public int compare(Integer left, Integer right) { + int result; + if (left != null & right != null) { + result = left - right; + } else { + result = left == null ? -1 : 1; + } + return result; + } + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/source/SymbolData.java b/sonar-batch/src/main/java/org/sonar/batch/symbol/SymbolData.java index 38ed3ed42f9..11ab5cca7e5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/source/SymbolData.java +++ b/sonar-batch/src/main/java/org/sonar/batch/symbol/SymbolData.java @@ -18,11 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.batch.source; +package org.sonar.batch.symbol; -import com.google.common.collect.Multimap; -import org.sonar.api.source.Symbol; -import org.sonar.api.source.Symbolizable; +import com.google.common.collect.SortedSetMultimap; +import org.sonar.api.batch.sensor.symbol.Symbol; import org.sonar.batch.index.Data; import java.util.Collection; @@ -32,19 +31,21 @@ public class SymbolData implements Data { private static final String FIELD_SEPARATOR = ","; private static final String SYMBOL_SEPARATOR = ";"; - private final Symbolizable.SymbolTable symbolTable; + private final SortedSetMultimap<Symbol, Integer> referencesBySymbol; - public SymbolData(Symbolizable.SymbolTable symbolTable) { - this.symbolTable = symbolTable; + public SymbolData(SortedSetMultimap<Symbol, Integer> referencesBySymbol) { + this.referencesBySymbol = referencesBySymbol; + } + + public SortedSetMultimap<Symbol, Integer> referencesBySymbol() { + return referencesBySymbol; } @Override public String writeString() { StringBuilder sb = new StringBuilder(); - Multimap<Symbol, Integer> referencesBySymbol = ((DefaultSymbolTable)symbolTable).getReferencesBySymbol(); - - for (Symbol symbol : ((DefaultSymbolTable)symbolTable).getReferencesBySymbol().keySet()) { + for (Symbol symbol : referencesBySymbol.keySet()) { sb.append(symbol.getDeclarationStartOffset()) .append(FIELD_SEPARATOR) @@ -59,8 +60,4 @@ public class SymbolData implements Data { return sb.toString(); } - @Override - public void readString(String s) { - throw new UnsupportedOperationException(); - } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/highlighting/DefaultHighlightingBuilderTest.java b/sonar-batch/src/test/java/org/sonar/batch/highlighting/DefaultHighlightingBuilderTest.java new file mode 100644 index 00000000000..aefc5afd732 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/highlighting/DefaultHighlightingBuilderTest.java @@ -0,0 +1,50 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.highlighting; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder.TypeOfText; +import org.sonar.batch.index.ComponentDataCache; +import org.sonar.core.source.SnapshotDataTypes; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class DefaultHighlightingBuilderTest { + + @Test + public void should_apply_registered_highlighting() throws Exception { + + ComponentDataCache cache = mock(ComponentDataCache.class); + + DefaultHighlightingBuilder highlightable = new DefaultHighlightingBuilder("myComponent", cache); + highlightable + .highlight(0, 10, TypeOfText.KEYWORD) + .highlight(20, 30, TypeOfText.CPP_DOC) + .done(); + + ArgumentCaptor<SyntaxHighlightingData> argCaptor = ArgumentCaptor.forClass(SyntaxHighlightingData.class); + verify(cache).setData(eq("myComponent"), eq(SnapshotDataTypes.SYNTAX_HIGHLIGHTING), argCaptor.capture()); + assertThat(argCaptor.getValue().writeString()).isEqualTo("0,10,k;20,30,cppd;"); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/source/SyntaxHighlightingDataBuilderTest.java b/sonar-batch/src/test/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilderTest.java index c190edc3ac7..6fad264c831 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/source/SyntaxHighlightingDataBuilderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilderTest.java @@ -17,9 +17,10 @@ * 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.batch.source; +package org.sonar.batch.highlighting; +import org.sonar.batch.highlighting.SyntaxHighlightingDataBuilder; import org.junit.Before; import org.junit.Rule; import org.junit.Test; diff --git a/sonar-batch/src/test/java/org/sonar/batch/source/SyntaxHighlightingDataTest.java b/sonar-batch/src/test/java/org/sonar/batch/highlighting/SyntaxHighlightingDataTest.java index 8a9ece89ef3..59de88ca848 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/source/SyntaxHighlightingDataTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/highlighting/SyntaxHighlightingDataTest.java @@ -17,7 +17,7 @@ * 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.batch.source; +package org.sonar.batch.highlighting; import com.google.common.collect.Lists; import org.junit.Test; @@ -38,7 +38,7 @@ public class SyntaxHighlightingDataTest { SyntaxHighlightingRule.create(24, 38, "k"), SyntaxHighlightingRule.create(24, 65, "cppd"), SyntaxHighlightingRule.create(42, 50, "k") - ); + ); String serializedRules = new SyntaxHighlightingData(orderedHighlightingRules).writeString(); assertThat(serializedRules).isEqualTo("0,10,cd;10,12,k;12,20,cd;24,38,k;24,65,cppd;42,50,k;"); diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/ComponentDataCacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/ComponentDataCacheTest.java index ff0e7eed20d..3a8835e7a3c 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/ComponentDataCacheTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/ComponentDataCacheTest.java @@ -83,9 +83,5 @@ public class ComponentDataCacheTest { return String.valueOf(data); } - @Override - public void readString(String s) { - data = Long.parseLong(s); - } } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java new file mode 100644 index 00000000000..259b2ffcec0 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java @@ -0,0 +1,92 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.mediumtest.highlighting; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; +import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin; + +import java.io.File; +import java.io.IOException; + +import static org.fest.assertions.Assertions.assertThat; + +public class HighlightingMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .bootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor")) + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void computeSyntaxHighlightingOnTempProject() throws IOException { + + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File xoohighlightingFile = new File(srcDir, "sample.xoo.highlighting"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + FileUtils.write(xoohighlightingFile, "0:10:s\n11:18:k"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + InputFile file = result.inputFiles().get(0); + assertThat(result.highlightingTypeFor(file, 0)).isEqualTo(HighlightingBuilder.TypeOfText.STRING); + assertThat(result.highlightingTypeFor(file, 9)).isEqualTo(HighlightingBuilder.TypeOfText.STRING); + assertThat(result.highlightingTypeFor(file, 10)).isNull(); + assertThat(result.highlightingTypeFor(file, 11)).isEqualTo(HighlightingBuilder.TypeOfText.KEYWORD); + + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java new file mode 100644 index 00000000000..c3875c625f3 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java @@ -0,0 +1,87 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.mediumtest.symbol; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult; +import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin; + +import java.io.File; +import java.io.IOException; + +import static org.fest.assertions.Assertions.assertThat; + +public class SymbolMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .bootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor")) + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void computeSyntaxHighlightingOnTempProject() throws IOException { + + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File xooSymbolFile = new File(srcDir, "sample.xoo.symbol"); + FileUtils.write(xooFile, "Sample xoo\ncontent\nanother xoo"); + FileUtils.write(xooSymbolFile, "7,10,27"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + InputFile file = result.inputFiles().get(0); + assertThat(result.symbolReferencesFor(file, 7, 10)).containsOnly(7, 27); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java index f281ea09899..78718da3682 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java @@ -23,6 +23,8 @@ import org.sonar.api.SonarPlugin; import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; import org.sonar.batch.mediumtest.xoo.plugin.lang.MeasureSensor; import org.sonar.batch.mediumtest.xoo.plugin.lang.ScmActivitySensor; +import org.sonar.batch.mediumtest.xoo.plugin.lang.SymbolReferencesSensor; +import org.sonar.batch.mediumtest.xoo.plugin.lang.SyntaxHighlightingSensor; import org.sonar.batch.mediumtest.xoo.plugin.rule.CreateIssueByInternalKeySensor; import org.sonar.batch.mediumtest.xoo.plugin.rule.OneIssueOnDirPerFileSensor; import org.sonar.batch.mediumtest.xoo.plugin.rule.OneIssuePerLineSensor; @@ -38,6 +40,8 @@ public final class XooPlugin extends SonarPlugin { // language MeasureSensor.class, ScmActivitySensor.class, + SyntaxHighlightingSensor.class, + SymbolReferencesSensor.class, Xoo.class, // sensors diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/MeasureSensor.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/MeasureSensor.java index 9a4b46afd1a..c28ebd5a05c 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/MeasureSensor.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/MeasureSensor.java @@ -104,7 +104,7 @@ public class MeasureSensor implements Sensor { @Override public void describe(SensorDescriptor descriptor) { descriptor - .name("Xoo Measure Analyzer") + .name("Xoo Measure Sensor") .provides(CoreMetrics.LINES) .workOnLanguages(Xoo.KEY) .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST); diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SymbolReferencesSensor.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SymbolReferencesSensor.java new file mode 100644 index 00000000000..91fa61e5c78 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SymbolReferencesSensor.java @@ -0,0 +1,98 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.mediumtest.xoo.plugin.lang; + +import com.google.common.base.Splitter; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.batch.sensor.symbol.Symbol; +import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; +import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; + +/** + * Parse files *.xoo.symbol + */ +public class SymbolReferencesSensor implements Sensor { + + private static final String SYMBOL_EXTENSION = ".symbol"; + + private void processFileHighlighting(InputFile inputFile, SensorContext context) { + File ioFile = inputFile.file(); + File symbolFile = new File(ioFile.getParentFile(), ioFile.getName() + SYMBOL_EXTENSION); + if (symbolFile.exists()) { + XooConstants.LOG.debug("Processing " + symbolFile.getAbsolutePath()); + try { + List<String> lines = FileUtils.readLines(symbolFile, context.fileSystem().encoding().name()); + int lineNumber = 0; + SymbolTableBuilder symbolTableBuilder = context.symbolTableBuilder(inputFile); + for (String line : lines) { + lineNumber++; + if (StringUtils.isBlank(line)) { + continue; + } + if (line.startsWith("#")) { + continue; + } + try { + Iterator<String> split = Splitter.on(",").split(line).iterator(); + int startOffset = Integer.parseInt(split.next()); + int endOffset = Integer.parseInt(split.next()); + Symbol s = symbolTableBuilder.newSymbol(startOffset, endOffset); + while (split.hasNext()) { + symbolTableBuilder.newReference(s, Integer.parseInt(split.next())); + } + } catch (Exception e) { + throw new IllegalStateException("Error processing line " + lineNumber + " of file " + symbolFile.getAbsolutePath(), e); + } + } + symbolTableBuilder.done(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor + .name("Xoo Highlighting Sensor") + .provides(CoreMetrics.LINES) + .workOnLanguages(Xoo.KEY) + .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST); + } + + @Override + public void execute(SensorContext context) { + for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) { + processFileHighlighting(file, context); + } + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SyntaxHighlightingSensor.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SyntaxHighlightingSensor.java new file mode 100644 index 00000000000..5b78759dbb9 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SyntaxHighlightingSensor.java @@ -0,0 +1,95 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.mediumtest.xoo.plugin.lang; + +import com.google.common.base.Splitter; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo; +import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; + +/** + * Parse files *.xoo.highlighting + */ +public class SyntaxHighlightingSensor implements Sensor { + + private static final String HIGHLIGHTING_EXTENSION = ".highlighting"; + + private void processFileHighlighting(InputFile inputFile, SensorContext context) { + File ioFile = inputFile.file(); + File highlightingFile = new File(ioFile.getParentFile(), ioFile.getName() + HIGHLIGHTING_EXTENSION); + if (highlightingFile.exists()) { + XooConstants.LOG.debug("Processing " + highlightingFile.getAbsolutePath()); + try { + List<String> lines = FileUtils.readLines(highlightingFile, context.fileSystem().encoding().name()); + int lineNumber = 0; + HighlightingBuilder highlightingBuilder = context.highlightingBuilder(inputFile); + for (String line : lines) { + lineNumber++; + if (StringUtils.isBlank(line)) { + continue; + } + if (line.startsWith("#")) { + continue; + } + try { + Iterator<String> split = Splitter.on(":").split(line).iterator(); + int startOffset = Integer.parseInt(split.next()); + int endOffset = Integer.parseInt(split.next()); + HighlightingBuilder.TypeOfText type = HighlightingBuilder.TypeOfText.forCssClass(split.next()); + highlightingBuilder.highlight(startOffset, endOffset, type); + } catch (Exception e) { + throw new IllegalStateException("Error processing line " + lineNumber + " of file " + highlightingFile.getAbsolutePath(), e); + } + } + highlightingBuilder.done(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor + .name("Xoo Highlighting Sensor") + .provides(CoreMetrics.LINES) + .workOnLanguages(Xoo.KEY) + .workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST); + } + + @Override + public void execute(SensorContext context) { + for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) { + processFileHighlighting(file, context); + } + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java index b4a8fdab7f0..7b7eb21ee58 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java @@ -42,6 +42,7 @@ import org.sonar.api.measures.MetricFinder; import org.sonar.api.resources.File; import org.sonar.api.resources.Project; import org.sonar.api.rule.RuleKey; +import org.sonar.batch.index.ComponentDataCache; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Matchers.eq; @@ -69,8 +70,9 @@ public class SensorContextAdapterTest { sensorContext = mock(SensorContext.class); settings = new Settings(); resourcePerspectives = mock(ResourcePerspectives.class); + ComponentDataCache componentDataCache = mock(ComponentDataCache.class); adaptor = new SensorContextAdaptor(sensorContext, metricFinder, new Project("myProject"), - resourcePerspectives, settings, fs, activeRules); + resourcePerspectives, settings, fs, activeRules, componentDataCache); } @Test diff --git a/sonar-batch/src/test/java/org/sonar/batch/source/DefaultHighlightableTest.java b/sonar-batch/src/test/java/org/sonar/batch/source/DefaultHighlightableTest.java index 421f5d87679..2f98e07cc79 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/source/DefaultHighlightableTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/source/DefaultHighlightableTest.java @@ -22,12 +22,17 @@ package org.sonar.batch.source; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; import org.sonar.api.component.Component; +import org.sonar.batch.highlighting.SyntaxHighlightingData; import org.sonar.batch.index.ComponentDataCache; import org.sonar.core.source.SnapshotDataTypes; import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class DefaultHighlightableTest { @@ -36,7 +41,7 @@ public class DefaultHighlightableTest { @Test public void should_store_highlighting_rules() throws Exception { - DefaultHighlightable highlightablePerspective = new DefaultHighlightable(null, null); + DefaultHighlightable highlightablePerspective = new DefaultHighlightable(mock(Component.class), null); highlightablePerspective.newHighlighting().highlight(0, 10, "k").highlight(20, 30, "cppd"); assertThat(highlightablePerspective.getHighlightingRules().getSortedRules()).hasSize(2); @@ -55,6 +60,8 @@ public class DefaultHighlightableTest { .highlight(20, 30, "cppd") .done(); - verify(cache).setStringData("myComponent", SnapshotDataTypes.SYNTAX_HIGHLIGHTING, "0,10,k;20,30,cppd;"); + ArgumentCaptor<SyntaxHighlightingData> argCaptor = ArgumentCaptor.forClass(SyntaxHighlightingData.class); + verify(cache).setData(eq("myComponent"), eq(SnapshotDataTypes.SYNTAX_HIGHLIGHTING), argCaptor.capture()); + assertThat(argCaptor.getValue().writeString()).isEqualTo("0,10,k;20,30,cppd;"); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/source/DefaultSymbolTableTest.java b/sonar-batch/src/test/java/org/sonar/batch/source/DefaultSymbolTableTest.java index 2e65c12473b..a1058b27d64 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/source/DefaultSymbolTableTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/source/DefaultSymbolTableTest.java @@ -35,7 +35,7 @@ public class DefaultSymbolTableTest { @Test public void should_order_symbol_and_references() throws Exception { - Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder(); + Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder("foo"); Symbol firstSymbol = symbolTableBuilder.newSymbol(10, 20); symbolTableBuilder.newReference(firstSymbol, 32); Symbol secondSymbol = symbolTableBuilder.newSymbol(84, 92); @@ -54,16 +54,16 @@ public class DefaultSymbolTableTest { public void should_reject_reference_conflicting_with_declaration() throws Exception { throwable.expect(UnsupportedOperationException.class); - Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder(); + Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder("foo"); Symbol symbol = symbolTableBuilder.newSymbol(10, 20); symbolTableBuilder.newReference(symbol, 15); } @Test public void test_toString() throws Exception { - Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder(); + Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder("foo"); Symbol symbol = symbolTableBuilder.newSymbol(10, 20); - assertThat(symbol.toString()).isEqualTo("Symbol{offset=10-20}"); + assertThat(symbol.toString()).isEqualTo("Symbol{component=foo, offset=10-20}"); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/source/SymbolDataTest.java b/sonar-batch/src/test/java/org/sonar/batch/source/SymbolDataTest.java deleted file mode 100644 index 8dfdd1cccd5..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/source/SymbolDataTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube 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. - * - * SonarQube 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.batch.source; - -import org.junit.Test; -import org.sonar.api.source.Symbol; -import org.sonar.api.source.Symbolizable; - -import static org.fest.assertions.Assertions.assertThat; - -public class SymbolDataTest { - - @Test - public void should_serialize_symbols_in_natural_order() throws Exception { - - Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder(); - Symbol firstSymbol = symbolTableBuilder.newSymbol(10, 20); - symbolTableBuilder.newReference(firstSymbol, 32); - Symbol secondSymbol = symbolTableBuilder.newSymbol(84, 92); - symbolTableBuilder.newReference(secondSymbol, 124); - Symbol thirdSymbol = symbolTableBuilder.newSymbol(55, 62); - symbolTableBuilder.newReference(thirdSymbol, 70); - Symbolizable.SymbolTable symbolTable = symbolTableBuilder.build(); - - SymbolData dataRepository = new SymbolData(symbolTable); - String serializedSymbolData = dataRepository.writeString(); - - assertThat(serializedSymbolData).isEqualTo("10,20,10,32;55,62,55,70;84,92,84,124;"); - } - - @Test - public void should_serialize_unused_symbol() throws Exception { - - Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder(); - symbolTableBuilder.newSymbol(10, 20); - - SymbolData dataRepository = new SymbolData(symbolTableBuilder.build()); - String serializedSymbolData = dataRepository.writeString(); - - assertThat(serializedSymbolData).isEqualTo("10,20,10;"); - } -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/symbol/DefaultSymbolTableBuilderTest.java b/sonar-batch/src/test/java/org/sonar/batch/symbol/DefaultSymbolTableBuilderTest.java new file mode 100644 index 00000000000..c661da30af1 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/symbol/DefaultSymbolTableBuilderTest.java @@ -0,0 +1,108 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.symbol; + +import com.google.common.collect.SortedSetMultimap; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +import org.sonar.api.batch.sensor.symbol.Symbol; +import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder; +import org.sonar.batch.index.ComponentDataCache; +import org.sonar.core.source.SnapshotDataTypes; + +import java.util.ArrayList; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class DefaultSymbolTableBuilderTest { + + @Rule + public ExpectedException throwable = ExpectedException.none(); + + @Test + public void should_order_symbol_and_references() throws Exception { + ComponentDataCache componentDataCache = mock(ComponentDataCache.class); + SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTableBuilder("foo", componentDataCache); + Symbol firstSymbol = symbolTableBuilder.newSymbol(10, 20); + symbolTableBuilder.newReference(firstSymbol, 32); + Symbol secondSymbol = symbolTableBuilder.newSymbol(84, 92); + symbolTableBuilder.newReference(secondSymbol, 124); + Symbol thirdSymbol = symbolTableBuilder.newSymbol(55, 62); + symbolTableBuilder.newReference(thirdSymbol, 70); + symbolTableBuilder.done(); + + ArgumentCaptor<SymbolData> argCaptor = ArgumentCaptor.forClass(SymbolData.class); + verify(componentDataCache).setData(eq("foo"), eq(SnapshotDataTypes.SYMBOL_HIGHLIGHTING), argCaptor.capture()); + + SortedSetMultimap<Symbol, Integer> referencesBySymbol = argCaptor.getValue().referencesBySymbol(); + + assertThat(new ArrayList<Symbol>(referencesBySymbol.keySet())).containsExactly(firstSymbol, thirdSymbol, secondSymbol); + assertThat(new ArrayList<Integer>(referencesBySymbol.get(firstSymbol))).containsExactly(10, 32); + assertThat(new ArrayList<Integer>(referencesBySymbol.get(secondSymbol))).containsExactly(84, 124); + assertThat(new ArrayList<Integer>(referencesBySymbol.get(thirdSymbol))).containsExactly(55, 70); + + assertThat(argCaptor.getValue().writeString()).isEqualTo("10,20,10,32;55,62,55,70;84,92,84,124;"); + } + + @Test + public void should_serialize_unused_symbol() throws Exception { + + ComponentDataCache componentDataCache = mock(ComponentDataCache.class); + SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTableBuilder("foo", componentDataCache); + symbolTableBuilder.newSymbol(10, 20); + symbolTableBuilder.done(); + + ArgumentCaptor<SymbolData> argCaptor = ArgumentCaptor.forClass(SymbolData.class); + verify(componentDataCache).setData(eq("foo"), eq(SnapshotDataTypes.SYMBOL_HIGHLIGHTING), argCaptor.capture()); + + assertThat(argCaptor.getValue().writeString()).isEqualTo("10,20,10;"); + } + + @Test + public void should_reject_reference_conflicting_with_declaration() throws Exception { + throwable.expect(UnsupportedOperationException.class); + + ComponentDataCache componentDataCache = mock(ComponentDataCache.class); + SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTableBuilder("foo", componentDataCache); + Symbol symbol = symbolTableBuilder.newSymbol(10, 20); + symbolTableBuilder.newReference(symbol, 15); + } + + @Test + public void should_reject_reference_from_another_file() throws Exception { + throwable.expect(UnsupportedOperationException.class); + + ComponentDataCache componentDataCache = mock(ComponentDataCache.class); + SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTableBuilder("foo", componentDataCache); + Symbol symbol = symbolTableBuilder.newSymbol(10, 20); + + SymbolTableBuilder symbolTableBuilder2 = new DefaultSymbolTableBuilder("foo2", componentDataCache); + Symbol symbol2 = symbolTableBuilder2.newSymbol(30, 40); + + symbolTableBuilder.newReference(symbol2, 15); + } + +} diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 2d8b6615c1b..8188fb9766a 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -407,7 +407,7 @@ layout.print=Print layout.permalink=Permalink layout.sonar.slogan=Embrace Quality layout.dashboards=Dashboards -layout.configuration=Configuration +layout.configuration=Project Configuration layout.projects=Projects layout.recent_projects=Recent Projects layout.user_panel.my_profile=My profile @@ -598,7 +598,7 @@ measure_filter.display.treemap=Treemap measure_filter.list.change=Change Columns measure_filter.treemap.change=Change Treemap measure_filter.add_column_button=Add Column -measure_filter.widget.unknown_filter_warning=This widget is configured to display a measure filter that doesn't exist anymore. +measure_filter.widget.unknown_filter_warning=This widget is configured to display a measure filter that does not exist anymore. measure_filter.error.UNKNOWN=Unexpected error. Please contact the administrator. measure_filter.error.TOO_MANY_RESULTS=Too many results. Please refine your search. measure_filter.error.VALUE_SHOULD_BE_A_NUMBER=Value used for metric should be a number. @@ -1958,7 +1958,7 @@ bulk_deletion.delete_all_ghosts=Delete all ghosts provisioning.no_results=There is currently no provisioned project. provisioning.missing.key=Key is missing provisioning.missing.name=Name is missing -provisioning.no_analysis=No analysis has been performed since creation. The only available section is Configuration. +provisioning.no_analysis=No analysis has been performed since creation. The only available section is Project Configuration. #------------------------------------------------------------------------------ diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java index 234b3865ca3..787d2fddb3d 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java @@ -24,10 +24,12 @@ import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.measure.Metric; import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.issue.IssueBuilder; import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.batch.sensor.measure.MeasureBuilder; +import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder; import org.sonar.api.config.Settings; import javax.annotation.CheckForNull; @@ -108,4 +110,20 @@ public interface SensorContext { */ boolean addIssue(Issue issue); + // ------------ HIGHLIGHTING ------------ + + /** + * Builder to define highlighting of a file. + * @since 4.5 + */ + HighlightingBuilder highlightingBuilder(InputFile inputFile); + + // ------------ SYMBOL REFERENCES ------------ + + /** + * Builder to define symbol references in a file. + * @since 4.5 + */ + SymbolTableBuilder symbolTableBuilder(InputFile inputFile); + } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/highlighting/HighlightingBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/highlighting/HighlightingBuilder.java new file mode 100644 index 00000000000..db8d5c46e93 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/highlighting/HighlightingBuilder.java @@ -0,0 +1,78 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.api.batch.sensor.highlighting; + + +/** + * This builder is used to define syntax highlighting (aka code coloration) on files. + * @since 4.5 + */ +public interface HighlightingBuilder { + + /** + * See sonar-colorizer.css + */ + enum TypeOfText { + ANNOTATION("a"), + CONSTANT("c"), + JAVADOC("j"), + CLASSIC_COMMENT("cd"), + CPP_DOC("cppd"), + KEYWORD("k"), + STRING("s"), + KEYWORD_LIGHT("h"), + PREPROCESS_DIRECTIVE("p"); + + private final String cssClass; + + private TypeOfText(String cssClass) { + this.cssClass = cssClass; + } + + public static TypeOfText forCssClass(String cssClass) { + for (TypeOfText typeOfText : TypeOfText.values()) { + if (typeOfText.cssClass().equals(cssClass)) { + return typeOfText; + } + } + throw new IllegalArgumentException("No TypeOfText for CSS class " + cssClass); + } + + /** + * For internal use + */ + public String cssClass() { + return cssClass; + } + } + + /** + * Call this method to indicate the type of text in a range. + * @param startOffset Starting position in file for this type of text. Beginning of a file starts with offset '0'. + * @param endOffset End position in file for this type of text. + * @param typeOfText see {@link TypeOfText} values. + */ + HighlightingBuilder highlight(int startOffset, int endOffset, TypeOfText typeOfText); + + /** + * Call this method only once when your are done with defining highlighting of the file. + */ + void done(); +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/highlighting/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/highlighting/package-info.java new file mode 100644 index 00000000000..b4d89fb5783 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/highlighting/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.api.batch.sensor.highlighting; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/Symbol.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/Symbol.java new file mode 100644 index 00000000000..9e1e3e8259e --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/Symbol.java @@ -0,0 +1,33 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.api.batch.sensor.symbol; + +/** + * Represent a symbol in a source file. + * @since 4.5 + */ +public interface Symbol { + + int getDeclarationStartOffset(); + + int getDeclarationEndOffset(); + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/SymbolTableBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/SymbolTableBuilder.java new file mode 100644 index 00000000000..3be82175d42 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/SymbolTableBuilder.java @@ -0,0 +1,48 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.api.batch.sensor.symbol; + +/** + * Use this builder to create symbol references. For now only references + * in the same file are supported. + * @since 4.5 + */ +public interface SymbolTableBuilder { + + /** + * Create a new symbol. + * @param fromOffset Starting offset in a file for the symbol declaration. File starts at offset '0'. + * @param toOffset Ending offset of symbol declaration. + * @return a new Symbol that can be used later in {@link #newReference(Symbol, int)} + */ + Symbol newSymbol(int fromOffset, int toOffset); + + /** + * Records that a {@link Symbol} is referenced at another location in the same file. + * @param symbol Symbol previously created with {@link #newSymbol(int, int)} + * @param fromOffset Starting offset of the place symbol is referenced. No need for end offset here since we assume it is same length. + */ + void newReference(Symbol symbol, int fromOffset); + + /** + * Call this method only once when your are done with defining symbols of the file. + */ + void done(); +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/source/DefaultSymbol.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/internal/DefaultSymbol.java index 629633a05de..8f071278505 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/source/DefaultSymbol.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/internal/DefaultSymbol.java @@ -18,29 +18,40 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.batch.source; +package org.sonar.api.batch.sensor.symbol.internal; import com.google.common.base.Objects; -import org.sonar.api.source.Symbol; +import org.sonar.api.batch.sensor.symbol.Symbol; -public class DefaultSymbol implements Symbol { +import java.io.Serializable; +public class DefaultSymbol implements Symbol, org.sonar.api.source.Symbol, Serializable { + + private final String componentKey; private final int declarationStartOffset; private final int declarationEndOffset; - public DefaultSymbol(int startOffset, int endOffset) { + public DefaultSymbol(String componentKey, int startOffset, int endOffset) { + this.componentKey = componentKey; this.declarationStartOffset = startOffset; this.declarationEndOffset = endOffset; } + public String componentKey() { + return componentKey; + } + + @Override public int getDeclarationStartOffset() { return declarationStartOffset; } + @Override public int getDeclarationEndOffset() { return declarationEndOffset; } + @Override public String getFullyQualifiedName() { return null; } @@ -48,6 +59,7 @@ public class DefaultSymbol implements Symbol { @Override public String toString() { return Objects.toStringHelper("Symbol") + .add("component", componentKey) .add("offset", String.format("%d-%d", declarationStartOffset, declarationEndOffset)) .toString(); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/internal/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/internal/package-info.java new file mode 100644 index 00000000000..e0ccdf1c9b3 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/internal/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.api.batch.sensor.symbol.internal; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/package-info.java new file mode 100644 index 00000000000..7acf7f3b056 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/symbol/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.api.batch.sensor.symbol; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rules/Rule.java b/sonar-plugin-api/src/main/java/org/sonar/api/rules/Rule.java index 45328b52e49..006eb0866cd 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/rules/Rule.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/rules/Rule.java @@ -48,6 +48,7 @@ import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; + import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -195,7 +196,6 @@ public class Rule { this.id = id; } - @CheckForNull public String getName() { return name; } @@ -203,7 +203,7 @@ public class Rule { /** * Sets the rule name */ - public Rule setName(@Nullable String name) { + public Rule setName(String name) { this.name = removeNewLineCharacters(name); return this; } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/source/Highlightable.java b/sonar-plugin-api/src/main/java/org/sonar/api/source/Highlightable.java index 0ab229be03c..6d5bd6bd14d 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/source/Highlightable.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/source/Highlightable.java @@ -19,11 +19,14 @@ */ package org.sonar.api.source; +import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.component.Perspective; /** * @since 3.6 + * @deprecated since 4.5 use {@link SensorContext#highlightingBuilder(org.sonar.api.batch.fs.InputFile)} */ +@Deprecated public interface Highlightable extends Perspective { interface HighlightingBuilder { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/source/Symbol.java b/sonar-plugin-api/src/main/java/org/sonar/api/source/Symbol.java index 260d6c8ecd7..31702ecb065 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/source/Symbol.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/source/Symbol.java @@ -20,15 +20,21 @@ package org.sonar.api.source; -public interface Symbol { +/** + * @deprecated since 4.5 replaced by {@link org.sonar.api.batch.sensor.symbol.Symbol} + */ +@Deprecated +public interface Symbol extends org.sonar.api.batch.sensor.symbol.Symbol { + @Override int getDeclarationStartOffset(); + @Override int getDeclarationEndOffset(); /** * @since unused - * @deprecated in 4.3 + * @deprecated in 4.3 not used. */ @Deprecated String getFullyQualifiedName(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/source/Symbolizable.java b/sonar-plugin-api/src/main/java/org/sonar/api/source/Symbolizable.java index 3a49ada34df..0b7f765cfe7 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/source/Symbolizable.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/source/Symbolizable.java @@ -19,13 +19,16 @@ */ package org.sonar.api.source; +import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.component.Perspective; import java.util.List; /** * @since 3.6 + * @deprecated since 4.5 use {@link SensorContext#symbolTableBuilder(org.sonar.api.batch.fs.InputFile)} */ +@Deprecated public interface Symbolizable extends Perspective { interface SymbolTableBuilder { diff --git a/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_and_overridden_debt_values.json b/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_and_overridden_debt_values.json new file mode 100644 index 00000000000..3aa6211c764 --- /dev/null +++ b/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_and_overridden_debt_values.json @@ -0,0 +1,18 @@ +{"total": 1, "p": 1, "ps": 10, "rules": [ + { + "key": "xoo:x1", + "debtChar": "RELIABILITY", + "debtCharName": "Reliability", + "debtSubChar": "SOFT_RELIABILITY", + "debtSubCharName": "Soft Reliability", + "debtRemFnType": "LINEAR_OFFSET", + "debtRemFnCoeff": "2h", + "debtRemFnOffset": "25min", + "debtOverloaded": true, + "defaultDebtChar": "RELIABILITY", + "defaultDebtSubChar": "HARD_RELIABILITY", + "defaultDebtRemFnType": "LINEAR_OFFSET", + "defaultDebtRemFnCoeff": "1h", + "defaultDebtRemFnOffset": "15min" + } +]} diff --git a/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json b/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json new file mode 100644 index 00000000000..6ae75abecb5 --- /dev/null +++ b/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_constant_debt.json @@ -0,0 +1,17 @@ +{"total": 1, "p": 1, "ps": 10, "rules": [ + { + "key": "xoo:x1", + "debtChar": "RELIABILITY", + "debtCharName": "Reliability", + "debtSubChar": "SOFT_RELIABILITY", + "debtSubCharName": "Soft Reliability", + "debtRemFnType": "CONSTANT_ISSUE", + "debtRemFnOffset": "5min", + "debtOverloaded": true, + "defaultDebtChar": "RELIABILITY", + "defaultDebtSubChar": "HARD_RELIABILITY", + "defaultDebtRemFnType": "LINEAR_OFFSET", + "defaultDebtRemFnCoeff": "1h", + "defaultDebtRemFnOffset": "15min" + } +]} diff --git a/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json b/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json new file mode 100644 index 00000000000..b1c457e13c0 --- /dev/null +++ b/sonar-server/src/test/resources/org/sonar/server/rule/ws/RulesWebServiceMediumTest/search_debt_rules_with_default_linear_offset_and_overridden_linear_debt.json @@ -0,0 +1,17 @@ +{"total": 1, "p": 1, "ps": 10, "rules": [ + { + "key": "xoo:x1", + "debtChar": "RELIABILITY", + "debtCharName": "Reliability", + "debtSubChar": "SOFT_RELIABILITY", + "debtSubCharName": "Soft Reliability", + "debtRemFnType": "LINEAR", + "debtRemFnCoeff": "1h", + "debtOverloaded": true, + "defaultDebtChar": "RELIABILITY", + "defaultDebtSubChar": "HARD_RELIABILITY", + "defaultDebtRemFnType": "LINEAR_OFFSET", + "defaultDebtRemFnCoeff": "1h", + "defaultDebtRemFnOffset": "15min" + } +]} |