Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Fixes #329 and cleans up nested appender class.
  • Loading branch information
meri committed Feb 11, 2016
1 parent 9321532 commit 1b3de72
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 160 deletions.
5 changes: 2 additions & 3 deletions src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g
Expand Up @@ -1324,10 +1324,9 @@ EXCLAMATION_MARK: '!';
fragment APPENDER_FRAGMENT: '&';
fragment MEANINGFULL_WHITESPACE: ;
//TODO explain why I need appender fragment
APPENDER: ws1=WS_FRAGMENT? app=APPENDER_FRAGMENT ws2=WS_FRAGMENT? {
emitAs($ws1, MEANINGFULL_WHITESPACE);
APPENDER: ws1=WS_FRAGMENT? app=APPENDER_FRAGMENT {
emitAs($ws1, WS);
emitAs($app, APPENDER);
emitAs($ws2, MEANINGFULL_WHITESPACE);
};

// -----------------
Expand Down
Expand Up @@ -8,13 +8,8 @@

public class NestedSelectorAppender extends SelectorPart {

private boolean directlyBefore;
private boolean directlyAfter;

public NestedSelectorAppender(HiddenTokenAwareTree underlyingStructure, boolean directlyBefore, boolean directlyAfter, SelectorCombinator leadingCombinator) {
public NestedSelectorAppender(HiddenTokenAwareTree underlyingStructure, SelectorCombinator leadingCombinator) {
super(underlyingStructure, leadingCombinator);
this.directlyBefore = directlyBefore;
this.directlyAfter = directlyAfter;
}

@Override
Expand All @@ -23,20 +18,8 @@ public List<ASTCssNode> getChilds() {
return super.getChilds();
}

public boolean isDirectlyBefore() {
return directlyBefore;
}

public void setDirectlyBefore(boolean directlyBefore) {
this.directlyBefore = directlyBefore;
}

public boolean isDirectlyAfter() {
return directlyAfter;
}

public void setDirectlyAfter(boolean directlyAfter) {
this.directlyAfter = directlyAfter;
return !hasLeadingCombinator();
}

@Override
Expand Down Expand Up @@ -64,14 +47,13 @@ public Selector getParentAsSelector() {
return (Selector) super.getParent();
}

//FIXME (now) proper toString
@Override
public String toString() {
StringBuilder builder = new StringBuilder("\"");
if (!isDirectlyAfter())
builder.append(" ");
builder.append("&");
if (!isDirectlyBefore())
builder.append(" ");
builder.append("\"");

return builder.toString();
Expand Down
Expand Up @@ -15,6 +15,10 @@ public SelectorCombinator(HiddenTokenAwareTree underlyingStructure) {
this(underlyingStructure, CombinatorType.DESCENDANT, CombinatorType.DESCENDANT.getDefaultSymbol());
}

public SelectorCombinator(HiddenTokenAwareTree underlyingStructure, CombinatorType combinator) {
this(underlyingStructure, combinator, combinator.getDefaultSymbol());
}

public SelectorCombinator(HiddenTokenAwareTree underlyingStructure, CombinatorType combinator, String symbol) {
super(underlyingStructure);
this.combinator = combinator;
Expand Down
Expand Up @@ -5,17 +5,55 @@
import java.util.Collection;
import java.util.List;

import com.github.sommeri.less4j.core.ast.ASTCssNode;
import com.github.sommeri.less4j.core.ast.ElementSubsequent;
import com.github.sommeri.less4j.core.ast.Extend;
import com.github.sommeri.less4j.core.ast.NestedSelectorAppender;
import com.github.sommeri.less4j.core.ast.Selector;
import com.github.sommeri.less4j.core.ast.SelectorCombinator;
import com.github.sommeri.less4j.core.ast.SelectorPart;
import com.github.sommeri.less4j.core.ast.SimpleSelector;
import com.github.sommeri.less4j.core.problems.BugHappened;
import com.github.sommeri.less4j.utils.ArraysUtils;

public class SelectorsManipulator {

public Selector removeAppenders(Selector selector) {
selector = replaceLeadingAppendersByEmptiness(selector);
if (!selector.containsAppender())
return selector;

Selector replacement = replaceMiddleAppendersByEmptiness(selector);
return replacement;
}

private Selector replaceMiddleAppendersByEmptiness(Selector selector) {
Selector empty = new Selector(selector.getUnderlyingStructure(), createEmptySimpleSelector(selector));
List<Selector> replaceAppenders = replaceAppenders(selector, Arrays.asList(empty));
Selector replacement = replaceAppenders.get(0);
return replacement;
}

private Selector replaceLeadingAppendersByEmptiness(Selector selector) {
while (!selector.isEmpty() && selector.getHead().isAppender()) {
selector.getHead().setParent(null);
selector.removeHead();
}

if (selector.isEmpty()) {
SimpleSelector empty = createEmptySimpleSelector(selector);
selector.addPart(empty);
}
return selector;
}

private SimpleSelector createEmptySimpleSelector(ASTCssNode underlyingStructureSource) {
SimpleSelector empty = new SimpleSelector(underlyingStructureSource.getUnderlyingStructure(), null, null, true);
empty.setEmptyForm(true);
return empty;
}


public List<Selector> replaceAppenders(Selector inSelector, List<Selector> replacements) {
if (!inSelector.containsAppender()) {
return indirectJoinAll(replacements, null, inSelector);
Expand Down Expand Up @@ -63,7 +101,7 @@ public Selector indirectJoinNoClone(Selector first, SelectorCombinator combinato

public Selector indirectJoinNoClone(Selector first, SelectorCombinator combinator, List<SelectorPart> second, List<Extend> extend) {
first.addExtends(extend);

if (second.isEmpty())
return first;

Expand Down Expand Up @@ -126,7 +164,8 @@ public void directlyJoinParts(SelectorPart first, SelectorPart second) {
private Collection<Selector> replaceFirstAppender(Selector selector, List<Selector> previousSelectors) {
if (selector.getHead().isAppender()) {
NestedSelectorAppender appender = (NestedSelectorAppender) selector.getHead();
return joinAll(previousSelectors, chopOffHead(selector), appender.getLeadingCombinator(), appender.isDirectlyBefore());
Selector reminder = chopOffHead(selector);
return joinAll(previousSelectors, reminder, appender.getLeadingCombinator(), isDirectlyAfterPreviousPart(reminder));
}

// appender somewhere in the middle
Expand All @@ -136,17 +175,23 @@ private Collection<Selector> replaceFirstAppender(Selector selector, List<Select

Selector afterAppender = splitOn(selector, appender);
List<Selector> partialResults = joinAll(selector, previousSelectors, appender.getLeadingCombinator(), appender.isDirectlyAfter());
return joinAll(partialResults, afterAppender, null, appender.isDirectlyBefore());
//FIXME (now) last parameter should be nide and repats cold in 146
return joinAll(partialResults, afterAppender, null, isDirectlyAfterPreviousPart(afterAppender));
}

private boolean isDirectlyAfterPreviousPart(Selector selector) {
return selector == null ? false : !selector.hasLeadingCombinator();
}

private List<Selector> joinAll(Selector first, List<Selector> seconds, SelectorCombinator leadingCombinator, boolean appenderDirectlyPlaced) {
//pretending null as after appender, less.js does not handle this case anyway
//this case being:
//.input-group-addon + {
// .selector {
// heeej: hoou;
// }
//}
// pretending null as after appender, less.js does not handle this case
// anyway
// this case being:
// .input-group-addon + {
// .selector {
// heeej: hoou;
// }
// }
boolean directJoin = isDirect(leadingCombinator, appenderDirectlyPlaced, null);
if (directJoin)
return directJoinAll(first, seconds);
Expand Down Expand Up @@ -198,26 +243,27 @@ private List<Selector> directJoinAll(Selector first, List<Selector> seconds) {
}

private boolean isDirect(SelectorCombinator beforeAppenderCombinator, boolean appenderDirectlyPlaced, Selector afterAppender) {
return beforeAppenderCombinator == null && appenderDirectlyPlaced && (afterAppender == null || !afterAppender.hasLeadingCombinator());
boolean result = beforeAppenderCombinator == null && appenderDirectlyPlaced && (afterAppender == null || !afterAppender.hasLeadingCombinator());
return result;
}

private Selector splitOn(Selector selector, NestedSelectorAppender appender) {
List<SelectorPart> parts = selector.getParts();
int indexOfAppender = parts.indexOf(appender);
List<SelectorPart> appenderAndAfter = parts.subList(indexOfAppender, parts.size());

//remove appender
// remove appender
appenderAndAfter.remove(0);
appender.setParent(null);

//create selector with after appender parts
// create selector with after appender parts
Selector result = null;
if (!appenderAndAfter.isEmpty()) {
result = new Selector(selector.getUnderlyingStructure(), new ArrayList<SelectorPart>(appenderAndAfter));
result.configureParentToAllChilds();
}

//leave only before appender parts in original selector
// leave only before appender parts in original selector
appenderAndAfter.clear();
return result;
}
Expand Down
@@ -1,16 +1,16 @@
package com.github.sommeri.less4j.core.compiler.selectors;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.github.sommeri.less4j.core.ast.ASTCssNode;
import com.github.sommeri.less4j.core.ast.RuleSet;
import com.github.sommeri.less4j.core.ast.Selector;
import com.github.sommeri.less4j.core.ast.SimpleSelector;

public class UselessLessElementsRemover {

private final SelectorsManipulator manipulator = new SelectorsManipulator();

public void removeUselessLessElements(ASTCssNode node) {
switch (node.getType()) {
case RULE_SET:
Expand All @@ -32,43 +32,10 @@ public void removeUselessLessElements(ASTCssNode node) {
public void removeFrom(RuleSet ruleSet) {
List<Selector> selectors = ruleSet.getSelectors();
for (Selector selector : selectors) {
removeFrom(selector, ruleSet);
Selector replacement = manipulator.removeAppenders(selector);
if (replacement!=selector)
ruleSet.replaceSelector(selector, replacement);
}
}

public void removeFrom(Selector selector, RuleSet parentRuleSet) {
selector = replaceLeadingAppendersByEmptiness(selector, parentRuleSet);
if (!selector.containsAppender())
return ;

replaceMiddleAppendersByEmptiness(selector, parentRuleSet);
}

private void replaceMiddleAppendersByEmptiness(Selector selector, RuleSet parentRuleSet) {
SelectorsManipulator manipulator= new SelectorsManipulator();
Selector empty = new Selector(selector.getUnderlyingStructure(), createEmptySimpleSelector(selector));
List<Selector> replaceAppenders = manipulator.replaceAppenders(selector, Arrays.asList(empty));
Selector replacement = replaceAppenders.get(0);
parentRuleSet.replaceSelector(selector, replacement);
}

private Selector replaceLeadingAppendersByEmptiness(Selector selector, RuleSet parentRuleSet) {
while (!selector.isEmpty() && selector.getHead().isAppender()) {
selector.getHead().setParent(null);
selector.removeHead();
}

if (selector.isEmpty()) {
SimpleSelector empty = createEmptySimpleSelector(selector);
selector.addPart(empty);
}
return selector;
}

private SimpleSelector createEmptySimpleSelector(ASTCssNode underlyingStructureSource) {
SimpleSelector empty = new SimpleSelector(underlyingStructureSource.getUnderlyingStructure(), null, null, true);
empty.setEmptyForm(true);
return empty;
}

}
Expand Up @@ -18,7 +18,11 @@ public class NestedInRulesetStack {
private LinkedList<ASTCssNode> nestedNodes = new LinkedList<ASTCssNode>();

public NestedInRulesetStack(RuleSet topLevelNode) {
pushSelectors(topLevelNode);
List<Selector> topSelectors = new ArrayList<Selector>();
for (Selector selector : topLevelNode.getSelectors()) {
topSelectors.add(selectorsManipulator.removeAppenders(selector.clone()));
}
selectors.push(topSelectors);
}

public void popSelectors() {
Expand All @@ -45,7 +49,8 @@ public void collect(Directive directive) {
private void combine(RuleSet ruleSet, List<Selector> previousSelectors) {
List<Selector> result = new ArrayList<Selector>();
for (Selector selector : ruleSet.getSelectors()) {
//FIXME: meri: review whether they have all the right visibility in the end
// FIXME: meri: review whether they have all the right visibility in the
// end
result.addAll(selectorsManipulator.replaceAppenders(selector, previousSelectors));
}

Expand Down
Expand Up @@ -947,17 +947,9 @@ public ArgumentDeclaration handleArgumentDeclaration(HiddenTokenAwareTree token)

@Override
public ASTCssNode handleNestedAppender(HiddenTokenAwareTree token) {
boolean directlyBefore = true;
boolean directlyAfter = true;
if (token.getChildren().size() == 2) {
directlyBefore = isMeaningfullWhitespace(token);
directlyAfter = !directlyBefore;
} else if (token.getChildren().size() == 3) {
directlyBefore = false;
directlyAfter = false;
}

return new NestedSelectorAppender(token, directlyBefore, directlyAfter, null);
//FIXME (now) set descendant in constructor and remove `directly` booleans
NestedSelectorAppender result = new NestedSelectorAppender(token, null);
return result;
}

public SimpleSelector handleElementName(HiddenTokenAwareTree kid) {
Expand All @@ -982,11 +974,6 @@ public EscapedSelector handleEscapedSelector(HiddenTokenAwareTree token) {
return new EscapedSelector(valueToken, quotedText.substring(2, quotedText.length() - 1), "" + quotedText.charAt(1), null);
}

private boolean isMeaningfullWhitespace(HiddenTokenAwareTree kid) {
int type = kid.getChild(0).getGeneralType();
return type == LessLexer.MEANINGFULL_WHITESPACE || type == LessLexer.DUMMY_MEANINGFULL_WHITESPACE;
}

@Override
public Keyframes handleKeyframes(HiddenTokenAwareTree token) {
Iterator<HiddenTokenAwareTree> children = token.getChildren().iterator();
Expand Down
34 changes: 17 additions & 17 deletions src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java
Expand Up @@ -992,15 +992,18 @@ private boolean appendSimpleSelector(SimpleSelector selector) {
return true;
}

private void appendSimpleSelectorTail(SimpleSelector selector) {
List<ElementSubsequent> allChilds = selector.getSubsequent();
for (ElementSubsequent astCssNode : allChilds) {
append(astCssNode);
public boolean appendSelectorCombinator(SelectorCombinator combinator) {
SelectorCombinator.CombinatorType realCombinator = combinator.getCombinatorType();
switch (realCombinator) {
case DESCENDANT:
cssOnly.ensureSeparator();
break;

default:
cssOnly.ensureSeparator().append(combinator.getSymbol());

}
}

private boolean appendCssClass(CssClass cssClass) {
cssAndSM.append(cssClass.getFullName(), cssClass.getUnderlyingStructure());
return true;
}

Expand All @@ -1012,18 +1015,15 @@ private void appendSimpleSelectorHead(SimpleSelector selector) {
}
}

public boolean appendSelectorCombinator(SelectorCombinator combinator) {
SelectorCombinator.CombinatorType realCombinator = combinator.getCombinatorType();
switch (realCombinator) {
case DESCENDANT:
cssOnly.ensureSeparator();
break;

default:
cssOnly.ensureSeparator().append(combinator.getSymbol());

private void appendSimpleSelectorTail(SimpleSelector selector) {
List<ElementSubsequent> allChilds = selector.getSubsequent();
for (ElementSubsequent astCssNode : allChilds) {
append(astCssNode);
}
}

private boolean appendCssClass(CssClass cssClass) {
cssAndSM.append(cssClass.getFullName(), cssClass.getUnderlyingStructure());
return true;
}

Expand Down

0 comments on commit 1b3de72

Please sign in to comment.