Skip to content

Commit

Permalink
Add ability to suppress warnings via Quick Fix actions (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
maliroteh-sf authored Jul 13, 2021
1 parent 909e267 commit 3288a5f
Show file tree
Hide file tree
Showing 18 changed files with 472 additions and 65 deletions.
4 changes: 2 additions & 2 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
<parent>
<groupId>com.salesforce.slds</groupId>
<artifactId>common</artifactId>
<version>0.0.11</version>
<version>0.0.12</version>
</parent>

<artifactId>core</artifactId>
<version>0.0.11</version>
<version>0.0.12</version>

<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@

public enum AnnotationType {

ALLOW("sldsValidatorAllow"),
IGNORE("sldsValidatorIgnore"),
ALLOW("sldsValidatorAllow"), // enables recommendation validation
IGNORE("sldsValidatorIgnore"), // disables recommendation validation
IGNORE_NEXT_LINE("sldsValidatorIgnoreNextLine"), // disables recommendation validation only for the next immediate line (even if an element is defined across multiple lines)
WARN("sldsValidatorWarn"),
NONE(null);

Expand All @@ -25,7 +26,7 @@ public String value() {
}

public boolean validate() {
return this != ALLOW;
return this != IGNORE && this != IGNORE_NEXT_LINE;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public enum ContextKey {
INVALID,
OVERRIDE,
UTILITY_CLASS,
DESIGN_TOKEN;
DESIGN_TOKEN,
SLDS_MOBILE_VALIDATION;

public static Optional<ContextKey> get(String name) {
for (ContextKey key : values()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
package com.salesforce.slds.shared.models.core;

import com.salesforce.slds.shared.models.annotations.Annotation;
import com.salesforce.slds.shared.models.annotations.AnnotationType;
import com.salesforce.slds.shared.models.locations.Location;
import com.salesforce.slds.shared.models.locations.Range;
import com.salesforce.slds.shared.models.override.ComponentOverride;
import com.salesforce.slds.shared.models.recommendation.Recommendation;

Expand Down Expand Up @@ -92,6 +95,84 @@ public List<Annotation> getAnnotations() {
.flatMap(List::stream).collect(Collectors.toList());
}

/*
* Searches through the content of an entry to determine the ranges (if any) where
* our linting rules should be ignored. Take the following content for example, with
* the line numbers (zero-based) in the first column followed by the content of that line:
*
* 0 a
* 1 b
* 2 sldsValidatorIgnore
* 3 c
* 4 d
* 5 sldsValidatorAllow
* 6 e
* 7 f
* 8 sldsValidatorIgnore
* 9 g
* 10 h
* 11 sldsValidatorAllow
* 12 i
* 13 sldsValidatorIgnoreNextLine
* 14 j
* 15 k
*
* In the above example, any content between sldsValidatorIgnore & sldsValidatorAllow (i.e 3-4, 9-10)
* should be exempt from validation rules. Also the line immediately following
* sldsValidatorIgnoreNextLine (i.e 14) should also be exempt from validation rules.
*/
public List<Range> getRecommendationSuppressionRanges() {
ArrayList<Range> lintingIgnoreRanges = new ArrayList<Range>();
for (int i = 0; rawContent != null && i < rawContent.size(); i++) {
String lineStr = rawContent.get(i);
int startColumnIgnore = lineStr.indexOf(AnnotationType.IGNORE.value());
int startColumnIgnoreNextLine = lineStr.indexOf(AnnotationType.IGNORE_NEXT_LINE.value());
if (startColumnIgnore >= 0 && startColumnIgnore != startColumnIgnoreNextLine) {
// we found a sldsValidatorIgnore
int j = i;
int endColumn = -1;
for (; j < rawContent.size(); j++) {
int column = rawContent.get(j).indexOf(AnnotationType.ALLOW.value());
if (column >= 0) {
endColumn = column + AnnotationType.ALLOW.value().length();
break;
}
}
if (endColumn == -1) {
// we found a sldsValidatorIgnore with no sldsValidatorAllow after it
// so we should exempt the rest of the lines.
lintingIgnoreRanges.add(
new Range(
new Location(i, startColumnIgnore),
new Location(Integer.MAX_VALUE, Integer.MAX_VALUE)
)
);
i = rawContent.size(); // skip to the end
} else {
// we found a sldsValidatorIgnore with sldsValidatorAllow after it
// so we should exempt the lines in between.
lintingIgnoreRanges.add(
new Range(
new Location(i, startColumnIgnore),
new Location(j, endColumn)
)
);
i = j; // skip to the next block
}
} else if (startColumnIgnoreNextLine >= 0) {
// we found a sldsValidatorIgnoreNextLine so we should exempt the next line.
lintingIgnoreRanges.add(
new Range(
new Location(i + 1, 0),
new Location(i + 1, Integer.MAX_VALUE)
)
);
i++; // skip the next line
}
}
return lintingIgnoreRanges;
}

public List<String> getRawContent() {
return this.rawContent;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@

package com.salesforce.slds.validation.processors;

import com.salesforce.slds.shared.models.core.Entry;
import com.salesforce.slds.shared.models.recommendation.Recommendation;

import java.util.List;

public interface Processor {

List<Recommendation> process(List<Recommendation> recommendations);
List<Recommendation> process(Entry entry, List<Recommendation> recommendations);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

package com.salesforce.slds.validation.processors;

import com.salesforce.slds.shared.models.core.Entry;
import com.salesforce.slds.shared.models.core.HTMLElement;
import com.salesforce.slds.shared.models.locations.Range;
import com.salesforce.slds.shared.models.recommendation.Action;
import com.salesforce.slds.shared.models.recommendation.ActionType;
Expand All @@ -17,17 +19,20 @@

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
@Lazy
public class SortAndFilterProcessor implements Processor {

@Override
public List<Recommendation> process(List<Recommendation> recommendations) {
public List<Recommendation> process(Entry entry, List<Recommendation> recommendations) {
List<Range> ranges = extractUtilitiesRange(recommendations);
List<Range> recommendationSuppressionRanges = entry.getRecommendationSuppressionRanges();

return recommendations.stream()
.filter(recommendation -> containItems(recommendation) && filter(recommendation, ranges))
.filter(recommendation -> !shouldSkipRecommendation(recommendationSuppressionRanges, recommendation))
.map(this::sort)
.sorted(Recommendation::compareTo)
.collect(Collectors.toList());
Expand Down Expand Up @@ -61,4 +66,29 @@ Recommendation sort(Recommendation recommendation) {
recommendation.setItems(items);
return recommendation;
}

private boolean shouldSkipRecommendation(List<Range> recommendationSuppressionRanges, Recommendation recommendation) {
HTMLElement element = recommendation.getElement();

// For now we only apply the filtering to inputs of type Markup. This is because CSS
// files are filtered differently (using annotations) at the time when recommendations
// are being created, and that filtering logic is a bit different. So if we applied the
// filtering to CSS files here as well, then it would break backwards compatibility.
// Once we figure out a good way to address backwards compatibility, we should update
// this here to include all file types using the exact filtering logic used here
// in order to keep things consistent.
if (element == null) {
return false;
}

Range elementRange = element.getRange();
return recommendationSuppressionRanges.stream().anyMatch(range ->
range.within(elementRange) || // completely contained withing the ignore range
(
// next line is meant to be ignored and the element indeed starts as the next line
range.getStart().getLine() == range.getEnd().getLine() &&
range.getStart().getLine() == elementRange.getStart().getLine()
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,14 @@ public void run() {
setup();

for (Entry entry : bundle.getEntries()) {

List<Recommendation> recommendations = validators.parallelStream()
.filter(validator -> validator instanceof RecommendationValidator)
.map(validator -> (RecommendationValidator) validator)
.map(validator -> validator.matches(entry, bundle, context))
.flatMap(List::stream)
.collect(aggregator.toList());

entry.setRecommendation(processor.process(recommendations));
entry.setRecommendation(processor.process(entry, recommendations));

List<ComponentOverride> overrides = validators.parallelStream()
.filter(validator -> validator instanceof OverrideValidator)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
import com.salesforce.slds.shared.converters.Converter;
import com.salesforce.slds.shared.converters.TypeConverters;
import com.salesforce.slds.shared.converters.tokens.VarTokenType;
import com.salesforce.slds.shared.models.annotations.AnnotationType;
import com.salesforce.slds.shared.models.context.Context;
import com.salesforce.slds.shared.models.context.ContextKey;
import com.salesforce.slds.shared.models.core.*;
import com.salesforce.slds.shared.models.locations.Range;
import com.salesforce.slds.shared.models.recommendation.Action;
import com.salesforce.slds.shared.models.recommendation.ActionType;
import com.salesforce.slds.shared.models.recommendation.Item;
import com.salesforce.slds.shared.models.recommendation.Recommendation;
import com.salesforce.slds.shared.models.recommendation.RelatedInformation;
import com.salesforce.slds.shared.utils.CssFontShortHandUtilities;
import com.salesforce.slds.tokens.models.DesignToken;
import com.salesforce.slds.tokens.registry.TokenRegistry;
Expand Down Expand Up @@ -53,14 +56,18 @@ public class MobileSLDS_CSSValidator implements RecommendationValidator {
public List<Recommendation> matches(Entry entry, Bundle bundle, Context context) {
List<Recommendation> recommendations = new ArrayList<>();

if (!context.isEnabled(ContextKey.SLDS_MOBILE_VALIDATION)) {
return recommendations;
}

List<Input> inputs = entry.getInputs().stream()
.filter(input -> input.getType() == Input.Type.STYLE).collect(Collectors.toList());

for (Input input : inputs) {
List<Style> styles = input.asRuleSet().getStylesWithAnnotationType();
recommendations.addAll(styles.stream()
.filter(Style::validate)
.map(style -> process(style, context, entry.getEntityType(), entry.getRawContent()))
.map(style -> process(style, context, entry.getRawContent()))
.filter(Objects::nonNull)
.collect(Collectors.toList()));
}
Expand All @@ -71,11 +78,12 @@ public List<Recommendation> matches(Entry entry, Bundle bundle, Context context
/**
* Process CSS RuleSet
* @param style
* @param context
* @param rawContents
* @return Recommendation
*/
private Recommendation process(Style style, Context context, Entry.EntityType entityType, List<String> rawContents) {
Set<Item> items = provideRecommendations(style, context, entityType, rawContents);
private Recommendation process(Style style, Context context, List<String> rawContents) {
Set<Item> items = provideRecommendations(style, context, rawContents);
if (items.isEmpty() == false) {
Recommendation.RecommendationBuilder builder = Recommendation.builder();
builder.input(style).items(items);
Expand Down Expand Up @@ -103,12 +111,12 @@ private boolean isTextOverflowEllipsis(Style style) {
/**
* Provide recommendations
* @param style
* @param context
* @param rawContents
* @return
*/
private Set<Item> provideRecommendations(Style style, Context context, Entry.EntityType entityType, List<String> rawContents) {
private Set<Item> provideRecommendations(Style style, Context context, List<String> rawContents) {
Set<Item> items = new LinkedHashSet<>();
Action.ActionBuilder actionBuilder = Action.builder().fileType(Input.Type.STYLE);
String styleValue = style.getValue();

// Check that SLDS token font size smaller than 14px(fontSize4) is not used.
Expand All @@ -129,42 +137,39 @@ private Set<Item> provideRecommendations(Style style, Context context, Entry.Ent
String size = matcher.group("size");
int fontSize = Integer.parseInt(size);
if (fontSize > 0 && fontSize < 4) {
Range range = cssValidationUtilities.getValueSpecificRange(value, style, rawContents);
actionBuilder.name(ACTION_NAME)
.description(USE_FONT_SIZE_4_OR_LARGER)
.actionType(ActionType.NONE);
items.add(new Item(style.getValue(), actionBuilder.range(range).build()));
addActionItemsForSmallTokenFont(value, styleValue, style, rawContents, items);
}
}
}
});

// Check that font size smaller than 14px is not used.
if (style.getProperty().equals("font-size")) {
addActionItemsForSmallFonts(styleValue, style, rawContents, actionBuilder, items);
addActionItemsForSmallFonts(styleValue, style, rawContents, items);
}

if (style.getProperty().equals("font")) {
CssFontShortHandUtilities fontShortHandUtilities = new CssFontShortHandUtilities(styleValue);
String fontSize = fontShortHandUtilities.getFontSize();
if (fontSize != null) {
addActionItemsForSmallFonts(fontSize, style, rawContents, actionBuilder, items);
addActionItemsForSmallFonts(fontSize, style, rawContents, items);
}
}

// Check that word wrapping is not explicitly specified.
if (isTextOverflowEllipsis(style) || isWhiteSpaceNowrap(style)) {
Range range = cssValidationUtilities.getValueSpecificRange(styleValue, style, rawContents);
actionBuilder.name(ACTION_NAME)
.description(AVOID_TRUNCATION)
.actionType(ActionType.NONE);
items.add(new Item(style.getValue(), actionBuilder.range(range).build()));
addActionItemsForWordWrapping(styleValue, style, rawContents, items);
}

return items;
}

private void addActionItemsForSmallFonts(String cssValue, Style style, List<String> rawContents, Action.ActionBuilder actionBuilder, Set<Item> items) {
private void addActionItemsForSmallTokenFont(String value, String cssValue, Style style, List<String> rawContents, Set<Item> items) {
Range range = cssValidationUtilities.getValueSpecificRange(value, style, rawContents);
addActionItems(cssValue, style, rawContents, items, USE_FONT_SIZE_4_OR_LARGER, range);
}

private void addActionItemsForSmallFonts(String cssValue, Style style, List<String> rawContents, Set<Item> items) {
Range range;
String fontAbsoluteSize = "((?:xx?-)?small)";
Pattern pattern = Pattern.compile(fontAbsoluteSize);
Expand All @@ -177,12 +182,32 @@ private void addActionItemsForSmallFonts(String cssValue, Style style, List<Stri
}

if (!range.equals(Range.EMPTY_RANGE)) {
actionBuilder.name(ACTION_NAME)
.description(USE_FONT_SIZE_14PX_OR_LARGER)
.actionType(ActionType.NONE);
items.add(new Item(style.getValue(), actionBuilder.range(range).build()));
addActionItems(cssValue, style, rawContents, items, USE_FONT_SIZE_14PX_OR_LARGER, range);
}
}

private void addActionItemsForWordWrapping(String cssValue, Style style, List<String> rawContents, Set<Item> items) {
Range range = cssValidationUtilities.getValueSpecificRange(cssValue, style, rawContents);
addActionItems(cssValue, style, rawContents, items, AVOID_TRUNCATION, range);
}

private void addActionItems(String cssValue, Style style, List<String> rawContents, Set<Item> items, String description, Range range) {
List<RelatedInformation> fullRangeInfo = new ArrayList<>();
fullRangeInfo.add(RelatedInformation.builder().range(style.getRange()).build());

Action action = Action.builder()
.value(cssValue)
.range(range)
.description(description)
.name(ACTION_NAME)
.relatedInformation(fullRangeInfo)
.actionType(ActionType.NONE)
.fileType(Input.Type.STYLE)
.build();
Item item = new Item(cssValue, action);
items.add(item);
}

private Range getRangeForSmallFonts(String cssValue, Style style, List<String> rawContents) {
Pattern pattern = Pattern.compile(RegexPattern.FONT_SIZE_PATTERN);
Matcher matcher = pattern.matcher(cssValue);
Expand Down
Loading

0 comments on commit 3288a5f

Please sign in to comment.