Repository: facebook/react
File: vendor/fbtransform/transforms/xjs.js (JavaScript)

Code before:
/**
 * Copyright 2013 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*global exports:true*/
"use strict";
var Syntax = require('esprima-fb').Syntax;
var utils = require('jstransform/src/utils');

var knownTags = {
  a: true,
  abbr: true,
  address: true,
  applet: true,
  area: true,
  article: true,
  aside: true,
  audio: true,
  b: true,
  base: true,
  bdi: true,
  bdo: true,
  big: true,
  blockquote: true,
  body: true,
  br: true,
  button: true,
  canvas: true,
  caption: true,
  circle: true,
  cite: true,
  code: true,
  col: true,
  colgroup: true,
  command: true,
  data: true,
  datalist: true,
  dd: true,
  defs: true,
  del: true,
  details: true,
  dfn: true,
  dialog: true,
  div: true,
  dl: true,
  dt: true,
  ellipse: true,
  em: true,
  embed: true,
  fieldset: true,
  figcaption: true,
  figure: true,
  footer: true,
  form: true,
  g: true,
  h1: true,
  h2: true,
  h3: true,
  h4: true,
  h5: true,
  h6: true,
  head: true,
  header: true,
  hgroup: true,
  hr: true,
  html: true,
  i: true,
  iframe: true,
  img: true,
  input: true,
  ins: true,
  kbd: true,
  keygen: true,
  label: true,
  legend: true,
  li: true,
  line: true,
  linearGradient: true,
  link: true,
  main: true,
  map: true,
  mark: true,
  marquee: true,
  menu: true,
  menuitem: true,
  meta: true,
  meter: true,
  nav: true,
  noscript: true,
  object: true,
  ol: true,
  optgroup: true,
  option: true,
  output: true,
  p: true,
  param: true,
  path: true,
  polyline: true,
  pre: true,
  progress: true,
  q: true,
  radialGradient: true,
  rect: true,
  rp: true,
  rt: true,
  ruby: true,
  s: true,
  samp: true,
  script: true,
  section: true,
  select: true,
  small: true,
  source: true,
  span: true,
  stop: true,
  strong: true,
  style: true,
  sub: true,
  summary: true,
  sup: true,
  svg: true,
  table: true,
  tbody: true,
  td: true,
  text: true,
  textarea: true,
  tfoot: true,
  th: true,
  thead: true,
  time: true,
  title: true,
  tr: true,
  track: true,
  u: true,
  ul: true,
  'var': true,
  video: true,
  wbr: true
};

function safeTrim(string) {
  return string.replace(/^[ \t]+/, '').replace(/[ \t]+$/, '');
}

// Replace all trailing whitespace characters with a single space character
function trimWithSingleSpace(string) {
  return string.replace(/^[ \t\xA0]{2,}/, ' ').
    replace(/[ \t\xA0]{2,}$/, ' ').replace(/^\s+$/, '');
}

/**
 * Special handling for multiline string literals
 * print lines:
 *
 *   line
 *   line
 *
 * as:
 *
 *   "line "+
 *   "line"
 */
