| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 | 'use strict';var assert = require('assert');var walk = require('pug-walk');function error() {  throw require('pug-error').apply(null, arguments);}module.exports = link;function link(ast) {  assert(ast.type === 'Block', 'The top level element should always be a block');  var extendsNode = null;  if (ast.nodes.length) {    var hasExtends = ast.nodes[0].type === 'Extends';    checkExtendPosition(ast, hasExtends);    if (hasExtends) {      extendsNode = ast.nodes.shift();    }  }  ast = applyIncludes(ast);  ast.declaredBlocks = findDeclaredBlocks(ast);  if (extendsNode) {    var mixins = [];    var expectedBlocks = [];    ast.nodes.forEach(function addNode(node) {      if (node.type === 'NamedBlock') {        expectedBlocks.push(node);      } else if (node.type === 'Block') {        node.nodes.forEach(addNode);      } else if (node.type === 'Mixin' && node.call === false) {        mixins.push(node);      } else {        error('UNEXPECTED_NODES_IN_EXTENDING_ROOT', 'Only named blocks and mixins can appear at the top level of an extending template', node);      }    });    var parent = link(extendsNode.file.ast);    extend(parent.declaredBlocks, ast);    var foundBlockNames = [];    walk(parent, function (node) {      if (node.type === 'NamedBlock') {        foundBlockNames.push(node.name);      }    });    expectedBlocks.forEach(function (expectedBlock) {      if (foundBlockNames.indexOf(expectedBlock.name) === -1) {        error(          'UNEXPECTED_BLOCK',          'Unexpected block ' + expectedBlock.name,          expectedBlock        );      }    });    Object.keys(ast.declaredBlocks).forEach(function (name) {      parent.declaredBlocks[name] = ast.declaredBlocks[name];    });    parent.nodes = mixins.concat(parent.nodes);    parent.hasExtends = true;    return parent;  }  return ast;}function findDeclaredBlocks(ast) /*: {[name: string]: Array<BlockNode>}*/ {  var definitions = {};  walk(ast, function before(node) {    if (node.type === 'NamedBlock' && node.mode === 'replace') {      definitions[node.name] = definitions[node.name] || [];      definitions[node.name].push(node);    }  });  return definitions;}function flattenParentBlocks(parentBlocks, accumulator) {  accumulator = accumulator || [];  parentBlocks.forEach(function (parentBlock) {    if (parentBlock.parents) {      flattenParentBlocks(parentBlock.parents, accumulator);    }    accumulator.push(parentBlock);  });  return accumulator;}function extend(parentBlocks, ast) {  var stack = {};  walk(ast, function before(node) {    if (node.type === 'NamedBlock') {      if (stack[node.name] === node.name) {        return node.ignore = true;      }      stack[node.name] = node.name;      var parentBlockList = parentBlocks[node.name] ? flattenParentBlocks(parentBlocks[node.name]) : [];      if (parentBlockList.length) {        node.parents = parentBlockList;        parentBlockList.forEach(function (parentBlock) {          switch (node.mode) {            case 'append':              parentBlock.nodes = parentBlock.nodes.concat(node.nodes);              break;            case 'prepend':              parentBlock.nodes = node.nodes.concat(parentBlock.nodes);              break;            case 'replace':              parentBlock.nodes = node.nodes;              break;          }        });      }    }  }, function after(node) {    if (node.type === 'NamedBlock' && !node.ignore) {      delete stack[node.name];    }  });}function applyIncludes(ast, child) {  return walk(ast, function before(node, replace) {    if (node.type === 'RawInclude') {      replace({type: 'Text', val: node.file.str.replace(/\r/g, '')});    }  }, function after(node, replace) {    if (node.type === 'Include') {      var childAST = link(node.file.ast);      if (childAST.hasExtends) {        childAST = removeBlocks(childAST);      }      replace(applyYield(childAST, node.block));    }  });}function removeBlocks(ast) {  return walk(ast, function (node, replace) {    if (node.type === 'NamedBlock') {      replace({        type: 'Block',        nodes: node.nodes      });    }  });}function applyYield(ast, block) {  if (!block || !block.nodes.length) return ast;  var replaced = false;  ast = walk(ast, null, function (node, replace) {    if (node.type === 'YieldBlock') {      replaced = true;      node.type = 'Block';      node.nodes = [block];    }  });  function defaultYieldLocation(node) {    var res = node;    for (var i = 0; i < node.nodes.length; i++) {      if (node.nodes[i].textOnly) continue;      if (node.nodes[i].type === 'Block') {        res = defaultYieldLocation(node.nodes[i]);      } else if (node.nodes[i].block && node.nodes[i].block.nodes.length) {        res = defaultYieldLocation(node.nodes[i].block);      }    }    return res;  }  if (!replaced) {    // todo: probably should deprecate this with a warning    defaultYieldLocation(ast).nodes.push(block);  }  return ast;}function checkExtendPosition(ast, hasExtends) {  var legitExtendsReached = false;  walk(ast, function (node) {    if (node.type === 'Extends') {      if (hasExtends && !legitExtendsReached) {        legitExtendsReached = true;      } else {        error('EXTENDS_NOT_FIRST', 'Declaration of template inheritance ("extends") should be the first thing in the file. There can only be one extends statement per file.', node);      }    }  });}
 |