doExtendChaining method

List<Extend> doExtendChaining(
  1. List<Extend> extendsList,
  2. List<Extend> extendsListTarget,
  3. [int iterationCount = 0]
)

Chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting the selector we would do normally, but we are also adding an extend with the same target selector this means this new extend can then go and alter other extends

this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if we look at each selector at a time, as is done in visitRuleset

Implementation

List<Extend> doExtendChaining(
    List<Extend> extendsList, List<Extend> extendsListTarget,
    [int iterationCount = 0]) {
  final extendsToAdd = <Extend>[];
  final extendVisitor = this;

  // loop through comparing every extend with every target extend.
  // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
  // e.g.  .a:extend(.b) {}  and .b:extend(.c) {} then the first extend extends the second one
  // and the second is the target.
  // the separation into two lists allows us to process a subset of chains with a bigger set, as is the
  // case when processing media queries
  for (var extendIndex = 0; extendIndex < extendsList.length; extendIndex++) {
    for (var targetExtendIndex = 0;
        targetExtendIndex < extendsListTarget.length;
        targetExtendIndex++) {
      final extend = extendsList[extendIndex];
      final targetExtend = extendsListTarget[targetExtendIndex];

      // look for circular references
      if (extend.parentIds.contains(targetExtend.objectId)) continue;

      // find a match in the target extends self selector (the bit before :extend)
      final selectorPath = <Selector>[targetExtend.selfSelectors[0]];
      final matches = extendVisitor.findMatch(extend, selectorPath);

      if (matches.isNotEmpty) {
        extend.hasFoundMatches = true;

        //we found a match, so for each self selector.
        extend.selfSelectors.forEach((Selector selfSelector) {
          final info = targetExtend.visibilityInfo();

          //process the extend as usual
          final newSelector = extendVisitor.extendSelector(
              matches, selectorPath, selfSelector,
              isVisible: extend.isVisible());

          // but now we create a new extend from it
          final newExtend = Extend(targetExtend.selector, targetExtend.option,
              0, targetExtend.currentFileInfo, info)
            ..selfSelectors = newSelector;

          // add the extend onto the list of extends for that selector
          newSelector.last.extendList = <Extend>[newExtend];

          // record that we need to add it.
          extendsToAdd.add(newExtend);
          newExtend.ruleset = targetExtend.ruleset;

          // remember its parents for circular references
          newExtend.parentIds
            ..addAll(targetExtend.parentIds)
            ..addAll(extend.parentIds);

          // only process the selector once.. if we have :extend(.a,.b) then multiple
          // extends will look at the same selector path, so when extending
          // we know that any others will be duplicates in terms of what is added to the css
          if (targetExtend.firstExtendOnThisSelectorPath) {
            newExtend.firstExtendOnThisSelectorPath = true;
            targetExtend.ruleset.paths.add(newSelector);
          }
        });
      }
    }
  }

  if (extendsToAdd.isNotEmpty) {
    // try to detect circular references to stop a stack overflow.
    // may no longer be needed.
    extendChainCount++;
    if (iterationCount > 100) {
      var selectorOne = r'{unable to calculate}';
      var selectorTwo = r'{unable to calculate}';
      try {
        selectorOne = extendsToAdd[0].selfSelectors[0].toCSS(null);
        selectorTwo = extendsToAdd[0].selector.toCSS(null);
      } catch (_) {}
      throw LessExceptionError(LessError(
          message:
              'extend circular reference detected. One of the circular extends is currently:$selectorOne:extend($selectorTwo)'));
    }

    // now process the new extends on the existing rules so that we can handle
    // a extending b extending c ectending d extending e...
    return extendsToAdd
      ..addAll(extendVisitor.doExtendChaining(
          extendsToAdd, extendsListTarget, iterationCount + 1));
  } else {
    return extendsToAdd;
  }

//3.0.0 20160714
// doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
//     //
//     // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering
//     // and pasting the selector we would do normally, but we are also adding an extend with the same target selector
//     // this means this new extend can then go and alter other extends
//     //
//     // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
//     // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already
//     // processed if we look at each selector at a time, as is done in visitRuleset
//
//     var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath,
//         extend, targetExtend, newExtend;
//
//     iterationCount = iterationCount || 0;
//
//     //loop through comparing every extend with every target extend.
//     // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
//     // e.g.  .a:extend(.b) {}  and .b:extend(.c) {} then the first extend extends the second one
//     // and the second is the target.
//     // the separation into two lists allows us to process a subset of chains with a bigger set, as is the
//     // case when processing media queries
//     for (extendIndex = 0; extendIndex < extendsList.length; extendIndex++) {
//         for (targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++) {
//
//             extend = extendsList[extendIndex];
//             targetExtend = extendsListTarget[targetExtendIndex];
//
//             // look for circular references
//             if ( extend.parent_ids.indexOf( targetExtend.object_id ) >= 0 ) { continue; }
//
//             // find a match in the target extends self selector (the bit before :extend)
//             selectorPath = [targetExtend.selfSelectors[0]];
//             matches = extendVisitor.findMatch(extend, selectorPath);
//
//             if (matches.length) {
//                 extend.hasFoundMatches = true;
//
//                 // we found a match, so for each self selector..
//                 extend.selfSelectors.forEach(function(selfSelector) {
//                     var info = targetExtend.visibilityInfo();
//
//                     // process the extend as usual
//                     newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector, extend.isVisible());
//
//                     // but now we create a new extend from it
//                     newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0, targetExtend.fileInfo(), info);
//                     newExtend.selfSelectors = newSelector;
//
//                     // add the extend onto the list of extends for that selector
//                     newSelector[newSelector.length - 1].extendList = [newExtend];
//
//                     // record that we need to add it.
//                     extendsToAdd.push(newExtend);
//                     newExtend.ruleset = targetExtend.ruleset;
//
//                     //remember its parents for circular references
//                     newExtend.parent_ids = newExtend.parent_ids.concat(targetExtend.parent_ids, extend.parent_ids);
//
//                     // only process the selector once.. if we have :extend(.a,.b) then multiple
//                     // extends will look at the same selector path, so when extending
//                     // we know that any others will be duplicates in terms of what is added to the css
//                     if (targetExtend.firstExtendOnThisSelectorPath) {
//                         newExtend.firstExtendOnThisSelectorPath = true;
//                         targetExtend.ruleset.paths.push(newSelector);
//                     }
//                 });
//             }
//         }
//     }
//
//     if (extendsToAdd.length) {
//         // try to detect circular references to stop a stack overflow.
//         // may no longer be needed.
//         this.extendChainCount++;
//         if (iterationCount > 100) {
//             var selectorOne = "{unable to calculate}";
//             var selectorTwo = "{unable to calculate}";
//             try {
//                 selectorOne = extendsToAdd[0].selfSelectors[0].toCSS();
//                 selectorTwo = extendsToAdd[0].selector.toCSS();
//             }
//             catch(e) {}
//             throw { message: "extend circular reference detected. One of the circular extends is currently:" +
//                 selectorOne + ":extend(" + selectorTwo + ")"};
//         }
//
//         // now process the new extends on the existing rules so that we can handle a extending b extending c extending
//         // d extending e...
//         return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount + 1));
//     } else {
//         return extendsToAdd;
//     }
// },
}