// ## Token types

// The assignment of fine-grained, information-carrying type objects
// allows the tokenizer to store the information it has about a
// token in a way that is very cheap for the parser to look up.

// All token type variables start with an underscore, to make them
// easy to recognize.

// The `beforeExpr` property is used to disambiguate between regular
// expressions and divisions. It is set on all token types that can
// be followed by an expression (thus, a slash after them would be a
// regular expression).
//
// The `startsExpr` property is used to check if the token ends a
// `yield` expression. It is set on all token types that either can
// directly start an expression (like a quotation mark) or can
// continue an expression (like the body of a string).
//
// `isLoop` marks a keyword as starting a loop, which is important
// to know when parsing a label, in order to allow or disallow
// continue jumps to that label.

export class TokenType {
  constructor(label, conf = {}) {
    this.label = label
    this.keyword = conf.keyword
    this.beforeExpr = !!conf.beforeExpr
    this.startsExpr = !!conf.startsExpr
    this.isLoop = !!conf.isLoop
    this.isAssign = !!conf.isAssign
    this.prefix = !!conf.prefix
    this.postfix = !!conf.postfix
    this.binop = conf.binop || null
    this.updateContext = null
  }
}

function binop(name, prec) {
  return new TokenType(name, {beforeExpr: true, binop: prec})
}
const beforeExpr = {beforeExpr: true}, startsExpr = {startsExpr: true}

// Map keyword names to token types.

export const keywords = {}

// Succinct definitions of keyword token types
function kw(name, options = {}) {
  options.keyword = name
  return keywords[name] = new TokenType(name, options)
}

export const types = {
  num: new TokenType("num", startsExpr),
  regexp: new TokenType("regexp", startsExpr),
  string: new TokenType("string", startsExpr),
  name: new TokenType("name", startsExpr),
  eof: new TokenType("eof"),

  // Punctuation token types.
  bracketL: new TokenType("[", {beforeExpr: true, startsExpr: true}),
  bracketR: new TokenType("]"),
  braceL: new TokenType("{", {beforeExpr: true, startsExpr: true}),
  braceR: new TokenType("}"),
  parenL: new TokenType("(", {beforeExpr: true, startsExpr: true}),
  parenR: new TokenType(")"),
  comma: new TokenType(",", beforeExpr),
  semi: new TokenType(";", beforeExpr),
  colon: new TokenType(":", beforeExpr),
  dot: new TokenType("."),
  question: new TokenType("?", beforeExpr),
  arrow: new TokenType("=>", beforeExpr),
  template: new TokenType("template"),
  invalidTemplate: new TokenType("invalidTemplate"),
  ellipsis: new TokenType("...", beforeExpr),
  backQuote: new TokenType("`", startsExpr),
  dollarBraceL: new TokenType("${", {beforeExpr: true, startsExpr: true}),

  // Operators. These carry several kinds of properties to help the
  // parser use them properly (the presence of these properties is
  // what categorizes them as operators).
  //
  // `binop`, when present, specifies that this operator is a binary
  // operator, and will refer to its precedence.
  //
  // `prefix` and `postfix` mark the operator as a prefix or postfix
  // unary operator.
  //
  // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as
  // binary operators with a very low precedence, that should result
  // in AssignmentExpression nodes.

  eq: new TokenType("=", {beforeExpr: true, isAssign: true}),
  assign: new TokenType("_=", {beforeExpr: true, isAssign: true}),
  incDec: new TokenType("++/--", {prefix: true, postfix: true, startsExpr: true}),
  prefix: new TokenType("!/~", {beforeExpr: true, prefix: true, startsExpr: true}),
  logicalOR: binop("||", 1),
  logicalAND: binop("&&", 2),
  bitwiseOR: binop("|", 3),
  bitwiseXOR: binop("^", 4),
  bitwiseAND: binop("&", 5),
  equality: binop("==/!=/===/!==", 6),
  relational: binop("</>/<=/>=", 7),
  bitShift: binop("<</>>/>>>", 8),
  plusMin: new TokenType("+/-", {beforeExpr: true, binop: 9, prefix: true, startsExpr: true}),
  modulo: binop("%", 10),
  star: binop("*", 10),
  slash: binop("/", 10),
  starstar: new TokenType("**", {beforeExpr: true}),

  // Keyword token types.
  _break: kw("break"),
  _case: kw("case", beforeExpr),
  _catch: kw("catch"),
  _continue: kw("continue"),
  _debugger: kw("debugger"),
  _default: kw("default", beforeExpr),
  _do: kw("do", {isLoop: true, beforeExpr: true}),
  _else: kw("else", beforeExpr),
  _finally: kw("finally"),
  _for: kw("for", {isLoop: true}),
  _function: kw("function", startsExpr),
  _if: kw("if"),
  _return: kw("return", beforeExpr),
  _switch: kw("switch"),
  _throw: kw("throw", beforeExpr),
  _try: kw("try"),
  _var: kw("var"),
  _const: kw("const"),
  _while: kw("while", {isLoop: true}),
  _with: kw("with"),
  _new: kw("new", {beforeExpr: true, startsExpr: true}),
  _this: kw("this", startsExpr),
  _super: kw("super", startsExpr),
  _class: kw("class", startsExpr),
  _extends: kw("extends", beforeExpr),
  _export: kw("export"),
  _import: kw("import"),
  _null: kw("null", startsExpr),
  _true: kw("true", startsExpr),
  _false: kw("false", startsExpr),
  _in: kw("in", {beforeExpr: true, binop: 7}),
  _instanceof: kw("instanceof", {beforeExpr: true, binop: 7}),
  _typeof: kw("typeof", {beforeExpr: true, prefix: true, startsExpr: true}),
  _void: kw("void", {beforeExpr: true, prefix: true, startsExpr: true}),
  _delete: kw("delete", {beforeExpr: true, prefix: true, startsExpr: true})
}


// Reserved word lists for various dialects of the language

export const reservedWords = {
  3: "abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile",
  5: "class enum extends super const export import",
  6: "enum",
  strict: "implements interface let package private protected public static yield",
  strictBind: "eval arguments"
}

// And the keywords

const ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"

export const keywords = {
  5: ecma5AndLessKeywords,
  6: ecma5AndLessKeywords + " const class extends export import super"
}

export const keywordRelationalOperator = /^in(stanceof)?$/

// ## Character categories

// Big ugly regular expressions that match characters in the
// whitespace, identifier, and identifier-start categories. These
// are only applied when a character is found to actually have a
// code point above 128.
// Generated by `bin/generate-identifier-regex.js`.

let nonASCIIidentifierStartChars = "aab5bac0-d6d8-f6f8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u05d0-\u05ea\u05ef-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u0860-\u086a\u08a0-\u08b4\u08b6-\u08bd\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u09fc\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0af9\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c60\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e86-\u0e8a\u0e8c-\u0ea3\u0ea5\u0ea7-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1878\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c88\u1c90-\u1cba\u1cbd-\u1cbf\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1cfa\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fef\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7bf\ua7c2-\ua7c6\ua7f7-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab67\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"
let nonASCIIidentifierChars = "\u200c\u200db7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u07fd\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u08d3-\u08e1\u08e3-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u09fe\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0afa-\u0aff\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c04\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0d00-\u0d03\u0d3b\u0d3c\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0ebc\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf4\u1cf7-\u1cf9\u1dc0-\u1df9\u1dfb-\u1dff\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua620-\ua629\ua66f\ua674-\ua67d\ua69e\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua880\ua881\ua8b4-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f1\ua8ff-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"

const nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]")
const nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]")

nonASCIIidentifierStartChars = nonASCIIidentifierChars = null

// These are a run-length and offset encoded representation of the
// >0xffff code points that are a valid part of identifiers. The
// offset starts at 0x10000, and each pair of numbers represents an
// offset to the next range, and then a size of the range. They were
// generated by bin/generate-identifier-regex.js

// eslint-disable-next-line comma-spacing
const astralIdentifierStartCodes = [0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,14,29,6,37,11,29,3,35,5,7,2,4,43,157,19,35,5,35,5,39,9,51,157,310,10,21,11,7,153,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,66,18,2,1,11,21,11,25,71,55,7,1,65,0,16,3,2,2,2,28,43,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,56,50,14,50,14,35,477,28,11,0,9,21,155,22,13,52,76,44,33,24,27,35,30,0,12,34,4,0,13,47,15,3,22,0,2,0,36,17,2,24,85,6,2,0,2,3,2,14,2,9,8,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,19,0,13,4,159,52,19,3,21,0,33,47,21,1,2,0,185,46,42,3,37,47,21,0,60,42,14,0,72,26,230,43,117,63,32,0,161,7,3,38,17,0,2,0,29,0,11,39,8,0,22,0,12,45,20,0,35,56,264,8,2,36,18,0,50,29,113,6,2,1,2,37,22,0,26,5,2,1,2,31,15,0,328,18,270,921,103,110,18,195,2749,1070,4050,582,8634,568,8,30,114,29,19,47,17,3,32,20,6,18,689,63,129,74,6,0,67,12,65,1,2,0,29,6135,9,754,9486,286,50,2,18,3,9,395,2309,106,6,12,4,8,8,9,5991,84,2,70,2,1,3,0,3,1,3,3,2,11,2,0,2,6,2,64,2,3,3,7,2,6,2,27,2,3,2,4,2,0,4,6,2,339,3,24,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,7,2357,44,11,6,17,0,370,43,1301,196,60,67,8,0,1205,3,2,26,2,1,2,0,3,0,2,9,2,3,2,0,2,0,7,0,5,0,2,0,2,0,2,2,2,1,2,0,3,0,2,0,2,0,2,0,2,0,2,1,2,0,3,3,2,6,2,3,2,3,2,0,2,9,2,16,6,2,2,4,2,16,4421,42710,42,4148,12,221,3,5761,15,7472,3104,541]

// eslint-disable-next-line comma-spacing
const astralIdentifierCodes = [509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,574,3,9,9,525,10,176,2,54,14,32,9,16,3,46,10,54,9,7,2,37,13,2,9,6,1,45,0,13,2,49,13,9,3,4,9,83,11,7,0,161,11,6,9,7,3,56,1,2,6,3,1,3,2,10,0,11,1,3,6,4,4,193,17,10,9,5,0,82,19,13,9,214,6,3,8,28,1,83,16,16,9,82,12,9,9,84,14,5,9,243,14,166,9,232,6,3,6,4,0,29,9,41,6,2,3,9,0,10,10,47,15,406,7,2,7,17,9,57,21,2,13,123,5,4,0,2,1,2,6,2,0,9,9,49,4,2,1,2,4,9,9,330,3,19306,9,135,4,60,6,26,9,1014,0,2,54,8,3,19723,1,5319,4,4,5,9,7,3,6,31,3,149,2,1418,49,513,54,5,49,9,0,15,0,23,4,2,14,1361,6,2,16,3,6,2,1,2,4,262,6,10,9,419,13,1495,6,110,6,6,9,792487,239]

// This has a complexity linear to the value of the code. The
// assumption is that looking up astral identifier characters is
// rare.
function isInAstralSet(code, set) {
  let pos = 0x10000
  for (let i = 0; i < set.length; i += 2) {
    pos += set[i]
    if (pos > code) return false
    pos += set[i + 1]
    if (pos >= code) return true
  }
}

// Test whether a given character code starts an identifier.

export function isIdentifierStart(code, astral) {
  if (code < 65) return code === 36
  if (code < 91) return true
  if (code < 97) return code === 95
  if (code < 123) return true
  if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code))
  if (astral === false) return false
  return isInAstralSet(code, astralIdentifierStartCodes)
}

// Test whether a given character is part of an identifier.

export function isIdentifierChar(code, astral) {
  if (code < 48) return code === 36
  if (code < 58) return true
  if (code < 65) return false
  if (code < 91) return true
  if (code < 97) return code === 95
  if (code < 123) return true
  if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code))
  if (astral === false) return false
  return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes)
}


