Package com.github.sommeri.less4j.core.compiler.stages

Source Code of com.github.sommeri.less4j.core.compiler.stages.MixinReferenceFinder

package com.github.sommeri.less4j.core.compiler.stages;

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

import com.github.sommeri.less4j.core.ast.GeneralBody;
import com.github.sommeri.less4j.core.ast.MixinReference;
import com.github.sommeri.less4j.core.ast.ReusableStructure;
import com.github.sommeri.less4j.core.ast.ReusableStructureName;
import com.github.sommeri.less4j.core.compiler.scopes.FullMixinDefinition;
import com.github.sommeri.less4j.core.compiler.scopes.InScopeSnapshotRunner;
import com.github.sommeri.less4j.core.compiler.scopes.InScopeSnapshotRunner.ITask;
import com.github.sommeri.less4j.core.compiler.scopes.IScope;

public class MixinReferenceFinder {

  private final ReferencesSolver parentSolver;
  private final AstNodesStack semiCompiledNodes;
  private boolean foundNamespace = false;

  public MixinReferenceFinder(ReferencesSolver referencesSolver, AstNodesStack semiCompiledNodes) {
    this.parentSolver = referencesSolver;
    this.semiCompiledNodes = semiCompiledNodes;
  }

  /**
   * Find referenced mixins by name. It does not check number of parameters nor
   * guards, because the search should stop once any mixin with referenced name
   * is found.
   *
   * However, it checks every encountered mixin for "legal" cycle (e.g. ruleset
   * needing itself) and completely ignores those who would cycle that way.
   *
   */
  public List<FullMixinDefinition> getNearestMixins(IScope scope, MixinReference reference) {
    foundNamespace = false;
    List<String> nameChain = reference.getNameChainAsStrings();
    IScope space = scope;

    List<FullMixinDefinition> result = findInMatchingNamespace(scope, nameChain, reference);
    while (result.isEmpty() && space.hasParent()) {
      space = space.getParent();
      result = findInMatchingNamespace(space, nameChain, reference);
    }
    return result;
  }

  public boolean foundNamespace() {
    return foundNamespace;
  }

  private List<FullMixinDefinition> getNearestLocalMixins(IScope scope, List<String> nameChain, ReusableStructureName name) {
    if (scope.isBodyOwnerScope())
      scope = scope.firstChild();

    List<FullMixinDefinition> mixins = scope.getMixinsByName(nameChain, name);
    if (mixins != null)
      mixins = removeLegalCycles(mixins, name);

    return mixins == null ? new ArrayList<FullMixinDefinition>() : mixins;
  }

  private List<FullMixinDefinition> removeLegalCycles(List<FullMixinDefinition> value, ReusableStructureName name) {
    List<FullMixinDefinition> result = new ArrayList<FullMixinDefinition>();
    for (FullMixinDefinition mixin : value) {
      if (!participatesInCuttableCycle(mixin))
        result.add(mixin);
    }
    return result;
  }

  private boolean participatesInCuttableCycle(FullMixinDefinition mixin) {
    /*
     * Detecting following kind of cycle:
     *
     * .simpleCase { .simpleCase(); }
     *
     * Note: the detection feels too hackish, but works and hotfix is needed
     * (bootstrap).
     */
    return mixin.getMixin().isAlsoRuleset() && semiCompiledNodes.contains(mixin.getMixin());
  }

  private List<FullMixinDefinition> findInMatchingNamespace(IScope scope, List<String> nameChain, MixinReference reference) {
    List<FullMixinDefinition> result = new ArrayList<FullMixinDefinition>();

    if (nameChain.isEmpty()) {
      foundNamespace = true;
    } else {
      for (int prefix = 1; prefix <= nameChain.size(); prefix++) {
        String name = toName(nameChain.subList(0, prefix));
        List<String> theRest = prefix==nameChain.size()? new ArrayList<String>(): nameChain.subList(prefix, nameChain.size());

        for (FullMixinDefinition fullMixin : scope.getMixinsByName(name)) {
          List<FullMixinDefinition> foundInNamespaces = buildAndFind(fullMixin, theRest, reference);
          result.addAll(foundInNamespaces);
        }
      }
    }

    result.addAll(getNearestLocalMixins(scope, nameChain, reference.getFinalName()));
    return result;
  }

  private String toName(List<String> list) {
    StringBuilder builder = new StringBuilder();
    for (String string : list) {
      builder.append(string);
    }
    return builder.toString();
  }

  private List<FullMixinDefinition> buildAndFind(FullMixinDefinition fullMixin, final List<String> nameChain, final MixinReference reference) {

    final List<FullMixinDefinition> result = new ArrayList<FullMixinDefinition>();

    final ReusableStructure mixin = fullMixin.getMixin();
    final GeneralBody bodyClone = mixin.getBody().clone();
    final IScope scope = fullMixin.getScope();

    InScopeSnapshotRunner.runInLocalDataSnapshot(scope, new ITask() {

      @Override
      public void run() {
        // Referenced namespace attempts to import its own mixins. Do not
        // try to compile it.
        if (!semiCompiledNodes.contains(bodyClone)) {
          parentSolver.unsafeDoSolveReferences(bodyClone, scope);
        }

        List<FullMixinDefinition> found = findInMatchingNamespace(scope, nameChain, reference);
        result.addAll(found);
      }

    });

    return result;
  }

}
TOP

Related Classes of com.github.sommeri.less4j.core.compiler.stages.MixinReferenceFinder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.