function renderXJSLiteral(object, isLast, state, start, end) {
  /** Added blank check filtering and triming*/
  var trimmedChildValue = safeTrim(object.value);
  var hasFinalNewLine = false;

  if (trimmedChildValue) {
    // head whitespace
    utils.append(object.value.match(/^[\t ]*/)[0], state);
    if (start) {
      utils.append(start, state);
    }

    var trimmedChildValueWithSpace = trimWithSingleSpace(object.value);

    /**
     */
    var initialLines = trimmedChildValue.split(/\r\n|\n|\r/);

    var lines = initialLines.filter(function(line) {
      return safeTrim(line).length > 0;
    });

    var hasInitialNewLine = initialLines[0] !== lines[0];
    hasFinalNewLine =
      initialLines[initialLines.length - 1] !== lines[lines.length - 1];

    var numLines = lines.length;
    lines.forEach(function (line, ii) {
      var lastLine = ii === numLines - 1;
      var trimmedLine = safeTrim(line);
      if (trimmedLine === '' && !lastLine) {
        utils.append(line, state);
      } else {
        var preString = '';
        var postString = '';
        var leading = line.match(/^[ \t]*/)[0];

        if (ii === 0) {
          if (hasInitialNewLine) {
            preString = ' ';
            leading = '\n' + leading;
          }
          if (trimmedChildValueWithSpace.substring(0, 1) === ' ') {
            // If this is the first line, and the original content starts with
            // whitespace, place a single space at the beginning.
            preString = ' ';
          }
        }
        if (!lastLine || trimmedChildValueWithSpace.substr(
             trimmedChildValueWithSpace.length - 1, 1) === ' ' ||
             hasFinalNewLine
             ) {
          // If either not on the last line, or the original content ends with
          // whitespace, place a single character at the end.
          postString = ' ';
        }

        utils.append(
          leading +
          JSON.stringify(
            preString + trimmedLine + postString
          ) +
          (lastLine ? '' : '+') +
          line.match(/[ \t]*$/)[0],
          state);
      }
      if (!lastLine) {
        utils.append('\n', state);
      }
    });
  } else {
    if (start) {
      utils.append(start, state);
    }
    utils.append('""', state);
  }
  if (end) {
    utils.append(end, state);
  }

  // add comma before trailing whitespace
  if (!isLast) {
    utils.append(',', state);
  }

  // tail whitespace
  if (hasFinalNewLine) {
    utils.append('\n', state);
  }
  utils.append(object.value.match(/[ \t]*$/)[0], state);
  utils.move(object.range[1], state);
}

function renderXJSExpressionContainer(traverse, object, isLast, path, state) {
  // Plus 1 to skip `{`.
  utils.move(object.range[0] + 1, state);
  traverse(object.expression, path, state);
  if (!isLast && object.expression.type !== Syntax.XJSEmptyExpression) {
    // If we need to append a comma, make sure to do so after the expression.
    utils.catchup(object.expression.range[1], state);
    utils.append(',', state);
  }

  // Minus 1 to skip `}`.
  utils.catchup(object.range[1] - 1, state);
  utils.move(object.range[1], state);
  return false;
}

function quoteAttrName(attr) {
  // Quote invalid JS identifiers.
  if (!/^[a-z_$][a-z\d_$]*$/i.test(attr)) {
    return "'" + attr + "'";
  }
  return attr;
}

exports.knownTags = knownTags;
exports.renderXJSExpressionContainer = renderXJSExpressionContainer;
exports.renderXJSLiteral = renderXJSLiteral;
exports.quoteAttrName = quoteAttrName;


Review comment (bug):
I'm finding it a little difficult to follow what's going on in here, but I think this is the source of failure when transforming something like:

```
/**
 * @jsx React.DOM
 */
<img alt="" src="" />
```

which, after these changes, now transforms to

```
/**
 * @jsx React.DOM
 */
React.DOM.img( {alt:"" src:""} )
```

In that scenario, `line` here is `""` -- and thus falsey.

On a side note (for future ref): I believe our style guide asks that you always use curly braces and to put conditional bodies on a new line


Code after:
/**
 * Copyright 2013 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*global exports:true*/
"use strict";
var Syntax = require('esprima-fb').Syntax;
var utils = require('jstransform/src/utils');