/**
 * Copyright IBM Corp. 2015, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

'use strict';

require('core-js/features/array/flat-map');

const { reporter } = require('@carbon/cli-reporter');
const { types: t, generate } = require('@carbon/scss-generator');
const fs = require('fs-extra');
const path = require('path');
const yaml = require('js-yaml');
const { formatTokenName, themes, tokens } = require('../lib');

const { colors: tokenColors } = tokens;
const FILE_BANNER = t.Comment(` Code generated by @carbon/themes. DO NOT EDIT.

 Copyright IBM Corp. 2018, 2018

 This source code is licensed under the Apache-2.0 license found in the
 LICENSE file in the root directory of this source tree.
`);
const SCSS_DIR = path.resolve(__dirname, '../scss');
const METADATA_FILE = path.resolve(__dirname, '../metadata.yml');
const MIXINS_ENTRYPOINT = path.join(SCSS_DIR, '_mixins.scss');
const TOKENS_ENTRYPOINT = path.join(SCSS_DIR, '_tokens.scss');
const MAPS_ENTRYPOINT = path.join(SCSS_DIR, '_theme-maps.scss');

const defaultTheme = 'white';
const defaultThemeMapName = 'carbon--theme';

async function build() {
  reporter.info('Building scss files for themes...');

  const metadata = transformMetadata(
    yaml.safeLoad(fs.readFileSync(METADATA_FILE, 'utf8'))
  );

  // Create maps for each theme:
  // $carbon--theme--name: (
  //   token-name: token-value
  // ) !default;
  const themeMaps = Object.keys(themes).flatMap(name => {
    const theme = themes[name];
    const comment = t.Comment(`/ Carbon's ${name} color theme
/ @type Map
/ @access public
/ @group @carbon/themes`);
    const variable = t.Assignment({
      id: t.Identifier(`carbon--theme--${name}`),
      init: t.SassMap({
        properties: Object.keys(theme).map(token =>
          t.SassMapProperty(
            t.Identifier(formatTokenName(token)),
            t.SassColor(theme[token])
          )
        ),
      }),
      default: true,
    });
    return [comment, variable];
  });

  // Create carbon--theme mixin, takes a theme as input and assigns all theme
  // variables using the `!global` flag before resetting at the end of the
  // function block
  const themeMixin = [
    t.Comment(`/ Define theme variables from a map of tokens
/ @access public
/ @param {Map} $theme [$${defaultThemeMapName}] - Map of theme tokens
/ @content Pass in your custom declaration blocks to be used after the token maps set theming variables.
/
/ @example scss
/   // Default usage
/   @include carbon--theme();
/
/   // Alternate styling (not white theme)
/   @include carbon--theme($carbon--theme--g90) {
/     // declarations...
/   }
/
/   // Inline styling
/   @include carbon--theme($carbon--theme--g90) {
/     .my-dark-theme {
/       // declarations...
/     }
/   }
/
/ @group @carbon/themes`),
    t.SassMixin({
      id: t.Identifier('carbon--theme'),
      params: [
        t.AssignmentPattern({
          left: t.Identifier('theme'),
          right: t.Identifier(defaultThemeMapName),
        }),
      ],
      body: t.BlockStatement({
        body: [
          ...tokenColors.map(token => {
            const name = formatTokenName(token);

            return t.Assignment({
              id: t.Identifier(name),
              init: t.CallExpression({
                callee: t.Identifier('map-get'),
                arguments: [t.Identifier('theme'), t.SassString(name)],
              }),
              global: true,
            });
          }),
          t.AtContent(),
          t.Comment(' Reset to default theme after apply in content'),
          t.IfStatement({
            test: t.LogicalExpression({
              left: t.Identifier('theme'),
              operator: '!=',
              right: t.Identifier(defaultThemeMapName),
            }),
            consequent: t.BlockStatement([
              t.SassMixinCall(t.Identifier('carbon--theme')),
            ]),
          }),
        ],
      }),
    }),
  ];

  const mixinsFile = t.StyleSheet([
    FILE_BANNER,
    t.SassImport('./theme-maps'),
    ...themeMixin,
  ]);

  const tokensFile = t.StyleSheet([
    FILE_BANNER,
    t.SassImport('./theme-maps'),
    ...tokenColors.flatMap(token => {
      const name = formatTokenName(token);
      const tokenData =
        (metadata.tokens &&
          metadata.tokens.find(tok => {
            return tok.name === token;
          })) ||
        {};

      return [
        tokenData.role && t.Comment(`/ ${tokenData.role.join('; ')}`),
        t.Comment(`/ @type Color
/ @access public
/ @group @carbon/themes`),
        tokenData.alias && t.Comment(`/ @alias ${tokenData.alias}`),
        tokenData.deprecated && t.Comment(`/ @deprecated`),
        t.Assignment({
          id: t.Identifier(name),
          init: t.CallExpression({
            callee: t.Identifier('map-get'),
            arguments: [t.Identifier(defaultThemeMapName), t.SassString(name)],
          }),
          default: true,
        }),
      ].filter(Boolean);
    }),
  ]);

  const themeMapsFile = t.StyleSheet([
    FILE_BANNER,
    ...themeMaps,
    t.Comment(`/ Carbon's default theme
/ @type Map
/ @access public
/ @alias carbon--theme--${defaultTheme}
/ @group @carbon/themes`),
    t.Assignment({
      id: t.Identifier(defaultThemeMapName),
      init: t.Identifier(`carbon--theme--${defaultTheme}`),
      default: true,
    }),
  ]);

  await fs.ensureDir(SCSS_DIR);
  await fs.writeFile(TOKENS_ENTRYPOINT, generate(tokensFile).code);
  await fs.writeFile(MIXINS_ENTRYPOINT, generate(mixinsFile).code);
  await fs.writeFile(MAPS_ENTRYPOINT, generate(themeMapsFile).code);

  reporter.success('Done! 🎉');
}

build().catch(error => {
  console.error(error);
});

/**
 * Transform token names to formats expected by Sassdoc for descriptions and
 * aliases
 * @param {Object} - token metadata
 * @return {Object} token metadata
 */
function transformMetadata(metadata) {
  const namesRegEx = new RegExp(
    metadata.tokens.map(token => token.name).join('|'),
    'g'
  );

  const replaceMap = {};
  metadata.tokens.map(token => {
    replaceMap[token.name] = formatTokenName(token.name);
  });

  metadata.tokens.forEach((token, i) => {
    // interactive01 to `$interactive-01`
    if (token.role) {
      token.role.forEach((role, j) => {
        metadata.tokens[i].role[j] = role.replace(namesRegEx, match => {
          return '`$' + replaceMap[match] + '`';
        });
      });
    }

    // brand01 to brand-01
    if (token.alias) {
      token.alias = formatTokenName(token.alias);
    }
  });

  return metadata;
}


