<commit_before>'use strict';

var walk = require('jade-walk');
var repeat = require('repeat-string');
var constantinople = require('constantinople');
var objectAssign = require('object-assign');

module.exports = generateCode;
module.exports.CodeGenerator = Compiler;
module.exports.defaultOptions = {
  indentChar: '  ',
  useColon: false, // Use block expansion when possible
  preferredQuote: "'" // Preferred quotation mark used in attributes
};

function generateCode(ast, options) {
  return (new Compiler(ast, options)).compile();
}

function Compiler(node, options) {
  this.options = options = objectAssign({}, module.exports.defaultOptions, options);
  this.node = cleanUpAST(node);
  this.indents = -1; // -1 to counter the root-level Block
  // If the current node is a nested node (through the block expansion syntax
  // (`p: strong`) or with nested filters `:uglify-js:babel`). This property
  // makes the visit* methods continue on the previous line instead of
  // starting a new line.
  this.nested = false;
  // This property directs the code generator to continue on the previous line
  // and use inline interpolation tokens (`#{}` and `#[]`) if possible.
  this.inline = false;
}

Compiler.prototype = {
  compile: function() {
    this.buf = [];
    this.visit(this.node);
    return this.buf.join('\n');
  },

  indent: function(add) {
    return repeat(this.options.indentChar, this.indents + (add !== undefined ? add : 0));
  },

  buffer: function(content) {
    if (content !== undefined) this.buf[this.buf.length - 1] += content;
  },

  bufLine: function(content, indents) {
    if (content === undefined) content = '';
    this.buf.push(this.indent(indents) + content);
  },

  quote: function(name) {
    var attrOut = '';
    if (this.options.preferredQuote === "'") {
      if (name.indexOf("'") !== -1 && name.indexOf('"') === -1) {
        attrOut += '"';
        attrOut += name;
        attrOut += '"';
      } else {
        attrOut += "'";
        attrOut += name.replace(/'/g, "\\'");
        attrOut += "'";
      }
    } else {
      if (name.indexOf('"') !== -1 && name.indexOf("'") === -1) {
        attrOut += "'";
        attrOut += name;
        attrOut += "'";
      } else {
        attrOut += '"';
        attrOut += name.replace(/"/g, '\\"');
        attrOut += '"';
      }
    }
    return attrOut;
  },

  attrs: function(attrs) {
    var regularAttrs = [];
    var classes = '';
    var id;
    attrs.forEach(function(attr) {
      var constVal = '';
      try {
        constVal = constantinople.toConstant(attr.val);
      } catch (ex) {}

      if (attr.name === 'class' && !attr.escaped && constVal &&
                 /^\-?[_a-z][_a-z0-9\-]*$/i.test(constVal)) {
        classes += '.' + constVal;
      } else if (attr.name === 'id' && !id && !attr.escaped && constVal &&
                 /^[\w-]+$/.test(constVal)) {
        id = constVal;
      } else {
        var attrOut = '';

        // name
        if (/^\w[^()[\]=!,`'"\s]*$/.test(attr.name)) {
          attrOut += attr.name;
        } else {
          var name = attr.name.replace(/\\/g, '\\\\');
          attrOut += this.quote(name);
        }

        if (!(typeof constVal === 'boolean' && constVal === true)) {
          // operator
          if (!attr.escaped) attrOut += '!';
          attrOut += '=';

          // value
          attrOut += attr.val;
        }

        regularAttrs.push(attrOut);
      }
    }.bind(this));

    var out = '';
    if (id) out += '#' + id;
    out += classes;
    if (regularAttrs.length) out += '(' + regularAttrs.join(' ') + ')';

    return out;
  },

  attributeBlocks: function(attributeBlocks) {
    return attributeBlocks.reduce(function (prev, cur) {
      return prev + '&attributes(' + cur + ')';
    }, '');
  },

  useColon: function(block, parent) {
    if (!this.options.useColon) return false;

    var parentOk = parent && ({
      Mixin: parent && parent.call,
      Tag: true,
      When: true
    })[parent.type];
    var node = block.nodes[0];
    var blockOk = block.nodes.length === 1 && ({
      Code: true,
      Filter: true,
      Mixin: node.call,
      Tag: true,
      Text: true
    })[node.type];
    return parentOk && parent.block === block && blockOk;
  },

  // heuristics to determine if dot syntax is preferred over piped text
  useDot: function(block, parent) {
    if (!block.nodes.length) return false;

    // line count
    var lines = block.nodes[block.nodes.length - 1].line - block.nodes[0].line + 1;
    if (lines === 1) return false;<commit_msg>Allow Include as parent of block expansion<commit_after>'use strict';

var walk = require('jade-walk');
var repeat = require('repeat-string');
var constantinople = require('constantinople');
var objectAssign = require('object-assign');

module.exports = generateCode;
module.exports.CodeGenerator = Compiler;
module.exports.defaultOptions = {
  indentChar: '  ',
  useColon: false, // Use block expansion when possible
  preferredQuote: "'" // Preferred quotation mark used in attributes
};

function generateCode(ast, options) {
  return (new Compiler(ast, options)).compile();
}

function Compiler(node, options) {
  this.options = options = objectAssign({}, module.exports.defaultOptions, options);
  this.node = cleanUpAST(node);
  this.indents = -1; // -1 to counter the root-level Block
  // If the current node is a nested node (through the block expansion syntax
  // (`p: strong`) or with nested filters `:uglify-js:babel`). This property
  // makes the visit* methods continue on the previous line instead of
  // starting a new line.
  this.nested = false;
  // This property directs the code generator to continue on the previous line
  // and use inline interpolation tokens (`#{}` and `#[]`) if possible.
  this.inline = false;
}

Compiler.prototype = {
  compile: function() {
    this.buf = [];
    this.visit(this.node);
    return this.buf.join('\n');
  },

  indent: function(add) {
    return repeat(this.options.indentChar, this.indents + (add !== undefined ? add : 0));
  },

  buffer: function(content) {
    if (content !== undefined) this.buf[this.buf.length - 1] += content;
  },

  bufLine: function(content, indents) {
    if (content === undefined) content = '';
    this.buf.push(this.indent(indents) + content);
  },

  quote: function(name) {
    var attrOut = '';
    if (this.options.preferredQuote === "'") {
      if (name.indexOf("'") !== -1 && name.indexOf('"') === -1) {
        attrOut += '"';
        attrOut += name;
        attrOut += '"';
      } else {
        attrOut += "'";
        attrOut += name.replace(/'/g, "\\'");
        attrOut += "'";
      }
    } else {
      if (name.indexOf('"') !== -1 && name.indexOf("'") === -1) {
        attrOut += "'";
        attrOut += name;
        attrOut += "'";
      } else {
        attrOut += '"';
        attrOut += name.replace(/"/g, '\\"');
        attrOut += '"';
      }
    }
    return attrOut;
  },

  attrs: function(attrs) {
    var regularAttrs = [];
    var classes = '';
    var id;
    attrs.forEach(function(attr) {
      var constVal = '';
      try {
        constVal = constantinople.toConstant(attr.val);
      } catch (ex) {}

      if (attr.name === 'class' && !attr.escaped && constVal &&
                 /^\-?[_a-z][_a-z0-9\-]*$/i.test(constVal)) {
        classes += '.' + constVal;
      } else if (attr.name === 'id' && !id && !attr.escaped && constVal &&
                 /^[\w-]+$/.test(constVal)) {
        id = constVal;
      } else {
        var attrOut = '';

        // name
        if (/^\w[^()[\]=!,`'"\s]*$/.test(attr.name)) {
          attrOut += attr.name;
        } else {
          var name = attr.name.replace(/\\/g, '\\\\');
          attrOut += this.quote(name);
        }

        if (!(typeof constVal === 'boolean' && constVal === true)) {
          // operator
          if (!attr.escaped) attrOut += '!';
          attrOut += '=';

          // value
          attrOut += attr.val;
        }

        regularAttrs.push(attrOut);
      }
    }.bind(this));

    var out = '';
    if (id) out += '#' + id;
    out += classes;
    if (regularAttrs.length) out += '(' + regularAttrs.join(' ') + ')';

    return out;
  },

  attributeBlocks: function(attributeBlocks) {
    return attributeBlocks.reduce(function (prev, cur) {
      return prev + '&attributes(' + cur + ')';
    }, '');
  },

  useColon: function(block, parent) {
    if (!this.options.useColon) return false;

    var parentOk = parent && ({
      Mixin: parent && parent.call,
      Include: true,
      Tag: true,
      When: true
    })[parent.type];
    var node = block.nodes[0];
    var blockOk = block.nodes.length === 1 && ({
      Code: true,
      Filter: true,
      Mixin: node.call,
      Tag: true,
      Text: true
    })[node.type];
    return parentOk && parent.block === block && blockOk;
  },

  // heuristics to determine if dot syntax is preferred over piped text
  useDot: function(block, parent) {
    if (!block.nodes.length) return false;

    // line count
    var lines = block.nodes[block.nodes.length - 1].line - block.nodes[0].line + 1;
    if (lines === 1) return false;
{'fraction_non_alphanumeric': 0.17783111499018112, 'fraction_numerical': 0.003491162993672267, 'mean_word_length': 2.3395264116575594, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '28303', 'n_tokens_mistral': 2915, 'n_tokens_neox': 2831, 'n_words': 707}