Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility to exclude array initializations #6

Merged
merged 8 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions docs/pages/pmd/userdocs/cpd/cpd.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ refactored out. We thus advise developers to use CPD to **help remove duplicates

### Refactoring duplicates

Once you have located some duplicates, several refactoring strategies may apply depending of the scope and extent of
Once you have located some duplicates, several refactoring strategies may apply depending on the scope and extent of
the duplication. Here's a quick summary:

* If the duplication is local to a method or single class:
Expand Down Expand Up @@ -180,9 +180,12 @@ exactly identical.
By default, annotations are not ignored."
languages="C#, Java"
%}
{% include custom/cli_option_row.html options="--ignore-sequences"
description="Ignore sequences of identifier and literals.
By default, such sequences are not ignored."
{% include custom/cli_option_row.html options="--ignore-sequences"
option_arg="type"
description="Ignore sequences. Types of sequences ignored can be specified in `type`
(if no parameter is provided, defaults to `literals-identifiers`).
See available options [here](#sequence-exclusion).
This option can be repeated to configure more than one type of sequence exclusion."
languages="C++"
%}
{% include custom/cli_option_row.html options="--ignore-usings"
Expand Down Expand Up @@ -357,6 +360,14 @@ to be "debug".

For details, see [CPD Report Formats](pmd_userdocs_cpd_report_formats.html).

## Sequence exclusion

Several strategies are available for the exclusion of lists from being marked as duplicates:

* `literals-identifiers` (Default) skip lists containing literals and identifiers.
* `literals` skip lists containing only literal elements. This is equivalent to `--ignore-literal-sequences`.
* `initializations` skip all list initializations, regardless of their content.

## Ant task

Andy Glover wrote an Ant task for CPD; here's how to use it:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.mutable.MutableBoolean;
import org.checkerframework.checker.nullness.qual.NonNull;

import net.sourceforge.pmd.cli.commands.typesupport.internal.CpdIgnoreSequenceTypeSupport;
import net.sourceforge.pmd.cli.commands.typesupport.internal.CpdLanguageTypeSupport;
import net.sourceforge.pmd.cli.internal.CliExitCode;
import net.sourceforge.pmd.cpd.CPDConfiguration;
import net.sourceforge.pmd.cpd.CPDSequenceIgnoreType;
import net.sourceforge.pmd.cpd.CpdAnalysis;
import net.sourceforge.pmd.cpd.internal.CpdLanguagePropertiesDefaults;
import net.sourceforge.pmd.internal.LogMessages;
Expand Down Expand Up @@ -65,8 +69,13 @@ public class CpdCommand extends AbstractAnalysisPmdSubcommand<CPDConfiguration>
@Option(names = "--ignore-literal-sequences", description = "Ignore sequences of literals such as list initializers.")
private boolean ignoreLiteralSequences;

@Option(names = "--ignore-sequences", description = "Ignore sequences of identifiers and literals")
private boolean ignoreIdentifierAndLiteralSequences;
@Option(names = "--ignore-sequences",
arity = "0..1",
description = "Type of sequences to exclude from duplication.%nValid values: ${COMPLETION-CANDIDATES}",
converter = CpdIgnoreSequenceTypeSupport.class,
completionCandidates = CpdIgnoreSequenceTypeSupport.class,
fallbackValue = "literals-identifiers")
private Set<CPDSequenceIgnoreType> sequenceIgnoreTypes = EnumSet.noneOf(CPDSequenceIgnoreType.class);

/**
* @deprecated Use {@link #failOnError} instead.
Expand Down Expand Up @@ -115,7 +124,9 @@ protected CPDConfiguration toConfiguration() {
configuration.setIgnoreAnnotations(ignoreAnnotations);
configuration.setIgnoreIdentifiers(ignoreIdentifiers);
configuration.setIgnoreLiterals(ignoreLiterals);
configuration.setIgnoreLiteralSequences(ignoreLiteralSequences);
configuration.setIgnoreLiteralSequences(ignoreLiteralSequences || sequenceIgnoreTypes.contains(CPDSequenceIgnoreType.LITERALS));
configuration.setIgnoreIdentifierAndLiteralSequences(sequenceIgnoreTypes.contains(CPDSequenceIgnoreType.LITERALS_IDENTIFIERS));
configuration.setIgnoreSequenceInitializations(sequenceIgnoreTypes.contains(CPDSequenceIgnoreType.INITIALIZATIONS));
configuration.setIgnoreUsings(ignoreUsings);
configuration.setOnlyRecognizeLanguage(language);
configuration.setMinimumTileSize(minimumTokens);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/

package net.sourceforge.pmd.cli.commands.typesupport.internal;

import java.util.Arrays;
import java.util.Iterator;

import net.sourceforge.pmd.cpd.CPDSequenceIgnoreType;

import picocli.CommandLine.ITypeConverter;
import picocli.CommandLine.TypeConversionException;

public class CpdIgnoreSequenceTypeSupport implements ITypeConverter<CPDSequenceIgnoreType>, Iterable<String> {

@Override
public Iterator<String> iterator() {
return Arrays.stream(CPDSequenceIgnoreType.values()).map(CPDSequenceIgnoreType::getName).iterator();
}

@Override
public CPDSequenceIgnoreType convert(String s) {
return Arrays.stream(CPDSequenceIgnoreType.values())
.filter(t -> t.getName().equalsIgnoreCase(s))
.findFirst()
.orElseThrow(() -> new TypeConversionException("Invalid sequence type: " + s));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import net.sourceforge.pmd.cpd.CPDConfiguration;
import net.sourceforge.pmd.util.CollectionUtil;
Expand Down Expand Up @@ -59,6 +64,84 @@ private void assertMultipleDirs(final CpdCommand result) {
assertEquals(listOf("a", "b"), CollectionUtil.map(config.getInputPathList(), Path::toString));
}

@ParameterizedTest
@MethodSource("ignoreSequencesTestArgs")
void testIgnoreSequencesOption(String[] args, ExpectedIgnoreOptions ignore) {
final CpdCommand cmd = setupAndParse(args);
CPDConfiguration config = cmd.toConfiguration();
assertIgnoreSequenceOptions(config, ignore);
}

static Collection<Arguments> ignoreSequencesTestArgs() {
Collection<Arguments> args = new ArrayList<Arguments>();
args.add(Arguments.of(
new String[] {
"--ignore-sequences",
"literals",
},
new ExpectedIgnoreOptions(false, true, false, false)
));
args.add(Arguments.of(
new String[] {
"--ignore-sequences",
"literals-identifiers",
},
new ExpectedIgnoreOptions(false, false, true, false)
));
args.add(Arguments.of(
new String[] {
"--ignore-sequences",
"initializations",
},
new ExpectedIgnoreOptions(false, false, false, true)
));
args.add(Arguments.of(
new String[] {
"--ignore-sequences",
"literals-identifiers",
"--ignore-sequences",
"initializations",
},
new ExpectedIgnoreOptions(false, false, true, true)
));
args.add(Arguments.of(
new String[] {
"--ignore-sequences",
},
new ExpectedIgnoreOptions(false, false, true, false)
));
args.add(Arguments.of(
new String[] { },
new ExpectedIgnoreOptions(false, false, false, false)
));
return args;
}

void assertIgnoreSequenceOptions(CPDConfiguration config, ExpectedIgnoreOptions exp) {
assertEquals(config.isIgnoreLiteralSequences(), exp.literalSequences, "Expected isIgnoreLiteralSequences() to be " + exp.literalSequences);
assertEquals(config.isIgnoreIdentifierAndLiteralSequences(), exp.identifierSequences, "Expected isIgnoreLiteralAndIdentifierSequences() to be " + exp.identifierSequences);
assertEquals(config.isIgnoreSequenceInitializations(), exp.arrayInitializations, "Expected isIgnoreArrayInitializations() to be " + exp.arrayInitializations);
}

static class ExpectedIgnoreOptions {
boolean annotations;
boolean literalSequences;
boolean identifierSequences;
boolean arrayInitializations;

ExpectedIgnoreOptions(
boolean annotations,
boolean literalSequences,
boolean identifierSequences,
boolean arrayInitializations
) {
this.annotations = annotations;
this.literalSequences = literalSequences;
this.identifierSequences = identifierSequences;
this.arrayInitializations = arrayInitializations;
}
}

@Override
protected CpdCommand createCommand() {
return new CpdCommand();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ public class CPDConfiguration extends AbstractConfiguration {

private boolean ignoreUsings;

private boolean ignoreLiteralSequences = false;

private boolean ignoreIdentifierAndLiteralSequences = false;
private CPDSequenceIgnoreSetting ignoreSequences = new CPDSequenceIgnoreSetting();

@Deprecated
// Note: The default value was false until up to 7.3.0 and is true since 7.4.0
Expand Down Expand Up @@ -181,6 +179,10 @@ void setRenderer(CPDReportRenderer renderer) {
this.cpdReportRenderer = renderer;
}

public void setIgnoreSequences(CPDSequenceIgnoreSetting value) {
this.ignoreSequences = value;
}

public boolean isIgnoreLiterals() {
return ignoreLiterals;
}
Expand Down Expand Up @@ -214,19 +216,27 @@ public void setIgnoreUsings(boolean ignoreUsings) {
}

public boolean isIgnoreLiteralSequences() {
return ignoreLiteralSequences;
return ignoreSequences.getValue(CPDSequenceIgnoreType.LITERALS);
}

public void setIgnoreLiteralSequences(boolean ignoreLiteralSequences) {
this.ignoreLiteralSequences = ignoreLiteralSequences;
ignoreSequences.setValue(CPDSequenceIgnoreType.LITERALS, ignoreLiteralSequences);
}

public boolean isIgnoreIdentifierAndLiteralSequences() {
return ignoreIdentifierAndLiteralSequences;
return ignoreSequences.getValue(CPDSequenceIgnoreType.LITERALS_IDENTIFIERS);
}

public void setIgnoreIdentifierAndLiteralSequences(boolean ignoreIdentifierAndLiteralSequences) {
this.ignoreIdentifierAndLiteralSequences = ignoreIdentifierAndLiteralSequences;
ignoreSequences.setValue(CPDSequenceIgnoreType.LITERALS_IDENTIFIERS, ignoreIdentifierAndLiteralSequences);
}

public boolean isIgnoreSequenceInitializations() {
return ignoreSequences.getValue(CPDSequenceIgnoreType.INITIALIZATIONS);
}

public void setIgnoreSequenceInitializations(boolean ignoreSequenceInitializations) {
ignoreSequences.setValue(CPDSequenceIgnoreType.INITIALIZATIONS, ignoreSequenceInitializations);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/

package net.sourceforge.pmd.cpd;

import java.util.EnumSet;
import java.util.Set;

/**
* Combined setting for 'ignore-sequence' variants (see {@link CPDSequenceIgnoreType}),
*/
public class CPDSequenceIgnoreSetting {
private Set<CPDSequenceIgnoreType> enabledIgnoreSequenceTypes = EnumSet.noneOf(CPDSequenceIgnoreType.class);

public void setValue(CPDSequenceIgnoreType type, boolean value) {
if (value) {
enabledIgnoreSequenceTypes.add(type);
} else {
enabledIgnoreSequenceTypes.remove(type);
}
}

public boolean getValue(CPDSequenceIgnoreType type) {
return enabledIgnoreSequenceTypes.contains(type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/

package net.sourceforge.pmd.cpd;

/**
* Options for skipping/ignoring tokens in CPD
*/
public enum CPDSequenceIgnoreType {
LITERALS("literals"),
LITERALS_IDENTIFIERS("literals-identifiers"),
INITIALIZATIONS("initializations");

private final String name;

CPDSequenceIgnoreType(String name) {
this.name = name;
}

public String getName() {
return name;
}

@Override
public String toString() {
return name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ private void setLanguageProperties(Language language, CPDConfiguration configura
setPropertyIfMissing(CpdLanguageProperties.CPD_IGNORE_IMPORTS, props, configuration.isIgnoreUsings());
setPropertyIfMissing(CpdLanguageProperties.CPD_IGNORE_LITERAL_SEQUENCES, props, configuration.isIgnoreLiteralSequences());
setPropertyIfMissing(CpdLanguageProperties.CPD_IGNORE_LITERAL_AND_IDENTIFIER_SEQUENCES, props, configuration.isIgnoreIdentifierAndLiteralSequences());
setPropertyIfMissing(CpdLanguageProperties.CPD_IGNORE_SEQUENCE_INITIALIZATION, props, configuration.isIgnoreSequenceInitializations());
if (!configuration.isNoSkipBlocks()) {
// see net.sourceforge.pmd.lang.cpp.CppLanguageModule.CPD_SKIP_BLOCKS
PropertyDescriptor<String> skipBlocks = (PropertyDescriptor) props.getPropertyDescriptor("cpdSkipBlocksPattern");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,9 @@ private CpdLanguageProperties() {
.defaultValue(false)
.desc("Ignore metadata such as Java annotations or C# attributes.")
.build();
public static final PropertyDescriptor<Boolean> CPD_IGNORE_SEQUENCE_INITIALIZATION =
PropertyFactory.booleanProperty("cpdIgnoreSequenceInitialization")
.defaultValue(false)
.desc("Ignore sequence initializations.")
.build();
}
Loading