/**
 * Copyright IBM Corp. 2018, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

'use strict';

const chalk = require('chalk');
const Table = require('cli-table');
const filesize = require('filesize');
const fs = require('fs-extra');
const path = require('path');
const glob = require('../glob');
const gzipSize = require('gzip-size');
const { reporter } = require('@carbon/cli-reporter');
const compile = require('../tools/compile');

async function measure(
  pattern,
  { cwd, entrypoint, monorepo = false, output, ignore = [] }
) {
  const outputFilename = 'results.json';
  const outputFilepath = output
    ? path.join(output, outputFilename)
    : path.join(cwd, outputFilename);
  reporter.info(`Running in: ${cwd}`);
  reporter.info(`Checking pattern: '${pattern}', ignoring: '${ignore}'`);
  reporter.info(`Writing results to: ${outputFilepath}`);

  const files = await glob(pattern, {
    cwd,
    ignore,
  });
  const defaultOptions = {
    includePaths: ['node_modules', '../../node_modules'],
  };

  const results = await Promise.all(
    compile(files.map(file => path.join(cwd, file)))
  );

  const errors = results.reduce((acc, result) => {
    if (result.error) {
      const error = result.error;
      error.filepath = result.filepath;
      return acc.concat(error);
    }
    return acc;
  }, []);

  if (errors.length > 0) {
    errors.forEach(error => {
      const { formatted, filepath } = error;
      reporter.error(`Error compiling ${path.relative(cwd, filepath)}`);
      console.log(chalk.gray(formatted));
    });
    process.exit(1);
    return;
  }

  // At this point, we've successfully compiled all of our scss files and now we
  // need to start getting the corresponding size values and compare them to
  // previous results, if they exist.

  const prevResults = (await fs.pathExists(outputFilepath))
    ? await fs.readJson(outputFilepath)
    : {};
  const nextResults = await Promise.all(
    results.map(async ({ result, filepath }) => ({
      filename: path.basename(filepath),
      package: await findPackageFor(filepath),
      size: Buffer.byteLength(result.css),
      gzip: await gzipSize(result.css),
    }))
  );

  await fs.writeJson(outputFilepath, nextResults, {
    spaces: 2,
  });

  console.log(printResults(prevResults, nextResults));
}

// Rough heuristic used to find the package name for a given file. Idea is to
// move upwards looking for directories that have a `package.json` file. Once we
// find one, we report back the name from that file.
async function findPackageFor(filepath) {
  let directory = filepath;

  while (directory !== '/') {
    const directoryToSearch = path.dirname(directory);
    const files = await fs.readdir(directoryToSearch);

    if (files.indexOf('package.json') !== -1) {
      const packageJson = await fs.readJson(
        path.join(directoryToSearch, 'package.json')
      );
      return packageJson.name;
    }

    directory = path.resolve(directory, '..');
  }

  throw new Error(`Unable to find package for: ${filepath}`);
}

function printResults(prevResults, results) {
  const resultsHeaders = [
    'Package',
    'File',
    'Prev Size',
    'Current Size',
    'Diff',
    'Prev Gzip',
    'Current Gzip',
    'Diff',
  ];

  const table = new Table({
    head: resultsHeaders.map(label => chalk.gray.yellow(label)),
  });

  results.forEach((result, index) => {
    const prevResult =
      prevResults.find(prevResult => {
        return (
          prevResult.filename === result.filename &&
          prevResult.package === result.package
        );
      }) || {};

    table.push([
      chalk.white.bold(result.package),
      chalk.white.bold(result.filename),
      chalk.gray.bold(prevResult.size || 0),
      chalk.white.bold(filesize(result.size)),
      percentChangeString(fractionalChange(prevResult.size, result.size)),
      chalk.gray.bold(prevResult.gzip || 0),
      chalk.white.bold(filesize(result.gzip)),
      percentChangeString(fractionalChange(prevResult.gzip, result.gzip)),
    ]);
  });

  return table.toString();
}

function fractionalChange(prev, current) {
  return (current - prev) / prev;
}

function percentChangeString(change) {
  if (!isFinite(change)) {
    // When a new package is created
    return 'n/a';
  }
  const formatted = (change * 100).toFixed(1);
  if (/^-|^0(?:\.0+)$/.test(formatted)) {
    return `${formatted}%`;
  } else {
    return `+${formatted}%`;
  }
}

module.exports = measure;


/**
 * Copyright IBM Corp. 2016, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

function svgToggleClass(svg, name, forceAdd) {
  const list = svg
    .getAttribute('class')
    .trim()
    .split(/\s+/);
  const uniqueList = Object.keys(list.reduce((o, item) => Object.assign(o, { [item]: 1 }), {}));
  const index = uniqueList.indexOf(name);
  const found = index >= 0;
  const add = forceAdd === undefined ? !found : forceAdd;

  if (found === !add) {
    if (add) {
      uniqueList.push(name);
    } else {
      uniqueList.splice(index, 1);
    }
    svg.setAttribute('class', uniqueList.join(' '));
  }
}

export default svgToggleClass;


/**
 * Copyright IBM Corp. 2018, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

'use strict';

const { reporter } = require('@carbon/cli-reporter');
const program = require('commander');
const path = require('path');
const packageJson = require('../package.json');
const bundlers = require('./bundlers');
const bundle = require('./commands/bundle');
const check = require('./commands/check');
const inline = require('./commands/inline');
const measure = require('./commands/measure');
const sassdoc = require('./commands/sassdoc');

async function bundler({ argv, cwd: getWorkingDirectory }) {
  const cwd = getWorkingDirectory();

  // prettier-ignore
  program
    .name(packageJson.name)
    .version(packageJson.version)
    .usage('<command> [options]');

  program
    .command('check <glob>')
    .description('check that each file can be compiled')
    .option('-i, --ignore <glob>', 'pass in a glob of files to ignore')
    .option('-l, --list', 'list all the files that were compiled')
    .action((pattern, cmd) =>
      check(pattern, {
        cwd,
        list: cmd.list || false,
        ignore: cmd.ignore || [],
      })
    );

  program
    .command('sassdoc <glob>')
    .description('generate sassdoc as markdown')
    .option('-i, --ignore <glob>', 'pass in a glob of files to ignore')
    .option('-j, --json', 'output as json file')
    .option('-o, --output <dir>', 'specify the directory to output the files')
    .action((pattern, cmd) =>
      sassdoc(pattern, {
        cwd,
        ignore: cmd.ignore || [],
        json: cmd.json || false,
        output: cmd.output || 'docs',
      })
    );

  program
    .command('inline')
    .description(
      'inline sass dependencies from package.json in a target folder'
    )
    .option(
      '-o, --output <dir>',
      'the directory to output inlined sass dependencies',
      'scss'
    )
    .action(cmd =>
      inline(cleanArgs(cmd), {
        cwd,
      })
    );

  program
    .command('measure <glob>')
    .description('measure the compiled size of your package(s)')
    .option('-i, --ignore <glob>', 'pass in a glob of files to ignore')
    .option('-o, --output <path>', 'specify the output path of your report')
    .action((pattern, cmd) =>
      measure(pattern, {
        cwd,
        ignore: cmd.ignore,
        output: cmd.output,
      })
    );

  program
    .command('bundle <entrypoint>')
    .description('bundle the given .js entrypoint')
    .option('-n, --name <name>', 'name the module for the UMD build')
    .option('-g, --globals <options>', 'global module names')
    .action((entrypoint, cmd) =>
      bundle(entrypoint, cleanArgs(cmd), {
        cwd,
      })
    );

  program
    .command('bundle:scss <entrypoint>')
    .description('bundle the given .scss entrypoint')
    .option('-n, --name <name>', 'name the output file')
    .option(
      '-o, --output <dir>',
      'specify the directory to output the files',
      'css'
    )
    .action((entrypoint, cmd) =>
      bundle(entrypoint, cleanArgs(cmd), {
        cwd,
      })
    );

  program.parse(argv);
}

// Inspired by Vue CLI:
// https://github.com/vuejs/vue-cli/blob/31e1b4995edef3d2079da654deedffe002a1d689/packages/%40vue/cli/bin/vue.js#L172
function cleanArgs(command) {
  return command.options.reduce((acc, option) => {
    // TODO: add case for reserved words from commander, like options

    // Add case for mapping `--foo-bar` to `fooBar`
    const key = option.long
      .replace(/^--/, '')
      .split('-')
      .map((word, i) => {
        if (i === 0) {
          return word;
        }
        return word[0].toUpperCase() + word.slice(1);
      })
      .join('');

    // If an option is not present and Command has a method with the same name
    // it should not be copied
    if (typeof command[key] !== 'function') {
      return {
        ...acc,
        [key]: command[key],
      };
    }
    return acc;
  }, {});
}

module.exports = bundler;


/**
 * Copyright IBM Corp. 2018, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

async function flatMapAsync(source, mapFn) {
  const results = await Promise.all(source.map(mapFn));
  return results.reduce((acc, result) => acc.concat(result), []);
}

module.exports = {
  flatMapAsync,
};


-/**
 * Copyright IBM Corp. 2018, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

'use strict';

const prettier = require('prettier');
const sassdoc = require('sassdoc');
const toc = require('markdown-toc');

const prettierOptions = {
  parser: 'markdown',
  printWidth: 80,
  singleQuote: true,
  trailingComma: 'es5',
};

/**
 * Custom slugify for markdown-toc to not include escaped emoji characters
 * @param {string} title - the anchor link
 */
const slugify = title => {
  return [...toc.slugify(title)].reduce((acc, ch) => {
    if (ch.charCodeAt(0) > 255) {
      return acc;
    }
    return acc + ch;
  }, '');
};