var knownTags = {
  a: true,
  abbr: true,
  address: true,
  applet: true,
  area: true,
  article: true,
  aside: true,
  audio: true,
  b: true,
  base: true,
  bdi: true,
  bdo: true,
  big: true,
  blockquote: true,
  body: true,
  br: true,
  button: true,
  canvas: true,
  caption: true,
  circle: true,
  cite: true,
  code: true,
  col: true,
  colgroup: true,
  command: true,
  data: true,
  datalist: true,
  dd: true,
  defs: true,
  del: true,
  details: true,
  dfn: true,
  dialog: true,
  div: true,
  dl: true,
  dt: true,
  ellipse: true,
  em: true,
  embed: true,
  fieldset: true,
  figcaption: true,
  figure: true,
  footer: true,
  form: true,
  g: true,
  h1: true,
  h2: true,
  h3: true,
  h4: true,
  h5: true,
  h6: true,
  head: true,
  header: true,
  hgroup: true,
  hr: true,
  html: true,
  i: true,
  iframe: true,
  img: true,
  input: true,
  ins: true,
  kbd: true,
  keygen: true,
  label: true,
  legend: true,
  li: true,
  line: true,
  linearGradient: true,
  link: true,
  main: true,
  map: true,
  mark: true,
  marquee: true,
  menu: true,
  menuitem: true,
  meta: true,
  meter: true,
  nav: true,
  noscript: true,
  object: true,
  ol: true,
  optgroup: true,
  option: true,
  output: true,
  p: true,
  param: true,
  path: true,
  polyline: true,
  pre: true,
  progress: true,
  q: true,
  radialGradient: true,
  rect: true,
  rp: true,
  rt: true,
  ruby: true,
  s: true,
  samp: true,
  script: true,
  section: true,
  select: true,
  small: true,
  source: true,
  span: true,
  stop: true,
  strong: true,
  style: true,
  sub: true,
  summary: true,
  sup: true,
  svg: true,
  table: true,
  tbody: true,
  td: true,
  text: true,
  textarea: true,
  tfoot: true,
  th: true,
  thead: true,
  time: true,
  title: true,
  tr: true,
  track: true,
  u: true,
  ul: true,
  'var': true,
  video: true,
  wbr: true
};

function renderXJSLiteral(object, isLast, state, start, end) {
  var lines = object.value.split(/\r\n|\n|\r/);
  
  if (start) {
    utils.append(start, state);
  }
  
  var lastNonEmptyLine = 0;
  
  lines.forEach(function (line, index) {
    if (line.match(/[^ \t]/)) {
      lastNonEmptyLine = index;
    }
  });
  
  lines.forEach(function (line, index) {
    var isFirstLine = index === 0;
    var isLastLine = index === lines.length - 1;
    var isLastNonEmptyLine = index === lastNonEmptyLine;
    
    // replace rendered whitespace tabs with spaces
    var trimmedLine = line.replace(/\t/g, ' ');
    
    // trim whitespace touching a newline
    if (!isFirstLine) {
      trimmedLine = trimmedLine.replace(/^[ ]+/, '');
    }
    if (!isLastLine) {
      trimmedLine = trimmedLine.replace(/[ ]+$/, '');
    }
    
    utils.append(line.match(/^[ \t]*/)[0], state);
    
    if (trimmedLine || isLastNonEmptyLine) {
      utils.append(
        JSON.stringify(trimmedLine) +
        (!isLastNonEmptyLine ? "+' '+" : ''),
        state);
      
      if (isLastNonEmptyLine) {
        if (end) {
          utils.append(end, state);
        }
        if (!isLast) {
          utils.append(',', state);
        }
      }
      
      // only restore tail whitespace if line had literals
      if (trimmedLine) {
        utils.append(line.match(/[ \t]*$/)[0], state);
      }
    }
    
    if (!isLastLine) {
      utils.append('\n', state);
    }
  });
  
  utils.move(object.range[1], state);
}

function renderXJSExpressionContainer(traverse, object, isLast, path, state) {
  // Plus 1 to skip `{`.
  utils.move(object.range[0] + 1, state);
  traverse(object.expression, path, state);
  if (!isLast && object.expression.type !== Syntax.XJSEmptyExpression) {
    // If we need to append a comma, make sure to do so after the expression.
    utils.catchup(object.expression.range[1], state);
    utils.append(',', state);
  }

  // Minus 1 to skip `}`.
  utils.catchup(object.range[1] - 1, state);
  utils.move(object.range[1], state);
  return false;
}

function quoteAttrName(attr) {
  // Quote invalid JS identifiers.
  if (!/^[a-z_$][a-z\d_$]*$/i.test(attr)) {
    return "'" + attr + "'";
  }
  return attr;
}

exports.knownTags = knownTags;
exports.renderXJSExpressionContainer = renderXJSExpressionContainer;
exports.renderXJSLiteral = renderXJSLiteral;
exports.quoteAttrName = quoteAttrName;