<commit_before>export class SourceToken {
  type: SourceType;
  start: number;
  end: number;

  constructor(type: SourceType, start: number, end: number) {
    this.type = type;
    this.start = start;
    this.end = end;
  }
}

/**
 * Represents a list of tokens and provides various utility functions for
 * finding tokens within it.
 */
export class SourceTokenList {
  _tokens: Array<SourceToken>;
  _indexCache: Array<SourceTokenListIndex>;
  length: number;
  startIndex: SourceTokenListIndex;
  endIndex: SourceTokenListIndex;

  constructor(tokens: Array<SourceToken>) {
    this._tokens = tokens;
    this._indexCache = new Array(tokens.length);
    this.length = tokens.length;
    this.startIndex = this._getIndex(0);
    this.endIndex = this._getIndex(tokens.length);
  }

  /**
   * Iterate over each token.
   */
  forEach(iterator: (token: SourceToken, index: SourceTokenListIndex, list: SourceTokenList) => void) {
    this._tokens.forEach((token, i) => iterator(token, this._getIndex(i), this));
  }

  /**
   * Map each token to an element of an array.
   */
  map<T>(mapper: (token: SourceToken, index: SourceTokenListIndex, list: SourceTokenList) => T): Array<T> {
    let result = [];
    this.forEach((token, index, list) => result.push(mapper(token, index, list)));
    return result;
  }

  /**
   * Get a slice of this token list using the given indexes.
   */
  slice(start: SourceTokenListIndex, end: SourceTokenListIndex): SourceTokenList {
    if (start._sourceTokenList !== this || end._sourceTokenList !== this) {
      throw new Error('cannot slice a list using indexes from another list');
    }
    return new SourceTokenList(this._tokens.slice(start._index, end._index));
  }

  /**
   * Get the token at the given index, if it exists.
   *
   * NOTE: The only value for which this should return `null` is this list's
   * `endIndex`.
   */
  tokenAtIndex(index: SourceTokenListIndex): ?SourceToken {
    if (index._sourceTokenList !== this) {
      throw new Error('cannot get token in one list using an index from another');
    }
    return this._tokens[index._index] || null;
  }

  /**
   * Finds the index of the token whose source range includes the given index.
   */
  indexOfTokenContainingSourceIndex(index: number): ?SourceTokenListIndex {
    if (typeof index !== 'number') {
      throw new Error(`expected source index to be a number, got: ${index}`);
    }
    return this.indexOfTokenMatchingPredicate(({ start, end }) => start <= index && index < end);
  }

  /**
   * Finds the index of the token whose source range starts at the given index.
   */
  indexOfTokenStartingAtSourceIndex(index: number): ?SourceTokenListIndex {
    if (typeof index !== 'number') {
      throw new Error(`expected source index to be a number, got: ${index}`);
    }
    return this.indexOfTokenMatchingPredicate(({ start }) => start === index);
  }

  /**
   * Finds the index of the token whose source range ends at the given index.
   */
  indexOfTokenEndingAtSourceIndex(index: number): ?SourceTokenListIndex {
    if (typeof index !== 'number') {
      throw new Error(`expected source index to be a number, got: ${index}`);
    }
    return this.indexOfTokenMatchingPredicate(({ end }) => end === index);
  }

  /**
   * Finds the index of the first token matching a predicate.
   */
  indexOfTokenMatchingPredicate(predicate: (token: SourceToken) => boolean): ?SourceTokenListIndex {
    for (let i = 0; i < this._tokens.length; i++) {
      if (predicate(this._tokens[i])) {
        return this._getIndex(i);
      }
    }
    return null;
  }

  /**
   * @internal
   */
  _getIndex(index: number): SourceTokenListIndex {
    let cached = this._indexCache[index];

    if (!cached) {
      cached = new SourceTokenListIndex(this, index);
      this._indexCache[index] = cached;
    }

    return cached;
  }

  /**
   * Get the list of tokens.
   */
  toArray(): Array<SourceToken> {
    return this._tokens.slice();
  }
}

/**
 * Represents a token at a particular index within a list of tokens.
 */
class SourceTokenListIndex {
  _sourceTokenList: SourceTokenList;
  _index: number;

  constructor(sourceTokenList: SourceTokenList, index: number) {
    this._sourceTokenList = sourceTokenList;
    this._index = index;
  }

  /**
   * Get a new index offset from this one, if the resulting offset is within
   * the list range.
   */
  advance(offset: number): ?SourceTokenListIndex {
    let newIndex = this._index + offset;
    if (newIndex < 0 || this._sourceTokenList.length < newIndex) {
      return null;
    }
    return this._sourceTokenList._getIndex(newIndex);
  }

  /**
   * Get the index of the token after this one, if it's not the last one.
   */
  next(): ?SourceTokenListIndex {
    return this.advance(1);
  }

  /**
   * Get the index of the token before this one, if it's not the first one.
   */
  previous(): ?SourceTokenListIndex {
    return this.advance(-1);
  }
}

/**
 * Generate a list of tokens from CoffeeScript source code.
 */
export default function lex(source: string): SourceTokenList {
  let location;
  let previousLocation;
  let tokens = [];
  let getNextLocation = stream(source);
  do {
    location = getNextLocation();
    if (previousLocation && previousLocation.type !== SPACE) {
      tokens.push(
        new SourceToken(
          previousLocation.type,
          previousLocation.index,
          location.index
        )
      );
    }
    previousLocation = location;
  } while (location.type !== EOF);
  return new SourceTokenList(tokens);
}

const REGEXP_FLAGS = ['i', 'g', 'm', 'y'];

/**
 * Represents a change in source code type at a particular index.
 */
export class SourceLocation {
  type: SourceType;
  index: number;

  constructor(type: SourceType, index: number) {
    this.type = type;
    this.index = index;
  }
}

/**
 * Represents a particular type of CoffeeScript code.
 */
export class SourceType {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

export const AT = new SourceType('AT');
export const COMMENT = new SourceType('COMMENT');
export const DSTRING = new SourceType('DSTRING');
export const EOF = new SourceType('EOF');
export const HERECOMMENT = new SourceType('HERECOMMENT');
export const HEREGEXP = new SourceType('HEREGEXP');
export const INTERPOLATION_START = new SourceType('INTERPOLATION_START');
export const INTERPOLATION_END = new SourceType('INTERPOLATION_END');
export const JS = new SourceType('JS');
export const LBRACE = new SourceType('LBRACE');
export const LBRACKET = new SourceType('LBRACKET');
export const LPAREN = new SourceType('LPAREN');
export const NEWLINE = new SourceType('NEWLINE');
export const NORMAL = new SourceType('NORMAL');
export const NUMBER = new SourceType('NUMBER');
export const OPERATOR = new SourceType('OPERATOR');
export const REGEXP = new SourceType('REGEXP');
export const RBRACE = new SourceType('RBRACE');
export const RBRACKET = new SourceType('RBRACKET');
export const RPAREN = new SourceType('RPAREN');
export const SEMICOLON = new SourceType('SEMICOLON');
export const SPACE = new SourceType('SPACE');
export const SSTRING = new SourceType('SSTRING');
export const TDSTRING = new SourceType('TDSTRING');
export const TSSTRING = new SourceType('TSSTRING');
export const UNKNOWN = new SourceType('UNKNOWN');
export const WORD = new SourceType('WORD');

/**
 * Borrowed, with tweaks, from CoffeeScript's lexer.coffee.
 */
const IDENTIFIER_PATTERN = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+)/;
const NUMBER_PATTERN = /^0b[01]+|^0o[0-7]+|^0x[\da-f]+|^\d*\.?\d+(?:e[+-]?\d+)?/i;
const OPERATOR_PATTERN = /^[-=|+*\/\\%<>&^!?\.,:]+/;
const SPACE_PATTERN = /^[^\n\r\S]+/;

/**
 * Provides a stream of source type change locations.
 */