/**
 * Create a JSON file of documented Sass items
 * @see {@link http://sassdoc.com/configuration/|Sassdoc configuration}
 * @param {string} sourceDir - source directory
 * @param {Object} config - configuration object
 * @return {Object} json object
 */
async function createJson(sourceDir, config) {
  config = config || {};

  return sassdoc.parse(sourceDir, config).then(
    data => {
      return data;
    },
    err => {
      console.error(err);
    }
  );
}

/**
 * Remove duplicate objects in `require` and `usedBy` arrays. Array objects have
 * `name` and `type` properties, sometimes nested in a `context` object.
 * @param {Array} arr - array with potential duplicates
 * @return {Array} deduped array
 */
function dedupeArray(arr) {
  return arr.reduce(
    (p, item) => {
      const type = item.type || item.context.type;
      const name = item.name || item.context.name;
      const id = [type, name].join('|');

      if (p.temp.indexOf(id) === -1) {
        p.out.push(item);
        p.temp.push(id);
      }
      return p;
    },
    { temp: [], out: [] }
  ).out;
}

/**
 * Create a unique Sassdoc item name
 * @param {string} name - Sassdoc name
 * @param {string} type - Sassdoc type (e.g. `variable`, `mixin`)
 * @return {string} unique Sassdoc item name
 */
function createUniqueName(name, type) {
  return `${name} [${type}]`;
}

/**
 * Create a standardized group name
 * @param {Array} group - Item's group
 * @return {string} group name
 */
function createGroupName(group) {
  return !group || !group[0] || group[0] === 'undefined' ? 'general' : group[0];
}

/**
 * Create GitHub-flavored markdown anchor link
 * @param {string} name - anchor value
 * @param {string} heading - anchor link destination
 * @return {string} markdown anchor
 */