export function stream(source: string, index: number=0): () => SourceLocation {
  let location = new SourceLocation(NORMAL, index);
  let interpolationStack = ([]: Array<SourceType>);
  let braceStack = [];
  let start = index;
  return function step(): SourceLocation {
    start = index;
    let lastLocation = location;

    if (index >= source.length) {
      setType(EOF);
    }

    switch (location.type) {
      case NORMAL:
      case SPACE:
      case WORD:
      case NUMBER:
      case OPERATOR:
      case LPAREN:
      case RPAREN:
      case LBRACE:
      case RBRACE:
      case LBRACKET:
      case RBRACKET:
      case NEWLINE:
      case AT:
      case SEMICOLON:
      case INTERPOLATION_START:
        if (consume(SPACE_PATTERN)) {
          setType(SPACE);
        } else if (consumeNewline()) {
          setType(NEWLINE);
        } else if (consume('"""')) {
          setType(TDSTRING);
        } else if (consume('"')) {
          setType(DSTRING);
        } else if (consume('\'\'\'')) {
          setType(TSSTRING);
        } else if (consume('\'')) {
          setType(SSTRING);
        } else if (consume('###')) {
          setType(HERECOMMENT);
        } else if (consume('#')) {
          setType(COMMENT);
        } else if (consume('///')) {
          setType(HEREGEXP);
        } else if (consume('(')) {
          setType(LPAREN);
        } else if (consume(')')) {
          setType(RPAREN);
        } else if (consume('[')) {
          setType(LBRACKET);
        } else if (consume(']')) {
          setType(RBRACKET);
        } else if (consume('{')) {
          braceStack.push(start);
          setType(LBRACE);
        } else if (consume('}')) {
          if (braceStack.length === 0) {
            popInterpolation();
          } else {
            braceStack.pop();
            setType(RBRACE);
          }
        } else if (!match(/^\/=?\s/) && consume('/')) {
          setType(REGEXP);
        } else if (consume('@')) {
          setType(AT);
        } else if (consume(';')) {
          setType(SEMICOLON);
        } else if (consume('`')) {
          setType(JS);
        } else if (consume(IDENTIFIER_PATTERN)) {
          setType(WORD);
        } else if (consume(NUMBER_PATTERN)) {
          setType(NUMBER);
        } else if (consume(OPERATOR_PATTERN)) {
          setType(OPERATOR);
        } else {
          setType(UNKNOWN);
        }
        break;

      case SSTRING:
        if (consume('\\')) {
          index++;
        } else if (consume('\'')) {
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case DSTRING:
        if (consume('\\')) {
          index++;
        } else if (consume('"')) {
          setType(NORMAL);
        } else if (consume('#{')) {
          pushInterpolation();
        } else {
          index++;
        }
        break;

      case COMMENT:
        if (consumeNewline()) {
          setType(NEWLINE);
        } else {
          index++;
        }
        break;

      case HERECOMMENT:
        if (consume('###')) {
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case TSSTRING:
        if (consume('\\')) {
          index++;
        } else if (consume('\'\'\'')) {
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case TDSTRING:
        if (consume('\\')) {
          index++;
        } else if (consume('"""')) {
          setType(NORMAL);
        } else if (consume('#{')) {
          pushInterpolation();
        } else {
          index++;
        }
        break;

      case INTERPOLATION_END:
        setType(interpolationStack.pop());
        break;

      case REGEXP:
        if (consume('\\')) {
          index++;
        } else if (consume('/')) {
          while (consumeAny(REGEXP_FLAGS));
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case HEREGEXP:
        if (consume('\\')) {
          index++;
        } else if (consume('///')) {
          while (consumeAny(REGEXP_FLAGS));
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case JS:
        if (consume('\\')) {
          index++;
        } else if (consume('`')) {
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case EOF:
        if (braceStack.length !== 0) {
          throw new Error(
            `unexpected EOF while looking for '}' to match '{' ` +
            `at ${braceStack[braceStack.length - 1]}`
          );
        }
        break;

      case UNKNOWN:
        // ¯\_(ツ)_/¯
        index++;
        break;

      default:
        throw new Error(`unknown source type at offset ${location.index}: ${location.type.name}`);
    }

    let shouldSkipToNextStep = (
      // Don't report on going back to "normal" source code.
      location.type === NORMAL ||
      // Don't report if nothing has changed, unless we're at the end.
      (
        location === lastLocation &&
        location.type !== EOF
      )
    );

    if (shouldSkipToNextStep) {
      return step();
    }

    return location;
  };

  function consumeAny(strings: Array<string>): boolean {
    return strings.some(string => consume(string));
  }

  function consume(value: string|RegExp): boolean {
    let matchedLength = match(value);
    if (matchedLength > 0) {
      index += matchedLength;
      return true;
    } else {
      return false;
    }
  }

  function consumeNewline(): boolean {
    return consumeAny(['\n', '\r\n', '\r']);
  }

  function setType(newType: SourceType) {
    location = new SourceLocation(newType, start);
  }

  function match(value: string|RegExp): number {
    if (typeof value === 'string') {
      let matches = source.slice(index, index + value.length) === value;
      return matches ? value.length : 0;
    } else {
      let match = source.slice(index).match(value);
      return match ? match[0].length : 0;
    }
  }

  function pushInterpolation() {
    interpolationStack.push(location.type);
    setType(INTERPOLATION_START);
  }

  function popInterpolation() {
    if (interpolationStack.length === 0) {
      throw new Error(`unexpected '}' found in string at ${index}: ${JSON.stringify(source)}`);
    }
    setType(INTERPOLATION_END);
  }
}

export function consumeStream(lexer: () => SourceLocation): Array<SourceLocation> {
  let result = [];
  let location;
  do {
    location = lexer();
    result.push(location);
  } while (location.type !== EOF);
  return result;
}
<commit_msg>Fix flow check.<commit_after>export class SourceToken {
  type: SourceType;
  start: number;
  end: number;

  constructor(type: SourceType, start: number, end: number) {
    this.type = type;
    this.start = start;
    this.end = end;
  }
}

/**
 * Represents a list of tokens and provides various utility functions for
 * finding tokens within it.
 */
export class SourceTokenList {
  _tokens: Array<SourceToken>;
  _indexCache: Array<SourceTokenListIndex>;
  length: number;
  startIndex: SourceTokenListIndex;
  endIndex: SourceTokenListIndex;

  constructor(tokens: Array<SourceToken>) {
    this._tokens = tokens;
    this._indexCache = new Array(tokens.length);
    this.length = tokens.length;
    this.startIndex = this._getIndex(0);
    this.endIndex = this._getIndex(tokens.length);
  }

  /**
   * Iterate over each token.
   */
  forEach(iterator: (token: SourceToken, index: SourceTokenListIndex, list: SourceTokenList) => void) {
    this._tokens.forEach((token, i) => iterator(token, this._getIndex(i), this));
  }

  /**
   * Map each token to an element of an array.
   */
  map<T>(mapper: (token: SourceToken, index: SourceTokenListIndex, list: SourceTokenList) => T): Array<T> {
    let result = [];
    this.forEach((token, index, list) => { result.push(mapper(token, index, list)); });
    return result;
  }

  /**
   * Get a slice of this token list using the given indexes.
   */
  slice(start: SourceTokenListIndex, end: SourceTokenListIndex): SourceTokenList {
    if (start._sourceTokenList !== this || end._sourceTokenList !== this) {
      throw new Error('cannot slice a list using indexes from another list');
    }
    return new SourceTokenList(this._tokens.slice(start._index, end._index));
  }

  /**
   * Get the token at the given index, if it exists.
   *
   * NOTE: The only value for which this should return `null` is this list's
   * `endIndex`.
   */
  tokenAtIndex(index: SourceTokenListIndex): ?SourceToken {
    if (index._sourceTokenList !== this) {
      throw new Error('cannot get token in one list using an index from another');
    }
    return this._tokens[index._index] || null;
  }

  /**
   * Finds the index of the token whose source range includes the given index.
   */
  indexOfTokenContainingSourceIndex(index: number): ?SourceTokenListIndex {
    if (typeof index !== 'number') {
      throw new Error(`expected source index to be a number, got: ${index}`);
    }
    return this.indexOfTokenMatchingPredicate(({ start, end }) => start <= index && index < end);
  }

  /**
   * Finds the index of the token whose source range starts at the given index.
   */
  indexOfTokenStartingAtSourceIndex(index: number): ?SourceTokenListIndex {
    if (typeof index !== 'number') {
      throw new Error(`expected source index to be a number, got: ${index}`);
    }
    return this.indexOfTokenMatchingPredicate(({ start }) => start === index);
  }

  /**
   * Finds the index of the token whose source range ends at the given index.
   */
  indexOfTokenEndingAtSourceIndex(index: number): ?SourceTokenListIndex {
    if (typeof index !== 'number') {
      throw new Error(`expected source index to be a number, got: ${index}`);
    }
    return this.indexOfTokenMatchingPredicate(({ end }) => end === index);
  }

  /**
   * Finds the index of the first token matching a predicate.
   */
  indexOfTokenMatchingPredicate(predicate: (token: SourceToken) => boolean): ?SourceTokenListIndex {
    for (let i = 0; i < this._tokens.length; i++) {
      if (predicate(this._tokens[i])) {
        return this._getIndex(i);
      }
    }
    return null;
  }

  /**
   * @internal
   */
  _getIndex(index: number): SourceTokenListIndex {
    let cached = this._indexCache[index];

    if (!cached) {
      cached = new SourceTokenListIndex(this, index);
      this._indexCache[index] = cached;
    }

    return cached;
  }

  /**
   * Get the list of tokens.
   */
  toArray(): Array<SourceToken> {
    return this._tokens.slice();
  }
}

/**
 * Represents a token at a particular index within a list of tokens.
 */
class SourceTokenListIndex {
  _sourceTokenList: SourceTokenList;
  _index: number;

  constructor(sourceTokenList: SourceTokenList, index: number) {
    this._sourceTokenList = sourceTokenList;
    this._index = index;
  }

  /**
   * Get a new index offset from this one, if the resulting offset is within
   * the list range.
   */
  advance(offset: number): ?SourceTokenListIndex {
    let newIndex = this._index + offset;
    if (newIndex < 0 || this._sourceTokenList.length < newIndex) {
      return null;
    }
    return this._sourceTokenList._getIndex(newIndex);
  }

  /**
   * Get the index of the token after this one, if it's not the last one.
   */
  next(): ?SourceTokenListIndex {
    return this.advance(1);
  }

  /**
   * Get the index of the token before this one, if it's not the first one.
   */
  previous(): ?SourceTokenListIndex {
    return this.advance(-1);
  }
}

/**
 * Generate a list of tokens from CoffeeScript source code.
 */
export default function lex(source: string): SourceTokenList {
  let location;
  let previousLocation;
  let tokens = [];
  let getNextLocation = stream(source);
  do {
    location = getNextLocation();
    if (previousLocation && previousLocation.type !== SPACE) {
      tokens.push(
        new SourceToken(
          previousLocation.type,
          previousLocation.index,
          location.index
        )
      );
    }
    previousLocation = location;
  } while (location.type !== EOF);
  return new SourceTokenList(tokens);
}

const REGEXP_FLAGS = ['i', 'g', 'm', 'y'];

/**
 * Represents a change in source code type at a particular index.
 */
export class SourceLocation {
  type: SourceType;
  index: number;

  constructor(type: SourceType, index: number) {
    this.type = type;
    this.index = index;
  }
}

/**
 * Represents a particular type of CoffeeScript code.
 */
export class SourceType {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

export const AT = new SourceType('AT');
export const COMMENT = new SourceType('COMMENT');
export const DSTRING = new SourceType('DSTRING');
export const EOF = new SourceType('EOF');
export const HERECOMMENT = new SourceType('HERECOMMENT');
export const HEREGEXP = new SourceType('HEREGEXP');
export const INTERPOLATION_START = new SourceType('INTERPOLATION_START');
export const INTERPOLATION_END = new SourceType('INTERPOLATION_END');
export const JS = new SourceType('JS');
export const LBRACE = new SourceType('LBRACE');
export const LBRACKET = new SourceType('LBRACKET');
export const LPAREN = new SourceType('LPAREN');
export const NEWLINE = new SourceType('NEWLINE');
export const NORMAL = new SourceType('NORMAL');
export const NUMBER = new SourceType('NUMBER');
export const OPERATOR = new SourceType('OPERATOR');
export const REGEXP = new SourceType('REGEXP');
export const RBRACE = new SourceType('RBRACE');
export const RBRACKET = new SourceType('RBRACKET');
export const RPAREN = new SourceType('RPAREN');
export const SEMICOLON = new SourceType('SEMICOLON');
export const SPACE = new SourceType('SPACE');
export const SSTRING = new SourceType('SSTRING');
export const TDSTRING = new SourceType('TDSTRING');
export const TSSTRING = new SourceType('TSSTRING');
export const UNKNOWN = new SourceType('UNKNOWN');
export const WORD = new SourceType('WORD');

/**
 * Borrowed, with tweaks, from CoffeeScript's lexer.coffee.
 */
const IDENTIFIER_PATTERN = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+)/;
const NUMBER_PATTERN = /^0b[01]+|^0o[0-7]+|^0x[\da-f]+|^\d*\.?\d+(?:e[+-]?\d+)?/i;
const OPERATOR_PATTERN = /^[-=|+*\/\\%<>&^!?\.,:]+/;
const SPACE_PATTERN = /^[^\n\r\S]+/;

/**
 * Provides a stream of source type change locations.
 */
export function stream(source: string, index: number=0): () => SourceLocation {
  let location = new SourceLocation(NORMAL, index);
  let interpolationStack = ([]: Array<SourceType>);
  let braceStack = [];
  let start = index;
  return function step(): SourceLocation {
    start = index;
    let lastLocation = location;

    if (index >= source.length) {
      setType(EOF);
    }

    switch (location.type) {
      case NORMAL:
      case SPACE:
      case WORD:
      case NUMBER:
      case OPERATOR:
      case LPAREN:
      case RPAREN:
      case LBRACE:
      case RBRACE:
      case LBRACKET:
      case RBRACKET:
      case NEWLINE:
      case AT:
      case SEMICOLON:
      case INTERPOLATION_START:
        if (consume(SPACE_PATTERN)) {
          setType(SPACE);
        } else if (consumeNewline()) {
          setType(NEWLINE);
        } else if (consume('"""')) {
          setType(TDSTRING);
        } else if (consume('"')) {
          setType(DSTRING);
        } else if (consume('\'\'\'')) {
          setType(TSSTRING);
        } else if (consume('\'')) {
          setType(SSTRING);
        } else if (consume('###')) {
          setType(HERECOMMENT);
        } else if (consume('#')) {
          setType(COMMENT);
        } else if (consume('///')) {
          setType(HEREGEXP);
        } else if (consume('(')) {
          setType(LPAREN);
        } else if (consume(')')) {
          setType(RPAREN);
        } else if (consume('[')) {
          setType(LBRACKET);
        } else if (consume(']')) {
          setType(RBRACKET);
        } else if (consume('{')) {
          braceStack.push(start);
          setType(LBRACE);
        } else if (consume('}')) {
          if (braceStack.length === 0) {
            popInterpolation();
          } else {
            braceStack.pop();
            setType(RBRACE);
          }
        } else if (!match(/^\/=?\s/) && consume('/')) {
          setType(REGEXP);
        } else if (consume('@')) {
          setType(AT);
        } else if (consume(';')) {
          setType(SEMICOLON);
        } else if (consume('`')) {
          setType(JS);
        } else if (consume(IDENTIFIER_PATTERN)) {
          setType(WORD);
        } else if (consume(NUMBER_PATTERN)) {
          setType(NUMBER);
        } else if (consume(OPERATOR_PATTERN)) {
          setType(OPERATOR);
        } else {
          setType(UNKNOWN);
        }
        break;

      case SSTRING:
        if (consume('\\')) {
          index++;
        } else if (consume('\'')) {
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case DSTRING:
        if (consume('\\')) {
          index++;
        } else if (consume('"')) {
          setType(NORMAL);
        } else if (consume('#{')) {
          pushInterpolation();
        } else {
          index++;
        }
        break;

      case COMMENT:
        if (consumeNewline()) {
          setType(NEWLINE);
        } else {
          index++;
        }
        break;

      case HERECOMMENT:
        if (consume('###')) {
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case TSSTRING:
        if (consume('\\')) {
          index++;
        } else if (consume('\'\'\'')) {
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case TDSTRING:
        if (consume('\\')) {
          index++;
        } else if (consume('"""')) {
          setType(NORMAL);
        } else if (consume('#{')) {
          pushInterpolation();
        } else {
          index++;
        }
        break;

      case INTERPOLATION_END:
        setType(interpolationStack.pop());
        break;

      case REGEXP:
        if (consume('\\')) {
          index++;
        } else if (consume('/')) {
          while (consumeAny(REGEXP_FLAGS));
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case HEREGEXP:
        if (consume('\\')) {
          index++;
        } else if (consume('///')) {
          while (consumeAny(REGEXP_FLAGS));
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case JS:
        if (consume('\\')) {
          index++;
        } else if (consume('`')) {
          setType(NORMAL);
        } else {
          index++;
        }
        break;

      case EOF:
        if (braceStack.length !== 0) {
          throw new Error(
            `unexpected EOF while looking for '}' to match '{' ` +
            `at ${braceStack[braceStack.length - 1]}`
          );
        }
        break;

      case UNKNOWN:
        // ¯\_(ツ)_/¯
        index++;
        break;

      default:
        throw new Error(`unknown source type at offset ${location.index}: ${location.type.name}`);
    }

    let shouldSkipToNextStep = (
      // Don't report on going back to "normal" source code.
      location.type === NORMAL ||
      // Don't report if nothing has changed, unless we're at the end.
      (
        location === lastLocation &&
        location.type !== EOF
      )
    );

    if (shouldSkipToNextStep) {
      return step();
    }

    return location;
  };

  function consumeAny(strings: Array<string>): boolean {
    return strings.some(string => consume(string));
  }

  function consume(value: string|RegExp): boolean {
    let matchedLength = match(value);
    if (matchedLength > 0) {
      index += matchedLength;
      return true;
    } else {
      return false;
    }
  }

  function consumeNewline(): boolean {
    return consumeAny(['\n', '\r\n', '\r']);
  }

  function setType(newType: SourceType) {
    location = new SourceLocation(newType, start);
  }

  function match(value: string|RegExp): number {
    if (typeof value === 'string') {
      let matches = source.slice(index, index + value.length) === value;
      return matches ? value.length : 0;
    } else {
      let match = source.slice(index).match(value);
      return match ? match[0].length : 0;
    }
  }

  function pushInterpolation() {
    interpolationStack.push(location.type);
    setType(INTERPOLATION_START);
  }

  function popInterpolation() {
    if (interpolationStack.length === 0) {
      throw new Error(`unexpected '}' found in string at ${index}: ${JSON.stringify(source)}`);
    }
    setType(INTERPOLATION_END);
  }
}

export function consumeStream(lexer: () => SourceLocation): Array<SourceLocation> {
  let result = [];
  let location;
  do {
    location = lexer();
    result.push(location);
  } while (location.type !== EOF);
  return result;
}
<|endoftext|>


<commit_before>'use strict';

const Promise = require('pinkie-promise');
const openUrl = require('opn');
const now = require('./parsers/now');
const spinner = require('./spinner');
const Renderer = require('./renderer');
const parseTableData = require('./parsers/table');
const fetchOptions = require('./options/fetch');
const api = require('./api')(fetchOptions);
const noop = require('./noop');

let cache = {};

const limitResults = (results, limit) => results.slice(0, limit);

const fetchTopStories = () => {
  return api.fetch(api.stories());
};

const fetchTopStoriesDetails = stories => {
  return Promise.all(
      stories.map(id => api.fetch(api.story(id)))
    );
};

const sortByTime = (newsest, oldest) => oldest.time - newsest.time;

const handlePingError = error => {
  spinner.stop();

  if (error.code === 'ENOTFOUND') {
    console.log(`Looks like you have internet connection issues ☹`);
  } else if (error.code === 'ETIMEDOUT') {
    console.log(`Tried ${fetchOptions.retries} times but the request has timed out. Sorry ☹`);
  } else {
    console.log(error);
  }

  return process.exit(1);
};

const ping = (options, shouldMute) => {
  const log = shouldMute ? noop : spinner.start;

  log(`Fetching top stories`);

  return fetchTopStories()
    // Limit results before requests are fired
    .then(response => {
      return limitResults(response.body, options.limit);
    })
    // Fires all requests
    .then(response => {
      log(`Loading details of the latest ${options.limit} top stories`);
      return fetchTopStoriesDetails(response);
    })
    // Returns a formatted array with the response request
    .then(response => {
      return response.map(item => item.body);
    })
    // Sort the reponse if requested
    .then(response => {
      return (options.latest) ? response.slice().sort(sortByTime) : response;
    })
    .then(response => {
      // Finally loaded
      spinner.stop();

      // Store data on local cache so it can be used later
      cache = response;

      return cache;
    })
    // Format the result to a data format compatible with the table widget
    .then(response => {
      return parseTableData(response);
    })
    // Handle error messages
    .catch(error => {
      handlePingError(error);
    });
};

const onTableSelect = (index, key) => {
  const selected = cache[index - 1];

  if (!selected.url || key === 'c') {
    openUrl(api.storyUrl(selected.id));
  } else if (key === 't') {
    openUrl(`https://twitter.com/intent/tweet?url=${selected.url}&text=${selected.title}`);
  } else {
    openUrl(selected.url);
  }
};

const render = (renderer, data) => renderer.render(data);

const reportStatusUpdate = renderer => {
  renderer.status = `Last updated at ${now()}`;
};

const run = options => {
  const renderer = new Renderer({
    shouldCloseOnSelect: !options['keep-open']
  });

  renderer.onTableSelect = onTableSelect;

  // Fetch data then render
  ping(options).then(response => {
    render(renderer, response);
    reportStatusUpdate(renderer);
  });

  return renderer;
};

module.exports = options => {
  const renderer = run(options);
  const onRefreshRequest = () => {
    renderer.status = `Updating list, hold on...`;
    // Refresh data then render
    ping(options, true).then(response => {
      renderer.update(response);
      reportStatusUpdate(renderer);
    });
  };

  renderer.onRefreshRequest = onRefreshRequest;
};
<commit_msg>change the comment of sorting block code<commit_after>'use strict';

const Promise = require('pinkie-promise');
const openUrl = require('opn');
const now = require('./parsers/now');
const spinner = require('./spinner');
const Renderer = require('./renderer');
const parseTableData = require('./parsers/table');
const fetchOptions = require('./options/fetch');
const api = require('./api')(fetchOptions);
const noop = require('./noop');

let cache = {};

const limitResults = (results, limit) => results.slice(0, limit);

const fetchTopStories = () => {
  return api.fetch(api.stories());
};

const fetchTopStoriesDetails = stories => {
  return Promise.all(
      stories.map(id => api.fetch(api.story(id)))
    );
};

const sortByTime = (newsest, oldest) => oldest.time - newsest.time;

const handlePingError = error => {
  spinner.stop();

  if (error.code === 'ENOTFOUND') {
    console.log(`Looks like you have internet connection issues ☹`);
  } else if (error.code === 'ETIMEDOUT') {
    console.log(`Tried ${fetchOptions.retries} times but the request has timed out. Sorry ☹`);
  } else {
    console.log(error);
  }

  return process.exit(1);
};

const ping = (options, shouldMute) => {
  const log = shouldMute ? noop : spinner.start;

  log(`Fetching top stories`);

  return fetchTopStories()
    // Limit results before requests are fired
    .then(response => {
      return limitResults(response.body, options.limit);
    })
    // Fires all requests
    .then(response => {
      log(`Loading details of the latest ${options.limit} top stories`);
      return fetchTopStoriesDetails(response);
    })
    // Returns a formatted array with the response request
    .then(response => {
      return response.map(item => item.body);
    })
    // Sort the response if options.latest requested
    .then(response => {
      return (options.latest) ? response.slice().sort(sortByTime) : response;
    })
    .then(response => {
      // Finally loaded
      spinner.stop();

      // Store data on local cache so it can be used later
      cache = response;

      return cache;
    })
    // Format the result to a data format compatible with the table widget
    .then(response => {
      return parseTableData(response);
    })
    // Handle error messages
    .catch(error => {
      handlePingError(error);
    });
};

const onTableSelect = (index, key) => {
  const selected = cache[index - 1];

  if (!selected.url || key === 'c') {
    openUrl(api.storyUrl(selected.id));
  } else if (key === 't') {
    openUrl(`https://twitter.com/intent/tweet?url=${selected.url}&text=${selected.title}`);
  } else {
    openUrl(selected.url);
  }
};

const render = (renderer, data) => renderer.render(data);

const reportStatusUpdate = renderer => {
  renderer.status = `Last updated at ${now()}`;
};

const run = options => {
  const renderer = new Renderer({
    shouldCloseOnSelect: !options['keep-open']
  });

  renderer.onTableSelect = onTableSelect;

  // Fetch data then render
  ping(options).then(response => {
    render(renderer, response);
    reportStatusUpdate(renderer);
  });

  return renderer;
};

module.exports = options => {
  const renderer = run(options);
  const onRefreshRequest = () => {
    renderer.status = `Updating list, hold on...`;
    // Refresh data then render
    ping(options, true).then(response => {
      renderer.update(response);
      reportStatusUpdate(renderer);
    });
  };

  renderer.onRefreshRequest = onRefreshRequest;
};
<|endoftext|>


<commit_before>// Node in an SVG document
// Contains all the options for optimising how the SVG is written
var SVG_Element = function(element, parents) {
    this.tag = element.nodeName;
    this.attributes = {};
    this.styles = {};
    this.parents = parents;
    this.text = "";

    // Add attributes to hash
    // Style attributes have a separate hash
    if (element.attributes) {
        for (var i = 0; i < element.attributes.length; i++){
            var attr = element.attributes.item(i);
            var attrName = attr.nodeName;

            if (attrName === 'style') {
                $.extend(this.styles, this.parseStyle(attr.value));
            } else if (defaultStyles[attrName] !== undefined || nonEssentialStyles[attrName] !== undefined) {
                this.styles[attrName] = attr.value;
            } else {
                this.attributes[attrName] = attr.value;

                // Parse path coordinates
                if (this.tag === "path" && attrName === 'd') {
                    this.pathCommands = this.parsePath(attr.value);
                }
            }
        }
    }

    // Add children
    this.children = [];

    for (var i = 0; i < element.childNodes.length; i++) {
        var child = element.childNodes[i];
        if (child instanceof Text) {
            // Tag contains text
            if (child.data.replace(/^\s*/, "") !== "") {
                this.text = child.data;
            }
        } else {
            this.children.push(new SVG_Element(child, this));
        }
    }
};

// Return an array of attributes that have not been removed
SVG_Element.prototype.getUsedAttributes = function(options) {
    var usedAttributes = [];

    for (var attr in this.attributes) {
        // Remove attributes whose namespace has been removed and links to namespace URIs
        if (attr.indexOf(':') !== -1) {
            var ns = attr.split(':');
            if (!options.namespaces[ns[0]] || (ns[0] === 'xmlns' && !options.namespaces[ns[1]])) {
                continue;
            }
        }

        // TODO: only remove ids that are not referenced elsewhere
        if (options.removeIDs && attr === 'id') {
            continue;
        }

        usedAttributes.push(attr);
    }

    return usedAttributes;
};

// Split a string from a path "d" attribute into a list of letters and values
SVG_Element.prototype.parsePath = function(dAttr) {
    var reCommands = /([ACHLMQSTVZ])([-\+\d\.\s,e]*)/gi
    var reDigits = /([-+]?[\d\.]+)([eE][-+]?[\d\.]+)?/g;
    var letters = [];
    var values = [];

    // Converts a string of digits to an array of floats
    var getDigits = function(digitString) {
        var digits = [];
        if (digitString) {
            while (digit = reDigits.exec(digitString)) {
                digits.push(parseFloat(digit));
            }
        }
        return digits;
    };

    while (commands = reCommands.exec(dAttr)) {
        letters.push(commands[1]);
        values.push(getDigits(commands[2]));
    }

    return { letters: letters, values: values };
}

// Split a string from a style attribute into a hash of styles
// e.g. style="fill:#269276;opacity:1" => {fill: '#269276', opacity: '1'}
SVG_Element.prototype.parseStyle = function(styleString) {
    var styles = {};
    var styleArray = styleString.split(/\s*;\s*/);

    for (var i = 0; i < styleArray.length; i++) {
        var value = styleArray[i].split(/\s*:\s*/);

        if (value.length === 2) {
            styles[value[0]] = value[1];
        }
    }

    return styles;
};

// Return a list of strings in the form "style:value" for the styles that are to be used
SVG_Element.prototype.getUsedStyles = function(options) {
    if (!this.styles) { return []; }

    var usedStyles = [];

    // Ignore other fill or stroke attributes if value is none or it is transparent
    var ignoreFill = (this.styles['fill'] === 'none' || options.styleDecimals(this.styles['fill-opacity']) == 0);
    var ignoreStroke = (this.styles['stroke'] === 'none' || options.styleDecimals(this.styles['stroke-opacity']) == 0 || options.styleDecimals(this.styles['stroke-width']) == 0);

    if ((ignoreFill && ignoreStroke) || this.styles['visibility'] === 'hidden'|| options.styleDecimals(this.styles['opacity']) == 0) {
        // TODO: don't show this element
        // Seems this would only be likely for animations or some weird styling with groups
    }

    for (var style in this.styles) {
        var value = options.styleDecimals(this.styles[style]);

        // Simplify colours, e.g. #ffffff -> #fff
        var repeated = value.match(/^#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/i);
        if (repeated) {
            value = '#' + repeated[1]  + repeated[2] + repeated[3];
        }

        if (ignoreFill && style.substr(0, 4) === 'fill') { continue; }
        if (ignoreStroke && style.substr(0, 6) === 'stroke') { continue; }
        if (options.removeDefaultStyles && value === defaultStyles[style]) { continue; }
        if (options.removeNonEssentialStyles && options.nonEssentialStyles[style]) { continue; }

        usedStyles.push(style + ":" + value);
    }

    if (ignoreFill) { usedStyles.push('fill:none'); }

    return usedStyles.sort();
};

// Return a string representing the SVG element
// All the optimisation is done here, so none of the original information is lost
SVG_Element.prototype.toString = function(options, depth) {
    // Remove namespace information
    if (this.tag.indexOf(':') !== -1) {
        var ns = this.tag.split(':')[0];
        if (!options.namespaces[ns]) {
            return "";
        }
    }

    var depth = depth || 0;
    var indent = (options.whitespace === 'remove') ? '' : new Array(depth + 1).join('  ');

    var str = indent + '<' + this.tag;

    // Write attributes
    var usedAttributes = this.getUsedAttributes(options);
    for (var i = 0; i < usedAttributes.length; i++) {
        var attr = usedAttributes[i];
        str += ' ' + attr + '="';

        // TODO: convert tags to lowercase so will work with 'viewbox'
        // TODO: also apply decimal places to transforms
        // TODO: add polygons and polylines
        if (attr === 'viewBox' || attr === 'points') {
            var values = this.attributes[attr].split(/[\s,]+/);
            values = $.map(values, options.attrDecimals);
            str += values.join(" ");
        } else if (this.tag === 'path' && attr === 'd') {
            var coordString = this.getPathString(options);
            if (coordString) {
                str += coordString;
            } else {
                return "";
            }
        } else {
            if (attr !== 'version') {
                str += options.attrDecimals(this.attributes[attr]);
            } else {
                str += this.attributes[attr];
            }
        }
        str += '"';
    }

    // Write styles
    var usedStyles = this.getUsedStyles(options);
    if (usedStyles.length > 1) {
        str += ' style="' + usedStyles.join(';') + '"';
    } else if (usedStyles.length === 1) {
        var style = usedStyles[0].split(':');
        str += ' ' + style[0] + '="' + style[1] + '"';
    }

    // Don't write group if it has no attributes, but do write its children
    // Assume g element has no text (which it shouldn't)
    // TODO: if g contains styles could add styles to children (only if using CSS)
    if (this.tag === 'g' && options.removeCleanGroups && !usedAttributes && !usedStyles) {
        var childString = "";
        for (var i = 0; i < this.children.length; i++) {
            childString += this.children[i].toString(options, depth + 1);
        }
        return childString;
    }

    // Write child information
    var childString = "";
    for (var i = 0; i < this.children.length; i++) {
        childString += this.children[i].toString(options, depth + 1);
    }

    if (this.text.length + childString.length > 0) {
        str += ">" + options.newLine;
        if (this.text) {
            str += indent + "  " + this.text + options.newLine;
        }
        if (childString) {
            str += childString;
        }
        str += indent + "</" + this.tag + ">" + options.newLine;
    } else {
        if (options.removeEmptyElements && usedAttributes.length === 0) {
            return "";
        }
        str += "/>" + options.newLine;
    }

    return str;
};

// Create a string for the 'd' attribute of a path
SVG_Element.prototype.getPathString = function(options) {
        var coordString = "";

        if (this.pathCommands) {
            var letters = this.pathCommands.letters;
            var values = this.pathCommands.values;

            var currentLetter;
            for (var i = 0; i < letters.length; i++) {
                coordString += (letters[i] === currentLetter) ? " " : letters[i];
                currentLetter = letters[i];

                if (values[i]) {
                    for (var j = 0; j < values[i].length; j++) {
                        if (j > 0  && values[i][j] >= 0) coordString += " ";
                        coordString += options.attrDecimals(values[i][j]);
                    }
                }
            }
        }

        return coordString;
    };

// A wrapper for SVG_Elements which store the options for optimisation
// Build from a jQuery object representing the SVG
var SVG_Object = function(jQuerySVG) {
    this.elements = new SVG_Element(jQuerySVG, null);

    // Set default options
    this.options = {
        whitespace: 'remove',
        removeIDs: false,
        removeDefaultStyles: true,
        removeNonEssentialStyles: true,
        removeEmptyElements: true,
        removeCleanGroups: true,
        attributeDecimalPlaces: 1,
        styleDecimalPlaces: 2,
    };

    this.options.nonEssentialStyles = nonEssentialStyles;
    this.options.namespaces = this.findNamespaces();
};

// Namespaces are attributes of the SVG element, prefaced with 'xmlns:'
// Create a hash mapping namespaces to false, except for the SVG namespace
SVG_Object.prototype.findNamespaces = function() {
        var namespaces = {};

        for (attr in this.elements.attributes) {
            if (attr.slice(0,6) === 'xmlns:') {
                var ns = attr.split(':')[1];
                namespaces[ns] = (ns === 'svg');
            }
        }

        return namespaces;
};

SVG_Object.prototype.toString = function() {
    this.options.newLine = (this.options.whitespace === 'remove') ? "": "\n";
    this.options.attrDecimals = this.getDecimalPlaceFunction(this.options.attributeDecimalPlaces);
    this.options.styleDecimals = this.getDecimalPlaceFunction(this.options.styleDecimalPlaces);

    return this.elements.toString(this.options);
};

// Return a function that given a number returns a rounded version to n decimal places
SVG_Object.prototype.getDecimalPlaceFunction = function(decimalPlaces) {
    if (!isNaN(parseInt(decimalPlaces))) {
        var scale = Math.pow(10, decimalPlaces);
        var reDigit = /^\s*([-+]?[\d\.]+)([eE][-+]?[\d\.]+)?\s*(%|em|ex|px|pt|pc|cm|mm|in)\s*$/;

        return function(str) {
            var digit = reDigit.exec(str);
            var n = parseFloat(digit ? digit[1] + (digit[2] || "") : str);

            if (isNaN(n)) {
                return str;
            } else {
                return "" + (Math.round(n * scale) / scale) + (digit ? digit[3] : "");
            }
        };
    } else {
        return function(str) { return str; };
    }
};<commit_msg>Start work on transformations and allow for different methods of decimal truncation<commit_after>// Node in an SVG document
// Contains all the options for optimising how the SVG is written
var SVG_Element = function(element, parents) {
    this.tag = element.nodeName;
    this.attributes = {};
    this.styles = {};
    this.parents = parents;
    this.text = "";

    // Add attributes to hash
    // Style attributes have a separate hash
    if (element.attributes) {
        for (var i = 0; i < element.attributes.length; i++){
            var attr = element.attributes.item(i);
            var attrName = attr.nodeName;

            if (attrName === 'style') {
                $.extend(this.styles, this.parseStyle(attr.value));
            } else if (defaultStyles[attrName] !== undefined || nonEssentialStyles[attrName] !== undefined) {
                // Style written as a separate attribute
                this.styles[attrName] = attr.value;
            } else {
                this.attributes[attrName] = attr.value;

                // Parse path coordinates
                if (this.tag === "path" && attrName === 'd') {
                    this.pathCommands = this.parsePath(attr.value);
                } else if (attrName === 'transform') {
                    this.transformations = this.parseTransform(attr.value);
                }
            }
        }
    }

    // Add children
    this.children = [];

    for (var i = 0; i < element.childNodes.length; i++) {
        var child = element.childNodes[i];
        if (child instanceof Text) {
            // Tag contains text
            if (child.data.replace(/^\s*/, "") !== "") {
                this.text = child.data;
            }
        } else {
            this.children.push(new SVG_Element(child, this));
        }
    }
};

// Split a string from a path "d" attribute into a list of letters and values
SVG_Element.prototype.parsePath = function(dAttr) {
    var reCommands = /([ACHLMQSTVZ])([-\+\d\.\s,e]*)/gi
    var reDigits = /([-+]?[\d\.]+)([eE][-+]?[\d\.]+)?/g;
    var letters = [];
    var values = [];

    // Converts a string of digits to an array of floats
    var getDigits = function(digitString) {
        var digit, digits = [];

        if (digitString) {
            while (digit = reDigits.exec(digitString)) {
                digits.push(parseFloat(digit));
            }
        }
        return digits;
    };

    while (commands = reCommands.exec(dAttr)) {
        letters.push(commands[1]);
        values.push(getDigits(commands[2]));
    }

    return { letters: letters, values: values };
}

// Split a string from a style attribute into a hash of styles
// e.g. style="fill:#269276;opacity:1" => {fill: '#269276', opacity: '1'}
SVG_Element.prototype.parseStyle = function(styleString) {
    var styles = {};
    var styleArray = styleString.split(/\s*;\s*/);

    for (var i = 0; i < styleArray.length; i++) {
        var value = styleArray[i].split(/\s*:\s*/);

        if (value.length === 2) {
            styles[value[0]] = value[1];
        }
    }

    return styles;
};

SVG_Element.prototype.parseTransform = function(transformString) {
    var reTransform = /([a-z]+)\s*\(([-\+\d\.\s,e]+)\)/gi;
    var transform, transforms = [];

    if (transformString) {
        while (transform = reTransform.exec(transformString)) {
            digits = transform[2].split(/\s*,\s*/);
            transforms.push(transform[1], $.map(digits, parseFloat))
        }
    }

    return transforms;

};

// Return an array of attributes that have not been removed
SVG_Element.prototype.getUsedAttributes = function(options) {
    var usedAttributes = [];

    for (var attr in this.attributes) {
        // Remove attributes whose namespace has been removed and links to namespace URIs
        if (attr.indexOf(':') !== -1) {
            var ns = attr.split(':');
            if (!options.namespaces[ns[0]] || (ns[0] === 'xmlns' && !options.namespaces[ns[1]])) {
                continue;
            }
        }

        if (options.removeDefaultAttributes &&
            positionAttributes.indexOf(attr) !== -1 &&
            options.positionDecimals(this.attributes[attr]) == 0) {
            continue;
        }

        // TODO: only remove ids that are not referenced elsewhere
        if (options.removeIDs && attr === 'id') {
            continue;
        }

        usedAttributes.push(attr);
    }

    return usedAttributes;
};

// Return a list of strings in the form "style:value" for the styles that are to be used
SVG_Element.prototype.getUsedStyles = function(options) {
    if (!this.styles) { return []; }

    var usedStyles = [];

    // Ignore other fill or stroke attributes if value is none or it is transparent
    var ignoreFill = (this.styles['fill'] === 'none' || options.styleDecimals(this.styles['fill-opacity']) == 0);
    var ignoreStroke = (this.styles['stroke'] === 'none' || options.styleDecimals(this.styles['stroke-opacity']) == 0 || options.styleDecimals(this.styles['stroke-width']) == 0);

    if ((ignoreFill && ignoreStroke) || this.styles['visibility'] === 'hidden'|| options.styleDecimals(this.styles['opacity']) == 0) {
        // TODO: don't show this element
        // Seems this would only be likely for animations or some weird styling with groups
    }

    for (var style in this.styles) {
        var value = options.styleDecimals(this.styles[style]);

        // If we're multiplying positons by powers of 10, certain styles also need multiplying
        // TODO: will also have to change font sizes
        if (options.attributeNumTruncate[1] === 'order of magnitude' && style === 'stroke-width') {
            value = options.positionDecimals(this.styles[style]);
        }

        // Simplify colours, e.g. #ffffff -> #fff
        var repeated = value.match(/^#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/i);
        if (repeated) {
            value = '#' + repeated[1]  + repeated[2] + repeated[3];
        }

        if (ignoreFill && style.substr(0, 4) === 'fill') { continue; }
        if (ignoreStroke && style.substr(0, 6) === 'stroke') { continue; }
        if (options.removeDefaultStyles && value == defaultStyles[style]) { continue; }
        if (options.removeNonEssentialStyles && options.nonEssentialStyles[style]) { continue; }

        usedStyles.push(style + ":" + value);
    }

    if (ignoreFill) { usedStyles.push('fill:none'); }

    return usedStyles.sort();
};

// Return a string representing the SVG element
// All the optimisation is done here, so none of the original information is lost
SVG_Element.prototype.toString = function(options, depth) {
    // Remove namespace information
    if (this.tag.indexOf(':') !== -1) {
        var ns = this.tag.split(':')[0];
        if (!options.namespaces[ns]) {
            return "";
        }
    }

    var depth = depth || 0;
    var indent = (options.whitespace === 'remove') ? '' : new Array(depth + 1).join('  ');

    var str = indent + '<' + this.tag;

    // Write attributes with numbers truncated in the prefered way
    var usedAttributes = this.getUsedAttributes(options);
    for (var i = 0; i < usedAttributes.length; i++) {
        var attr = usedAttributes[i];
        str += ' ' + attr + '="';

        // TODO: convert tags to lowercase so will work with 'viewbox'
        // TODO: also apply decimal places to transforms
        if (attr === 'viewBox' || attr === 'points') {
            var values = this.attributes[attr].split(/[\s,]+/);
            values = $.map(values, options.positionDecimals);
            str += values.join(" ");
        } else if (this.tag === 'path' && attr === 'd') {
            var coordString = this.getPathString(options);
            // Don't show a path with no coordinates
            if (!coordString) { return ""; }
            str += coordString;
        } else if (this.tag === 'svg' && (attr === 'width' || attr === 'height')) {
            str += options.svgSizeDecimals(this.attributes[attr]);
        } else if (positionAttributes.indexOf(attr) !== -1 ) {
            str += options.positionDecimals(this.attributes[attr]);
        } else {
            str += this.attributes[attr];
        }
        str += '"';
    }

    // Write styles
    var usedStyles = this.getUsedStyles(options);
    if (usedStyles.length > 1) {
        str += ' style="' + usedStyles.join(';') + '"';
    } else if (usedStyles.length === 1) {
        var style = usedStyles[0].split(':');
        str += ' ' + style[0] + '="' + style[1] + '"';
    }

    // Don't write group if it has no attributes, but do write its children
    // Assume g element has no text (which it shouldn't)
    // TODO: if g contains styles could add styles to children (only if using CSS)
    if (this.tag === 'g' && options.removeCleanGroups && !usedAttributes && !usedStyles) {
        var childString = "";
        for (var i = 0; i < this.children.length; i++) {
            childString += this.children[i].toString(options, depth + 1);
        }
        return childString;
    }

    // Write child information
    var childString = "";
    for (var i = 0; i < this.children.length; i++) {
        childString += this.children[i].toString(options, depth + 1);
    }

    if (this.text.length + childString.length > 0) {
        str += ">" + options.newLine;
        if (this.text) {
            str += indent + "  " + this.text + options.newLine;
        }
        if (childString) {
            str += childString;
        }
        str += indent + "</" + this.tag + ">" + options.newLine;
    } else {
        if (options.removeEmptyElements && usedAttributes.length === 0) {
            return "";
        }
        str += "/>" + options.newLine;
    }

    return str;
};

// Create a string for the 'd' attribute of a path
SVG_Element.prototype.getPathString = function(options) {
    var coordString = "";

    if (this.pathCommands) {
        var letters = this.pathCommands.letters;
        var values = this.pathCommands.values;

        var currentLetter;
        for (var i = 0; i < letters.length; i++) {
            coordString += (letters[i] === currentLetter) ? " " : letters[i];
            currentLetter = letters[i];

            if (values[i]) {
                for (var j = 0; j < values[i].length; j++) {
                    if (j > 0  && values[i][j] >= 0) coordString += " ";
                    coordString += options.positionDecimals(values[i][j]);
                }
            }
        }
    }

    return coordString;
};

SVG_Element.prototype.applyTransform = function() {

};

// A wrapper for SVG_Elements which store the options for optimisation
// Build from a jQuery object representing the SVG
var SVG_Object = function(jQuerySVG) {
    this.elements = new SVG_Element(jQuerySVG, null);

    // Set default options
    this.options = {
        whitespace: 'remove',
        removeIDs: false,
        removeDefaultAttributes: true,
        removeDefaultStyles: true,
        removeNonEssentialStyles: true,
        removeEmptyElements: true,
        removeCleanGroups: true,
        applyTransforms: true,
        attributeNumTruncate: [1, 'decimal place'],
        styleNumTruncate: [2, 'significant figure'],
        svgSizeTruncate: [0, 'decimal place'],
    };

    this.options.attributeNumTruncate = [1, 'order of magnitude'];

    this.options.nonEssentialStyles = nonEssentialStyles;
    this.options.namespaces = this.findNamespaces();
};

// Namespaces are attributes of the SVG element, prefaced with 'xmlns:'
// Create a hash mapping namespaces to false, except for the SVG namespace
SVG_Object.prototype.findNamespaces = function() {
        var namespaces = {};

        for (attr in this.elements.attributes) {
            if (attr.slice(0,6) === 'xmlns:') {
                var ns = attr.split(':')[1];
                namespaces[ns] = (ns === 'svg');
            }
        }

        return namespaces;
};

SVG_Object.prototype.toString = function() {
    this.options.newLine = (this.options.whitespace === 'remove') ? "": "\n";

    this.options.positionDecimals = this.getDecimalOptimiserFunction(this.options.attributeNumTruncate);
    this.options.styleDecimals = this.getDecimalOptimiserFunction(this.options.styleNumTruncate);
    this.options.svgSizeDecimals = this.getDecimalOptimiserFunction(this.options.svgSizeTruncate);

    return this.elements.toString(this.options);
};

// Return a function that given a number optimises it.
// type === 'decimal place': round to a number of decimal places
// type === 'significant figure': round to a number of significant figures (needs work)
// type === 'order of magnitude': multiply by a power of 10, then round
SVG_Object.prototype.getDecimalOptimiserFunction = function(parameters) {
    var level = parameters[0];
    var type = parameters[1];

    if (!isNaN(parseInt(level))) {
        var scale = Math.pow(10, level);
        var reDigit = /^\s*([-+]?[\d\.]+)([eE][-+]?[\d\.]+)?\s*(%|em|ex|px|pt|pc|cm|mm|in)\s*$/;

        var roundFunction;
        if (type === 'decimal place') {
            roundFunction = function(n) { return Math.round(n * scale) / scale; }
        } else if (type === 'significant figure') {
            roundFunction = function(n) {
                if (n == 0) { return 0; }
                var mag = Math.pow(10, level - Math.ceil(Math.log(n < 0 ? -n: n) / Math.LN10));
                return Math.round(n * mag) / mag;
            }
        } else if (type === 'order of magnitude') {
            roundFunction = function(n) { return Math.round(n * scale); }
        } else {
            roundFunction = function(n) { return n; }
        }

        return function(str) {
            // Parse digit string to digit, while keeping any final units
            var digit = reDigit.exec(str);
            var n = parseFloat(digit ? digit[1] + (digit[2] || "") : str);

            if (isNaN(n)) {
                return str;
            } else {
                return roundFunction(n) + (digit ? digit[3] : "");
            }
        };
    } else {
        // This shouldn't happen, but just in case, return an identity function
        return function(str) { return str; };
    }
};<|endoftext|>


<commit_before>const Promise = require('bluebird');
const Errors = require('common-errors');
const EventEmitter = require('eventemitter3');
const forOwn = require('lodash/forOwn');
const each = require('lodash/each');
const flatten = require('lodash/flatten');
const is = require('is');
const stdout = require('stdout-stream');
const partial = require('lodash/partial');
const assert = require('assert');

const CONNECTORS_PROPERTY = '_connectors';
const DESTRUCTORS_PROPERTY = '_destructors';

/**
 * Configuration options for the service
 * @type {Object}
 */
const defaultOpts = {
  logger: {
    debug: process.env.NODE_ENV !== 'production',
  },
  plugins: ['validator', 'logger', 'amqp'],
  hooks: {},
  amqp: {},
  sigterm: true,
};

/**
 * @namespace Mservice
 */
class Mservice extends EventEmitter {
  /**
   * @type {{amqp: string, http: string, socketIO: string}}
   */
  static ActionTransport = {
    amqp: 'amqp',
    http: 'http',
    socketIO: 'socketIO',
  };

  static ConnectorsTypes = {
    database: 'database',
    migration: 'migration',
    transport: 'transport',
  }

  static ConnectorsPriority = [
    Mservice.ConnectorsTypes.database,
    Mservice.ConnectorsTypes.migration,
    Mservice.ConnectorsTypes.transport,
  ]

  static PluginsTypes = {
    essential: 'essential',
    database: 'database',
    transport: 'transport',
  }

  static PluginsPriority = [
    Mservice.PluginsTypes.essential,
    Mservice.PluginsTypes.database,
    Mservice.PluginsTypes.transport,
  ];

  static routerExtension(name) {
    // eslint-disable-next-line import/no-dynamic-require
    return require(`./plugins/router/extensions/${name}`);
  }

  /**
   * @namespace Users
   * @param  {Object} opts
   * @return {Users}
   */
  constructor(opts = {}) {
    super();

    // init configuration
    const config = this._config = { ...defaultOpts, ...opts };

    // init getters
    [
      'config', 'amqp', 'redis',
      'validator', 'log', 'elasticsearch',
      'cassandra', 'http', 'socketIO',
      'router', 'knex',
    ].map(prop => this._defineGetter(prop));

    // init migrations
    this._migrators = {};

    // init plugins
    this._initPlugins(config);

    // setup error listener
    this.on('error', (err) => {
      this._onError(err);
    });

    // setup hooks
    forOwn(config.hooks, (_hooks, eventName) => {
      const hooks = Array.isArray(_hooks) ? _hooks : [_hooks];
      each(hooks, hook => this.on(eventName, hook));
    });

    if (config.sigterm) {
      const exit = this.exit.bind(this);

      this.on('ready', () => {
        process.on('SIGTERM', exit);
      });

      this.on('close', () => {
        process.removeListener('SIGTERM', exit);
      });
    }
  }

  /**
   * Overrides SIG* events and exits cleanly
   */
  exit() {
    stdout.write('received close signal...\n closing connections...\n');
    return this.close()
      .timeout(1000)
      .finally(() => {
        process.exit(0);
      });
  }

  /**
   * asyncronously calls event listeners
   * and waits for them to complete.
   * This is a bit odd compared to normal event listeners,
   * but works well for dynamically running async actions and waiting
   * for them to complete
   *
   * @param  {String} event
   * @param  {Mixed}  ...args
   * @return {Promise}
   */
  hook(event, ...args) {
    const listeners = this.listeners(event);

    return Promise
      .bind(this)
      .return(listeners)
      .map(function performOp(listener) {
        return listener.apply(this, args);
      });
  }

  /**
   * Adds migrators
   */
  addMigrator(name, fn, ...args) {
    this._migrators[name] = partial(fn, ...args);
  }

  /**
   * Performs migration for a given database or throws if migrator is not present
   */
  migrate(name, ...args) {
    const migrate = this._migrators[name];
    assert(is.fn(migrate), `migrator ${name} not defined`);
    return migrate(...args);
  }

  /**
   * Defines convinience getters
   */
  _defineGetter(name) {
    Object.defineProperty(this, name, {
      __proto__: null,
      enumerable: true,
      configurable: false,
      get: () => this._get(name),
    });
  }

  /**
   *
   */
  _get(name) {
    const it = this[`_${name}`];
    if (it) {
      return it;
    }

    return this.emit('error', new Errors.NotPermittedError(`${name} was not initialized`));
  }

  /**
   * Generic connector for all of the plugins
   * @return {Promise}
   */
  connect() {
    return this._processAndEmit(this.getConnectors(), 'ready');
  }

  /**
   * Generic cleanup function
   * @return {Promise}
   */
  close() {
    return this._processAndEmit(this.getDesturctors(), 'close');
  }

  /**
   * Helper for calling funcs and emitting event after
   */
  _processAndEmit(collection, event) {
    return Promise
      .mapSeries(
        Mservice.ConnectorsPriority,
        (connectorType) => {
          const connectors = collection[connectorType];

          if (!connectors) {
            return [];
          }

          return Promise.map(connectors, func => func());
        }
      )
      .then(result => flatten(result))
      .tap(() => this.emit(event));
  }

  // ****************************** Plugin section: public ************************************

  /**
   * Public function to init plugins
   *
   * @param  {Object} mod
   * @param  {String} mod.name
   * @param  {Function} mod.attach
   */
  initPlugin(mod, conf) {
    const expose = mod.attach.call(this, conf || this._config[mod.name], __filename);

    if (!is.object(expose)) {
      return;
    }

    const { connect, close } = expose;
    const type = Mservice.ConnectorsTypes[mod.type];

    assert(type, 'Plugin type must be equal to one of connectors type');

    if (is.fn(connect)) {
      this.addConnector(type, connect);
    }

    if (is.fn(close)) {
      this.addDestructor(type, close);
    }
  }

  getConnectors() {
    return this[CONNECTORS_PROPERTY];
  }

  getDesturctors() {
    return this[DESTRUCTORS_PROPERTY];
  }

  addConnector(type, handler) {
    this._addHandler(CONNECTORS_PROPERTY, type, handler);
  }

  addDestructor(type, handler) {
    this._addHandler(DESTRUCTORS_PROPERTY, type, handler);
  }

  // ***************************** Plugin section: private **************************************

  _addHandler(property, type, handler) {
    if (this[property] === undefined) {
      this[property] = {};
    }

    if (this[property][type] === undefined) {
      this[property][type] = [];
    }

    this[property][type].push(handler);
  }

  /**
   * Initializes service plugins
   * @param  {Object} config
   */
  _initPlugins(config) {
    this._connectors = {};
    this._destructors = {};

    Mservice.PluginsPriority
      .forEach((pluginType) => {
        this._connectors[pluginType] = [];
        this._destructors[pluginType] = [];
      });

    // init plugins
    config.plugins.forEach((plugin) => {
      // eslint-disable-next-line import/no-dynamic-require
      this.initPlugin(require(`./plugins/${plugin}`));
    });

    this.emit('init');
  }

  /**
   * Notifies about errors when no other listeners are present
   * by throwing them
   * @param  {Object} err
   */
  _onError(err) {
    if (this.listeners('error').length > 1) {
      return;
    }

    throw err;
  }
}

module.exports = Mservice;
<commit_msg>feat(plugins): add `application` connector type (#210)<commit_after>const Promise = require('bluebird');
const Errors = require('common-errors');
const EventEmitter = require('eventemitter3');
const forOwn = require('lodash/forOwn');
const each = require('lodash/each');
const flatten = require('lodash/flatten');
const is = require('is');
const stdout = require('stdout-stream');
const partial = require('lodash/partial');
const assert = require('assert');

const CONNECTORS_PROPERTY = '_connectors';
const DESTRUCTORS_PROPERTY = '_destructors';

/**
 * Configuration options for the service
 * @type {Object}
 */
const defaultOpts = {
  logger: {
    debug: process.env.NODE_ENV !== 'production',
  },
  plugins: ['validator', 'logger', 'amqp'],
  hooks: {},
  amqp: {},
  sigterm: true,
};

/**
 * @namespace Mservice
 */
class Mservice extends EventEmitter {
  /**
   * @type {{amqp: string, http: string, socketIO: string}}
   */
  static ActionTransport = {
    amqp: 'amqp',
    http: 'http',
    socketIO: 'socketIO',
  };

  static ConnectorsTypes = {
    database: 'database',
    migration: 'migration',
    transport: 'transport',
    application: 'application',
  }

  static ConnectorsPriority = [
    Mservice.ConnectorsTypes.database,
    Mservice.ConnectorsTypes.migration,
    Mservice.ConnectorsTypes.transport,
    Mservice.ConnectorsTypes.application,
  ]

  static PluginsTypes = {
    essential: 'essential',
    database: 'database',
    transport: 'transport',
  }

  static PluginsPriority = [
    Mservice.PluginsTypes.essential,
    Mservice.PluginsTypes.database,
    Mservice.PluginsTypes.transport,
  ];

  static routerExtension(name) {
    // eslint-disable-next-line import/no-dynamic-require
    return require(`./plugins/router/extensions/${name}`);
  }

  /**
   * @namespace Users
   * @param  {Object} opts
   * @return {Users}
   */
  constructor(opts = {}) {
    super();

    // init configuration
    const config = this._config = { ...defaultOpts, ...opts };

    // init getters
    [
      'config', 'amqp', 'redis',
      'validator', 'log', 'elasticsearch',
      'cassandra', 'http', 'socketIO',
      'router', 'knex',
    ].map(prop => this._defineGetter(prop));

    // init migrations
    this._migrators = {};

    // init plugins
    this._initPlugins(config);

    // setup error listener
    this.on('error', (err) => {
      this._onError(err);
    });

    // setup hooks
    forOwn(config.hooks, (_hooks, eventName) => {
      const hooks = Array.isArray(_hooks) ? _hooks : [_hooks];
      each(hooks, hook => this.on(eventName, hook));
    });

    if (config.sigterm) {
      const exit = this.exit.bind(this);

      this.on('ready', () => {
        process.on('SIGTERM', exit);
      });

      this.on('close', () => {
        process.removeListener('SIGTERM', exit);
      });
    }
  }

  /**
   * Overrides SIG* events and exits cleanly
   */
  exit() {
    stdout.write('received close signal...\n closing connections...\n');
    return this.close()
      .timeout(1000)
      .finally(() => {
        process.exit(0);
      });
  }

  /**
   * asyncronously calls event listeners
   * and waits for them to complete.
   * This is a bit odd compared to normal event listeners,
   * but works well for dynamically running async actions and waiting
   * for them to complete
   *
   * @param  {String} event
   * @param  {Mixed}  ...args
   * @return {Promise}
   */
  hook(event, ...args) {
    const listeners = this.listeners(event);

    return Promise
      .bind(this)
      .return(listeners)
      .map(function performOp(listener) {
        return listener.apply(this, args);
      });
  }

  /**
   * Adds migrators
   */
  addMigrator(name, fn, ...args) {
    this._migrators[name] = partial(fn, ...args);
  }

  /**
   * Performs migration for a given database or throws if migrator is not present
   */
  migrate(name, ...args) {
    const migrate = this._migrators[name];
    assert(is.fn(migrate), `migrator ${name} not defined`);
    return migrate(...args);
  }

  /**
   * Defines convinience getters
   */
  _defineGetter(name) {
    Object.defineProperty(this, name, {
      __proto__: null,
      enumerable: true,
      configurable: false,
      get: () => this._get(name),
    });
  }

  /**
   *
   */
  _get(name) {
    const it = this[`_${name}`];
    if (it) {
      return it;
    }

    return this.emit('error', new Errors.NotPermittedError(`${name} was not initialized`));
  }

  /**
   * Generic connector for all of the plugins
   * @return {Promise}
   */
  connect() {
    return this._processAndEmit(this.getConnectors(), 'ready');
  }

  /**
   * Generic cleanup function
   * @return {Promise}
   */
  close() {
    return this._processAndEmit(this.getDesturctors(), 'close');
  }

  /**
   * Helper for calling funcs and emitting event after
   */
  _processAndEmit(collection, event) {
    return Promise
      .mapSeries(
        Mservice.ConnectorsPriority,
        (connectorType) => {
          const connectors = collection[connectorType];

          if (!connectors) {
            return [];
          }

          return Promise.map(connectors, func => func());
        }
      )
      .then(result => flatten(result))
      .tap(() => this.emit(event));
  }

  // ****************************** Plugin section: public ************************************

  /**
   * Public function to init plugins
   *
   * @param  {Object} mod
   * @param  {String} mod.name
   * @param  {Function} mod.attach
   */
  initPlugin(mod, conf) {
    const expose = mod.attach.call(this, conf || this._config[mod.name], __filename);

    if (!is.object(expose)) {
      return;
    }

    const { connect, close } = expose;
    const type = Mservice.ConnectorsTypes[mod.type];

    assert(type, 'Plugin type must be equal to one of connectors type');

    if (is.fn(connect)) {
      this.addConnector(type, connect);
    }

    if (is.fn(close)) {
      this.addDestructor(type, close);
    }
  }

  getConnectors() {
    return this[CONNECTORS_PROPERTY];
  }

  getDesturctors() {
    return this[DESTRUCTORS_PROPERTY];
  }

  addConnector(type, handler) {
    this._addHandler(CONNECTORS_PROPERTY, type, handler);
  }

  addDestructor(type, handler) {
    this._addHandler(DESTRUCTORS_PROPERTY, type, handler);
  }

  // ***************************** Plugin section: private **************************************

  _addHandler(property, type, handler) {
    if (this[property] === undefined) {
      this[property] = {};
    }

    if (this[property][type] === undefined) {
      this[property][type] = [];
    }

    this[property][type].push(handler);
  }

  /**
   * Initializes service plugins
   * @param  {Object} config
   */
  _initPlugins(config) {
    this._connectors = {};
    this._destructors = {};

    Mservice.PluginsPriority
      .forEach((pluginType) => {
        this._connectors[pluginType] = [];
        this._destructors[pluginType] = [];
      });

    // init plugins
    config.plugins.forEach((plugin) => {
      // eslint-disable-next-line import/no-dynamic-require
      this.initPlugin(require(`./plugins/${plugin}`));
    });

    this.emit('init');
  }

  /**
   * Notifies about errors when no other listeners are present
   * by throwing them
   * @param  {Object} err
   */
  _onError(err) {
    if (this.listeners('error').length > 1) {
      return;
    }

    throw err;
  }
}

module.exports = Mservice;
<|endoftext|>


<commit_before>'use strict';

var test = require('prova'),
  through = require('through'),
  fs = require('fs'),
  parse = require('../../streams/parse'),
  pivot = require('../../streams/pivot'),
  inferName = require('../../streams/infer_name');

// ---- Begin test cases ----
/* eslint-disable */

// Each test case is a jsdoc comment plus some example code. The test expectation
// for each test case is that the jsdoc description is equal to the inferred name.

// ExpressionStatement (comment attached here)
//   AssignmentExpression
//     MemberExpression
//     Identifier
/** expressionStatement1 */
exports.expressionStatement1 = test;

// ExpressionStatement
//   AssignmentExpression
//     MemberExpression (comment attached here)
//     FunctionExpression
/** expressionStatement2 */
exports.expressionStatement2 = function () {};

exports = {
  // Property (comment attached here)
  //   Identifier
  //   FunctionExpression
  /** property1 */
  property1: test,

  // Property
  //   Identifier (comment attached here)
  //   FunctionExpression
  /** property2 */
  property2: function () {}
};

/** function1 */
function function1() {}

/** function2 */
var function2 = function () {};

/**
* explicitName
* @name explicitName
*/
function implicitName() {}

/* eslint-enable */
// ---- End test cases ----

function check(comment) {
  var expected = comment.description,
    actual = comment.tags.name && comment.tags.name[ 0 ].name;

  test('inferName - ' + expected, function (t) {
    t.equal(actual, expected);
    t.end();
  });
}

var stream = parse();

stream
  .pipe(inferName())
  .pipe(pivot())
  .pipe(through(check));

stream.end({
  file: __filename,
  source: fs.readFileSync(__filename, 'utf8')
});
<commit_msg>Rewrite tests in the style of infer_membership<commit_after>'use strict';

var test = require('prova'),
  concat = require('concat-stream'),
  parse = require('../../streams/parse'),
  pivot = require('../../streams/pivot'),
  inferName = require('../../streams/infer_name');

function evaluate(fn, callback) {
  var stream = parse();

  stream
    .pipe(inferName())
    .pipe(pivot())
    .pipe(concat(callback));

  stream.end({
    file: __filename,
    source: '(' + fn.toString() + ')'
  });
}

test('inferName - expression statement', function (t) {
  evaluate(function () {
    // ExpressionStatement (comment attached here)
    //   AssignmentExpression
    //     MemberExpression
    //     Identifier
    /** Test */
    exports.name = test;
  }, function (result) {
    t.equal(result[ 0 ].tags.name[ 0 ].name, 'name');
    t.end();
  });
});

test('inferName - expression statement, function', function (t) {
  evaluate(function () {
    // ExpressionStatement
    //   AssignmentExpression
    //     MemberExpression (comment attached here)
    //     FunctionExpression
    /** Test */
    exports.name = function () {};
  }, function (result) {
    t.equal(result[ 0 ].tags.name[ 0 ].name, 'name');
    t.end();
  });
});

test('inferName - property', function (t) {
  evaluate(function () {
    exports = {
      // Property (comment attached here)
      //   Identifier
      //   FunctionExpression
      /** Test */
      name: test
    };
  }, function (result) {
    t.equal(result[ 0 ].tags.name[ 0 ].name, 'name');
    t.end();
  });
});

test('inferName - property, function', function (t) {
  evaluate(function () {
    exports = {
      // Property
      //   Identifier (comment attached here)
      //   FunctionExpression
      /** Test */
      name: function () {}
    };
  }, function (result) {
    t.equal(result[ 0 ].tags.name[ 0 ].name, 'name');
    t.end();
  });
});

test('inferName - function declaration', function (t) {
  evaluate(function () {
    /** Test */
    function name() {}
    return name;
  }, function (result) {
    t.equal(result[ 0 ].tags.name[ 0 ].name, 'name');
    t.end();
  });
});

test('inferName - anonymous function expression', function (t) {
  evaluate(function () {
    /** Test */
    var name = function () {};
    return name;
  }, function (result) {
    t.equal(result[ 0 ].tags.name[ 0 ].name, 'name');
    t.end();
  });
});

test('inferName - named function expression', function (t) {
  evaluate(function () {
    /** Test */
    var name = function name2() {};
    return name;
  }, function (result) {
    t.equal(result[ 0 ].tags.name[ 0 ].name, 'name');
    t.end();
  });
});

test('inferName - explicit name', function (t) {
  evaluate(function () {
    /** @name explicitName */
    function implicitName() {}
    return implicitName;
  }, function (result) {
    t.equal(result[ 0 ].tags.name[ 0 ].name, 'explicitName');
    t.end();
  });
});
<|endoftext|>


<commit_before>import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import reduxThunk from 'redux-thunk'

import App from './components/app'
import reducers from './reducers'

import './scss/index'

const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore)
const store = createStoreWithMiddleware(reducers)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>
  , document.getElementById('root'))
<commit_msg>Simplify store creation<commit_after>import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import reduxThunk from 'redux-thunk'

import App from './components/app'
import reducers from './reducers'

import './scss/index'

const store = createStore(reducers, applyMiddleware(reduxThunk))

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>
  , document.getElementById('root'))
<|endoftext|>


<commit_before>'use strict';

function User(userName) {
  this.userName = userName;
  this.rounds = 0;
  this.jackpots = 0;
  this.pairs = 0;
}

function convertObjectToUser(object) {
  var user = new User(object.userName);
  user.rounds = object.rounds;
  user.jackpots = object.jackpots;
  user.pairs = object.pairs;
  return user;
}

User.prototype.losses = function() {
  return this.rounds - this.jackpots + this.pairs;
};

var Data = {};

Data.loadCurrentUser = function() {
  var currentUserLoad =  localStorage.getItem('currentUser');
  var usersLoad = localStorage.getItem('users');
  if (currentUserLoad === null || usersLoad === null){
    return null;
  } else {
    return convertObjectToUser(JSON.parse(usersLoad) [currentUserLoad]);
  };
};

Data.saveUser = function(user) {
  localStorage.setItem('currentUser', user.userName);
  var usersToEdit = JSON.parse(localStorage.getItem('users'));
  if(usersToEdit === null) {
    usersToEdit = {};
  }
  usersToEdit[user.userName] = user;
  localStorage.setItem('users', JSON.stringify(usersToEdit));
};

Data.loadUser = function(user){
  return this.loadUserName(user.userName);
};

Data.loadUserName = function(userName) {
  var usersLoad =  localStorage.getItem('users');

  if (usersLoad === null){
    return null;
  } else {
    localStorage.setItem('currentUser', userName);
    return convertObjectToUser(JSON.parse(usersLoad)[userName]);
  };
};

Data.getAllUsers = function() {
  var allUsersLoad = JSON.parse(localStorage.getItem('users'));
  if (allUsersLoad === null) {
    return [];
  } else {
    var temp = [];
    for (var each in allUsersLoad) {
      temp.push(convertObjectToUser(allUsersLoad[each]));
    }
    return temp;
  }
};
<commit_msg>added method reset<commit_after>'use strict';

var initialBalance = 25;
var jackpotWorth = 3;
var pairWorth = 2;

function User(userName) {
  this.userName = userName;
  this.rounds = 0;
  this.jackpots = 0;
  this.pairs = 0;
}

function convertObjectToUser(object) {
  var user = new User(object.userName);
  user.rounds = object.rounds;
  user.jackpots = object.jackpots;
  user.pairs = object.pairs;
  return user;
}

User.prototype.resetStats = function(){
  this.jackpots = 0;
  this.pairs = 0;
  this.rounds = 0;
};

User.prototype.moneyBalance = function() {
  return (this.pairs * pairWorth + this.jackpots * jackpotWorth ) - this.losses() + initialBalance;
};

User.prototype.losses = function() {
  return this.rounds - (this.jackpots + this.pairs);
};

var Data = {};

Data.loadCurrentUser = function() {
  var currentUserLoad =  localStorage.getItem('currentUser');
  var usersLoad = localStorage.getItem('users');
  if (currentUserLoad === null || usersLoad === null){
    return null;
  } else {
    return convertObjectToUser(JSON.parse(usersLoad) [currentUserLoad]);
  };
};

Data.saveUser = function(user) {
  localStorage.setItem('currentUser', user.userName);
  var usersToEdit = JSON.parse(localStorage.getItem('users'));
  if(usersToEdit === null) {
    usersToEdit = {};
  }
  usersToEdit[user.userName] = user;
  localStorage.setItem('users', JSON.stringify(usersToEdit));
};

Data.loadUser = function(user){
  return this.loadUserName(user.userName);
};

Data.loadUserName = function(userName) {
  var usersLoad =  localStorage.getItem('users');

  if (usersLoad === null){
    return null;
  } else {
    localStorage.setItem('currentUser', userName);
    return convertObjectToUser(JSON.parse(usersLoad)[userName]);
  };
};

Data.getAllUsers = function() {
  var allUsersLoad = JSON.parse(localStorage.getItem('users'));
  if (allUsersLoad === null) {
    return [];
  } else {
    var temp = [];
    for (var each in allUsersLoad) {
      temp.push(convertObjectToUser(allUsersLoad[each]));
    }
    return temp;
  }
};
<|endoftext|>