function createAnchorLink(name, heading) {
  const anchorLink = heading
    .toLowerCase()
    .replace(/ /g, '-')
    .replace(/[`~!@#$%^&*()+=<>?,./:;"'|{}\[\]\\–—]/g, '')
    .replace(
      /[　。？！，、；：“”【】（）〔〕［］﹃﹄“”‘’﹁﹂—…－～《》〈〉「」]/g,
      ''
    );

  return `[${name}](#${anchorLink})`;
}

/**
 * Create markdown for Sassdoc item (function, mixin, placeholder, variable)
 * @param {string} item - Sassdoc item
 * @return {string} item in markdown formatting
 */
function createMarkdownItem(item) {
  let str = '';

  if (!item.context) return '';

  let status = item.access === 'public' ? '✅' : '❌';

  if (item.deprecated || item.deprecated === '') {
    status += '⚠';
  }

  // Name
  str += `\n\n### ${status}${createUniqueName(
    item.context.name,
    item.context.type
  )}`;

  // Description
  if (item.description) {
    str += `\n\n${item.description.trim()}`;
  }

  // Value (variables)
  if (item.context.value) {
    str += `

Source code

\`\`\`scss
$${item.context.name}: ${item.context.value};
\`\`\`
`;
  }

  // Code (mixins)
  if (item.context.code) {
    let paramStr = '';

    if (item.parameter) {
      item.parameter.forEach(param => {
        if (paramStr) paramStr += `, `;
        paramStr += `$${param.name}`;
        if (param.default) paramStr += `: ${param.default}`;
      });
    }

    str += `

Source code

\`\`\`scss
@${item.context.type} ${item.context.name}(${paramStr}) {${item.context.code}}
\`\`\`
`;
  }

  // Parameters
  if (item.parameter && item.parameter.length) {
    str += `

- **Parameters**:

| Name | Description | Type | Default value |
| --- | --- | --- | --- |`;

    item.parameter.forEach(param => {
      const paramType = param.type
        ? `\`${param.type.replace(/\|/g, `\\|`)}\``
        : '—';
      const paramDefault = param.default ? `\`${param.default}\`` : '—';

      const row = `\n| \`$${param.name}\` | ${param.description ||
        '—'} | ${paramType} | ${paramDefault} |`;

      str += row;
    });
  }

  // Example
  if (item.example && item.example.length) {
    str += `\n\n**Example**:`;

    if (item.example[0].description) {
      str += ` ${item.example[0].description}`;
    }

    str += `

Example code

\`\`\`${item.example[0].type}
${item.example[0].code}
\`\`\`
`;
  }

  // Bullets
  const metadata = [];

  const groupName = createGroupName(item.group);

  metadata.push({
    key: 'Group',
    value: createAnchorLink(groupName, groupName),
  });

  if (item.return) {
    metadata.push({
      key: 'Returns',
      value: `\`${item.return.type}\` ${item.return.description || ''}`,
    });
  }

  if (item.type) {
    metadata.push({
      key: 'Type',
      value: `\`${item.type}\``,
    });
  }

  if (item.alias) {
    metadata.push({
      key: 'Alias',
      value: `\`${item.alias}\``,
    });
  }

  if (item.aliased) {
    let subbullets = '';

    item.aliased.forEach(aliased => {
      subbullets += `\n  - \`${aliased}\``;
    });

    metadata.push({
      key: 'Aliased',
      value: subbullets,
    });
  }

  if (item.content) {
    metadata.push({
      key: 'Content',
      value: item.content,
    });
  }

  if (item.require && item.require.length) {
    let subbullets = '';

    dedupeArray(item.require).forEach(requires => {
      subbullets += `\n   - ${createAnchorLink(
        `${requires.name} [${requires.type}]`,
        createUniqueName(requires.name, requires.type)
      )}`;
    });

    metadata.push({
      key: 'Requires',
      value: subbullets,
    });
  }

  if (item.usedBy && item.usedBy.length) {
    let subbullets = '';

    dedupeArray(item.usedBy).forEach(usedBy => {
      subbullets += `\n   - ${createAnchorLink(
        `${usedBy.context.name} [${usedBy.context.type}]`,
        createUniqueName(usedBy.context.name, usedBy.context.type)
      )}`;
    });

    metadata.push({
      key: 'Used by',
      value: subbullets,
    });
  }

  // if (item.since && item.since.length) {
  //   metadata.push({
  //     key: 'Since',
  //     value: item.since[0].version,
  //   });
  // }

  if (item.link && item.link.length) {
    let subbullets = '';

    item.link.forEach(link => {
      subbullets += `\n   - [${link.caption || 'Link'}](${link.url})`;
    });

    metadata.push({
      key: 'Links',
      value: subbullets,
    });
  }

  if (item.deprecated || item.deprecated === '') {
    metadata.push({
      key: 'Deprecated',
      value: item.deprecated || 'This may not be available in future releases',
    });
  }

  if (metadata.length) {
    str += '\n';

    metadata.forEach(meta => {
      str += `\n- **${meta.key}**: ${meta.value}`;
    });
  }

  return str;
}

/**
 * Create a markdown file of documented Sass items
 * @see {@link http://sassdoc.com/configuration/|Sassdoc configuration}
 * @param {string} sourceDir - source directory
 * @param {Object} config - configuration object
 * @return {string} markdown
 */
async function createMarkdown(sourceDir, config) {
  config = config || {};

  return sassdoc.parse(sourceDir, config).then(
    data => {
      let markdownFile = '';

      const documentedItems = data.filter(
        (item, index) => item.access === 'public' || item.access === 'private'
      );

      markdownFile += `# Sass API

| Mark | Description |
| --- | --- |
| ✅ | Public functions, mixins, placeholders, and variables |
| ❌ | Private items - not supported outside package's build |
| ⚠ | Deprecated items - may not be available in future releases |

`;

      let currentGroup = '';

      documentedItems.forEach(item => {
        const itemGroup = createGroupName(item.group);

        if (itemGroup !== currentGroup) {
          markdownFile += `\n\n## ${itemGroup}`;
          currentGroup = itemGroup;
        }

        markdownFile += createMarkdownItem(item);
      });

      return prettier.format(
        toc.insert(markdownFile, { slugify }),
        prettierOptions
      );
    },
    err => {
      console.error(err);
    }
  );
}

module.exports = {
  createJson,
  createMarkdown,
};


/**
 * Copyright IBM Corp. 2018, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

'use strict';

const { reporter } = require('@carbon/cli-reporter');
const fs = require('fs-extra');
const path = require('path');
const spawn = require('cross-spawn');

const PACKAGES_DIR = path.resolve(__dirname, '../packages');
const BUILD_DIR = path.resolve(__dirname, '../build');

const IGNORE_EXAMPLE_DIRS = new Set([
  'styled-components',
  'vue-cli',
  'storybook',
]);

/**
 * The goal here is to create a top-level `build` folder with content to be
 * displayed in the `gh-pages` branch. Specifically we want packages available
 * at: `packages/<package-name>/examples/<example-name>` to be mirrored over in
 * the `build` folder at: `build/<package-name>/examples/<example-name>`.
 */
async function main() {
  reporter.info('Building examples...');

  await fs.remove(BUILD_DIR);
  await fs.ensureDir(BUILD_DIR);

  const packageNames = await fs.readdir(PACKAGES_DIR);

  const packages = await Promise.all(
    packageNames.map(async name => {
      // Verify that each file that we read from the packages directory is
      // actually a folder. Typically used to catch `.DS_store` files that
      // accidentally appear when opening with MacOS Finder
      const filepath = path.join(PACKAGES_DIR, name);
      const stat = await fs.lstat(filepath);
      const descriptor = {
        filepath,
        name,
      };
      if (!stat.isDirectory()) {
        throw new Error(`Unexpected file: ${name} at ${filepath}`);
      }

      // Try and figure out if the package has an examples directory, if not
      // then we can skip it
      const examplesDir = path.join(filepath, 'examples');
      if (!(await fs.pathExists(examplesDir))) {
        return descriptor;
      }

      const examples = (await fs.readdir(examplesDir)).filter(example => {
        return example !== '.yarnrc' && !IGNORE_EXAMPLE_DIRS.has(example);
      });

      return {
        ...descriptor,
        examples: examples.map(name => ({
          filepath: path.join(examplesDir, name),
          name,
        })),
      };
    })
  );

  const packagesWithExamples = packages.filter(
    pkg => Array.isArray(pkg.examples) && pkg.examples.length !== 0
  );

  await Promise.all(
    packagesWithExamples.map(async pkg => {
      reporter.info(`Building examples in package \`${pkg.name}\``);

      const { examples, filepath, name } = pkg;
      const packageDir = path.join(BUILD_DIR, name, 'examples');

      await fs.ensureDir(packageDir);

      await Promise.all(
        examples.map(async example => {
          reporter.info(
            `Building example \`${example.name}\` in package \`${pkg.name}\``
          );

          const exampleDir = path.join(packageDir, example.name);
          const exampleBuildDir = path.join(example.filepath, 'build');
          const packageJsonPath = path.join(example.filepath, 'package.json');
          const packageJson = await fs.readJson(packageJsonPath);

          await fs.ensureDir(exampleDir);

          if (packageJson.scripts.build) {
            spawn.sync('yarn', ['install'], {
              stdio: 'ignore',
              cwd: example.filepath,
            });
            spawn.sync('yarn', ['build'], {
              stdio: 'ignore',
              cwd: example.filepath,
            });
          }

          if (await fs.pathExists(exampleBuildDir)) {
            await fs.copy(exampleBuildDir, exampleDir);
            return;
          }

          await fs.copy(example.filepath, exampleDir, {
            filter(src, dest) {
              const relativePath = path.relative(example.filepath, src);
              if (relativePath.includes('node_modules')) {
                return false;
              }
              if (relativePath[0] === '.') {
                return false;
              }
              return true;
            },
          });
          reporter.success(
            `Built example \`${example.name}\` in package \`${pkg.name}\``
          );
        })
      );

      reporter.success(`Built examples in package \`${pkg.name}\``);
    })
  );

  const links = packagesWithExamples.reduce((html, pkg) => {
    const links = pkg.examples.reduce((acc, example) => {
      const href = `./${pkg.name}/examples/${example.name}/`;
      return acc + `<li><a href="${href}">${example.name}</a></li>`;
    }, '');

    return (
      html +
      '\n' +
      `

@carbon/${pkg.name}

    ${links}

`
    );
  }, '');

  const indexFile = `
<!DOCTYPE html>

  <meta charset="UTF-8">
  <link href="https://fonts.googleapis.com/css?family=IBM+Plex+Mono" rel="stylesheet">
  <title>Carbon Elements</title>

${links}

`;

  await fs.writeFile(path.join(BUILD_DIR, 'index.html'), indexFile);

  // Copy icons over, useful for adding download links
  await fs.copy(
    path.resolve(__dirname, '../packages/icons/svg'),
    path.join(BUILD_DIR, 'icons/svg')
  );
}

main().catch(error => {
  console.log(error);
});


/**
 * Copyright IBM Corp. 2016, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

import Flatpickr from 'flatpickr';
import settings from '../../globals/js/settings';
import mixin from '../../globals/js/misc/mixin';
import createComponent from '../../globals/js/mixins/create-component';
import initComponentBySearch from '../../globals/js/mixins/init-component-by-search';
import handles from '../../globals/js/mixins/handles';
import on from '../../globals/js/misc/on';

/* eslint no-underscore-dangle: [2, { "allow": ["_input", "_updateClassNames", "_updateInputFields"], "allowAfterThis": true }] */

// `this.options` create-component mix-in creates prototype chain
// so that `options` given in constructor argument wins over the one defined in static `options` property
// 'Flatpickr' wants flat structure of object instead

function flattenOptions(options) {
  const o = {};
  // eslint-disable-next-line guard-for-in, no-restricted-syntax
  for (const key in options) {
    o[key] = options[key];
  }
  return o;
}

// Weekdays shorthand for english locale
Flatpickr.l10ns.en.weekdays.shorthand.forEach((day, index) => {
  const currentDay = Flatpickr.l10ns.en.weekdays.shorthand;
  if (currentDay[index] === 'Thu' || currentDay[index] === 'Th') {
    currentDay[index] = 'Th';
  } else {
    currentDay[index] = currentDay[index].charAt(0);
  }
});

const toArray = arrayLike => Array.prototype.slice.call(arrayLike);

class DatePicker extends mixin(createComponent, initComponentBySearch, handles) {
  /**
   * DatePicker.
   * @extends CreateComponent
   * @extends InitComponentBySearch
   * @extends Handles
   * @param {HTMLElement} element The element working as an date picker.
   */
  constructor(element, options) {
    super(element, options);
    const type = this.element.getAttribute(this.options.attribType);
    this.calendar = this._initDatePicker(type);
    if (this.calendar.calendarContainer) {
      this.manage(
        on(this.element, 'keydown', e => {
          if (e.which === 40) {
            this.calendar.calendarContainer.focus();
          }
        })
      );
      this.manage(
        on(this.calendar.calendarContainer, 'keydown', e => {
          if (e.which === 9 && type === 'range') {
            this._updateClassNames(this.calendar);
            this.element.querySelector(this.options.selectorDatePickerInputFrom).focus();
          }
        })
      );
    }
  }

  /**
   * Opens the date picker dropdown when this component gets focus.
   * Used only for range mode for now.
   * @private
   */
  _handleFocus = () => {
    if (this.calendar) {
      this.calendar.open();
    }
  };

  /**
   * Closes the date picker dropdown when this component loses focus.
   * Used only for range mode for now.
   * @private
   */
  _handleBlur = event => {
    if (this.calendar) {
      const focusTo = event.relatedTarget;
      if (
        !focusTo ||
        (!this.element.contains(focusTo) &&
          (!this.calendar.calendarContainer || !this.calendar.calendarContainer.contains(focusTo)))
      ) {
        this.calendar.close();
      }
    }
  };

  _initDatePicker = type => {
    if (type === 'range') {
      // Given FlatPickr assumes one `<input>` even in range mode,
      // use a hidden `<input>` for such purpose, separate from our from/to `<input>`s
      const doc = this.element.ownerDocument;
      const rangeInput = doc.createElement('input');
      rangeInput.className = this.options.classVisuallyHidden;
      rangeInput.setAttribute('aria-hidden', 'true');
      this.element.appendChild(rangeInput);
      this._rangeInput = rangeInput;

      // An attempt to open the date picker dropdown when this component gets focus,
      // and close the date picker dropdown when this component loses focus
      const w = doc.defaultView;
      const hasFocusin = 'onfocusin' in w;
      const hasFocusout = 'onfocusout' in w;
      const focusinEventName = hasFocusin ? 'focusin' : 'focus';
      const focusoutEventName = hasFocusout ? 'focusout' : 'blur';
      this.manage(on(this.element, focusinEventName, this._handleFocus, !hasFocusin));
      this.manage(on(this.element, focusoutEventName, this._handleBlur, !hasFocusout));
      this.manage(
        on(this.element.querySelector(this.options.selectorDatePickerIcon), focusoutEventName, this._handleBlur, !hasFocusout)
      );
    }
    const self = this;
    const date = type === 'range' ? this._rangeInput : this.element.querySelector(this.options.selectorDatePickerInput);
    const { onClose, onChange, onMonthChange, onYearChange, onOpen, onValueUpdate } = this.options;
    const calendar = new Flatpickr(
      date,
      Object.assign(flattenOptions(this.options), {
        allowInput: true,
        mode: type,
        positionElement: type === 'range' && this.element.querySelector(this.options.selectorDatePickerInputFrom),
        onClose(selectedDates, ...remainder) {
          // An attempt to disable Flatpickr's focus tracking system,
          // which has adverse effect with our old set up with two `<input>`s or our latest setup with a hidden `<input>`
          if (self.shouldForceOpen) {
            if (self.calendar.calendarContainer) {
              self.calendar.calendarContainer.classList.add('open');
            }
            self.calendar.isOpen = true;
          }
          if (!onClose || onClose.call(this, selectedDates, ...remainder) !== false) {
            self._updateClassNames(calendar);
            self._updateInputFields(selectedDates, type);
          }
        },
        onChange(...args) {
          if (!onChange || onChange.call(this, ...args) !== false) {
            self._updateClassNames(calendar);
            if (type === 'range') {
              if (calendar.selectedDates.length === 1 && calendar.isOpen) {
                self.element.querySelector(self.options.selectorDatePickerInputTo).classList.add(self.options.classFocused);
              } else {
                self.element.querySelector(self.options.selectorDatePickerInputTo).classList.remove(self.options.classFocused);
              }
            }
          }
        },
        onMonthChange(...args) {
          if (!onMonthChange || onMonthChange.call(this, ...args) !== false) {
            self._updateClassNames(calendar);
          }
        },
        onYearChange(...args) {
          if (!onYearChange || onYearChange.call(this, ...args) !== false) {
            self._updateClassNames(calendar);
          }
        },
        onOpen(...args) {
          // An attempt to disable Flatpickr's focus tracking system,
          // which has adverse effect with our old set up with two `<input>`s or our latest setup with a hidden `<input>`
          self.shouldForceOpen = true;
          setTimeout(() => {
            self.shouldForceOpen = false;
          }, 0);
          if (!onOpen || onOpen.call(this, ...args) !== false) {
            self._updateClassNames(calendar);
          }
        },
        onValueUpdate(...args) {
          if ((!onValueUpdate || onValueUpdate.call(this, ...args) !== false) && type === 'range') {
            self._updateInputFields(self.calendar.selectedDates, type);
          }
        },
        nextArrow: this._rightArrowHTML(),
        prevArrow: this._leftArrowHTML(),
      })
    );
    if (type === 'range') {
      this._addInputLogic(this.element.querySelector(this.options.selectorDatePickerInputFrom), 0);
      this._addInputLogic(this.element.querySelector(this.options.selectorDatePickerInputTo), 1);
    }
    this.manage(
      on(this.element.querySelector(this.options.selectorDatePickerIcon), 'click', () => {
        calendar.open();
      })
    );
    this._updateClassNames(calendar);
    if (type !== 'range') {
      this._addInputLogic(date);
    }
    return calendar;
  };

  _rightArrowHTML() {
    return `
      <svg
        focusable="false"
        preserveAspectRatio="xMidYMid meet"
        style="will-change: transform;"
        xmlns="http://www.w3.org/2000/svg"
        width="16"
        height="16"
        viewBox="0 0 16 16"
        aria-hidden="true">
          <path d="M11 8l-5 5-.7-.7L9.6 8 5.3 3.7 6 3z"></path>
      </svg>`;
  }

  _leftArrowHTML() {
    return `
      <svg
        focusable="false"
        preserveAspectRatio="xMidYMid meet"
        style="will-change: transform;"
        xmlns="http://www.w3.org/2000/svg"
        width="16"
        height="16"
        viewBox="0 0 16 16"
        aria-hidden="true"
      >
        <path d="M5 8l5-5 .7.7L6.4 8l4.3 4.3-.7.7z"></path>
      </svg>`;
  }

  _addInputLogic = (input, index) => {
    if (!isNaN(index) && (index < 0 || index > 1)) {
      throw new RangeError(`The index of <input> (${index}) is out of range.`);
    }
    const inputField = input;
    this.manage(
      on(inputField, 'change', evt => {
        if (evt.isTrusted || (evt.detail && evt.detail.isNotFromFlatpickr)) {
          const inputDate = this.calendar.parseDate(inputField.value);
          if (inputDate && !isNaN(inputDate.valueOf())) {
            if (isNaN(index)) {
              this.calendar.setDate(inputDate);
            } else {
              const { selectedDates } = this.calendar;
              selectedDates[index] = inputDate;
              this.calendar.setDate(selectedDates);
            }
          }
        }
        this._updateClassNames(this.calendar);
      })
    );
    // An attempt to temporarily set the `<input>` being edited as the one FlatPicker manages,
    // as FlatPicker attempts to take over `keydown` event handler on `document` to run on the date picker dropdown.
    this.manage(
      on(inputField, 'keydown', evt => {
        const origInput = this.calendar._input;
        this.calendar._input = evt.target;
        setTimeout(() => {
          this.calendar._input = origInput;
        });
      })
    );
  };

  _updateClassNames = ({ calendarContainer, selectedDates }) => {
    if (calendarContainer) {
      calendarContainer.classList.add(this.options.classCalendarContainer);
      calendarContainer.querySelector('.flatpickr-month').classList.add(this.options.classMonth);
      calendarContainer.querySelector('.flatpickr-weekdays').classList.add(this.options.classWeekdays);
      calendarContainer.querySelector('.flatpickr-days').classList.add(this.options.classDays);
      toArray(calendarContainer.querySelectorAll('.flatpickr-weekday')).forEach(item => {
        const currentItem = item;
        currentItem.innerHTML = currentItem.innerHTML.replace(/\s+/g, '');
        currentItem.classList.add(this.options.classWeekday);
      });
      toArray(calendarContainer.querySelectorAll('.flatpickr-day')).forEach(item => {
        item.classList.add(this.options.classDay);
        if (item.classList.contains('today') && selectedDates.length > 0) {
          item.classList.add('no-border');
        } else if (item.classList.contains('today') && selectedDates.length === 0) {
          item.classList.remove('no-border');
        }
      });
    }
  };

  _updateInputFields = (selectedDates, type) => {
    if (type === 'range') {
      if (selectedDates.length === 2) {
        this.element.querySelector(this.options.selectorDatePickerInputFrom).value = this._formatDate(selectedDates[0]);
        this.element.querySelector(this.options.selectorDatePickerInputTo).value = this._formatDate(selectedDates[1]);
      } else if (selectedDates.length === 1) {
        this.element.querySelector(this.options.selectorDatePickerInputFrom).value = this._formatDate(selectedDates[0]);
      }
    } else if (selectedDates.length === 1) {
      this.element.querySelector(this.options.selectorDatePickerInput).value = this._formatDate(selectedDates[0]);
    }
    this._updateClassNames(this.calendar);
  };

  _formatDate = date => this.calendar.formatDate(date, this.calendar.config.dateFormat);

  release() {
    if (this._rangeInput && this._rangeInput.parentNode) {
      this._rangeInput.parentNode.removeChild(this._rangeInput);
    }
    if (this.calendar) {
      try {
        this.calendar.destroy();
      } catch (err) {} // eslint-disable-line no-empty
      this.calendar = null;
    }
    return super.release();
  }

  /**
   * The component options.
   * If `options` is specified in the constructor,
   * {@linkcode DatePicker.create .create()}, or {@linkcode DatePicker.init .init()},
   * properties in this object are overriden for the instance being create and how {@linkcode DatePicker.init .init()} works.
   * @property {string} selectorInit The CSS selector to find date picker UIs.
   */
  static get options() {
    const { prefix } = settings;
    return {
      selectorInit: '[data-date-picker]',
      selectorDatePickerInput: '[data-date-picker-input]',
      selectorDatePickerInputFrom: '[data-date-picker-input-from]',
      selectorDatePickerInputTo: '[data-date-picker-input-to]',
      selectorDatePickerIcon: '[data-date-picker-icon]',
      classCalendarContainer: `${prefix}--date-picker__calendar`,
      classMonth: `${prefix}--date-picker__month`,
      classWeekdays: `${prefix}--date-picker__weekdays`,
      classDays: `${prefix}--date-picker__days`,
      classWeekday: `${prefix}--date-picker__weekday`,
      classDay: `${prefix}--date-picker__day`,
      classFocused: `${prefix}--focused`,
      classVisuallyHidden: `${prefix}--visually-hidden`,
      attribType: 'data-date-picker-type',
      dateFormat: 'm/d/Y',
    };
  }

  /**
   * The map associating DOM element and date picker UI instance.
   * @type {WeakMap}
   */
  static components /* #__PURE_CLASS_PROPERTY__ */ = new WeakMap();
}

export default DatePicker;


/* eslint-disable newline-before-return */
const { barba } = window;
const list = document.querySelector('[data-test="hooks-list"]');

/**
 * Append list item
 *
 * @param {*} str List item content
 * @param {string} prefix Prefix for global
 * @returns {void}
 */
function append(str, prefix = '') {
  const item = document.createElement('li');

  item.textContent = prefix + str;
  list.appendChild(item);
}

/**
 * Promisify hooks
 *
 * @returns {Promise} Promise
 */
function sleep() {
  return new Promise(resolve => {
    setTimeout(resolve, 100);
  });
}

export const base = {
  beforeAppear() {
    append('beforeAppear');
    return sleep();
  },
  appear() {
    append('appear');
    return sleep();
  },
  afterAppear() {
    append('afterAppear');
    return sleep();
  },
  before() {
    append('before');
    return sleep();
  },
  beforeLeave() {
    append('beforeLeave');
    return sleep();
  },
  leave() {
    append('leave');
    return sleep();
  },
  afterLeave() {
    append('afterLeave');
    return sleep();
  },
  beforeEnter() {
    append('beforeEnter');
    return sleep();
  },
  enter() {
    append('enter');
    return sleep();
  },
  afterEnter() {
    append('afterEnter');
    return sleep();
  },
  after() {
    append('after');
    return sleep();
  },
};

barba.hooks.beforeAppear(() => {
  append('beforeAppear', 'global:');
});
barba.hooks.appear(() => {
  append('appear', 'global:');
});
barba.hooks.afterAppear(() => {
  append('afterAppear', 'global:');
});
barba.hooks.before(() => {
  append('before', 'global:');
});
barba.hooks.beforeLeave(() => {
  append('beforeLeave', 'global:');
});
barba.hooks.leave(() => {
  append('leave', 'global:');
});
barba.hooks.afterLeave(() => {
  append('afterLeave', 'global:');
});
barba.hooks.beforeEnter(() => {
  append('beforeEnter', 'global:');
});
barba.hooks.enter(() => {
  append('enter', 'global:');
});
barba.hooks.afterEnter(() => {
  append('afterEnter', 'global:');
});
barba.hooks.after(() => {
  append('after', 'global:');
});

export const hooks = {
  ...base,
  custom: ({ trigger }) =>
    trigger === 'barba' ||
    (trigger.dataset && trigger.dataset.test === 'link.hooks'),
};

export const hooksSync = {
  ...base,
  sync: true,
  custom: ({ trigger }) =>
    trigger.dataset && trigger.dataset.test === 'link.hooks-sync',
};