[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: scottrogowski\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n__pycache__\nout.*\n.coverage*\nhtmlcov\nbuild/\n*.egg-info\ndist\ncomposer.json\ncomposer.lock\nvendor\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Code2flow CHANGELOG\n\n## [2.5.1] - 2023-01-08\n- Minor fix for installing code2flow in windows environments\n- Minor README updates and typo corrections\n\n## [2.5.0] - 2022-03-25\n- Add async/await functionality to Python\n- Add --include-only-* CLI options\n- Minor README updates\n- Minor logging updates\n\n## [2.4.0] - 2021-12-26\n- Implement subsets\n\n## [2.3.1] - 2021-12-13\n- Colored edges\n- Improve dependency robustness\n- Fixes for two Python bugs\n- Small textual improvements\n\n## [2.3.0] - 2021-10-11\nFix a few rare javascript bugs. Fix non-UTF8 encoding issue for Python. Better debugging.\n\n## [2.2.0] - 2021-06-21\nRuby + PHP support\n\n## [2.1.1] - 2021-06-15\nUpdates to the CLI that allow code2flow to hypothetically run in Windows\n\n## [2.1.0] - 2021-06-15\nJavascript support\n\n## [2.0.1] - 2021-05-30\nAdd support for constructors to Python\n\n## [2.0.0] - 2021-04-28\nAlmost complete rewrite / refactor. Update to Python3. Use ASTs\n\n## [0.2] - 2013-06-08\nCleaned up code. Remove comments runs much faster. Test scripts. Template.py implementation file. More to come!\n\n## [0.1] - 2013-05-23\nInitial release\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2021 Scott Rogowski\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE, CHANGELOG.md\ninclude code2flow/get_ast.js\ninclude code2flow/get_ast.php\n"
  },
  {
    "path": "Makefile",
    "content": "build:\n\trm -rf dist\n\tpython3 setup.py sdist\n\ntest:\n\tpytest -n=4 --cov-report=html --cov-report=term --cov=code2flow -x\n\nclean:\n\trm -rf build\n\trm -rf dist\n\trm -f out.*\n\trm -rf *.egg-info\n\trm -rf htmlcov\n"
  },
  {
    "path": "README.md",
    "content": "![code2flow logo](https://raw.githubusercontent.com/scottrogowski/code2flow/master/assets/code2flowlogo.png)\n\n![Version 2.5.1](https://img.shields.io/badge/version-2.5.1-brightgreen) ![Build passing](https://img.shields.io/badge/build-passing-brightgreen) ![Coverage 100%](https://img.shields.io/badge/coverage-100%25-brightgreen) ![License MIT](https://img.shields.io/badge/license-MIT-green])\n\nCode2flow generates [call graphs](https://en.wikipedia.org/wiki/Call_graph) for dynamic programming language. Code2flow supports Python, JavaScript, Ruby, and PHP.\n\nThe basic algorithm is simple:\n\n1. Translate your source files into ASTs.\n1. Find all function definitions.\n1. Determine where those functions are called.\n1. Connect the dots.\n\nCode2flow is useful for:\n- Untangling spaghetti code.\n- Identifying orphaned functions.\n- Getting new developers up to speed.\n\nCode2flow provides a *pretty good estimate* of your project's structure. No algorithm can generate a perfect call graph for a [dynamic language](https://en.wikipedia.org/wiki/Dynamic_programming_language) – even less so if that language is [duck-typed](https://en.wikipedia.org/wiki/Duck_typing). See the known limitations in the section below.\n\n*(Below: Code2flow running against a subset of itself `code2flow code2flow/engine.py code2flow/python.py --target-function=code2flow --downstream-depth=3`)*\n\n![code2flow running against a subset of itself](https://raw.githubusercontent.com/scottrogowski/code2flow/master/assets/code2flow_output.png)\n\nInstallation\n------------\n\n```bash\npip3 install code2flow\n```\n\nIf you don't have it already, you will also need to install graphviz. Installation instructions can be found [here](https://graphviz.org/download/).\n\nAdditionally, depending on the language you want to parse, you may need to install additional dependencies:\n- JavaScript: [Acorn](https://www.npmjs.com/package/acorn)\n- Ruby: [Parser](https://github.com/whitequark/parser)\n- PHP: [PHP-Parser](https://github.com/nikic/PHP-Parser)\n- Python: No extra dependencies needed\n\nUsage\n-----\n\nTo generate a DOT file, run something like:\n\n```bash\ncode2flow mypythonfile.py\n```\n\nOr, for Javascript:\n\n```bash\ncode2flow myjavascriptfile.js\n```\n\nYou can specify multiple files or import directories:\n\n```bash\ncode2flow project/directory/source_a.js project/directory/source_b.js\n```\n\n```bash\ncode2flow project/directory/*.js\n```\n\n```bash\ncode2flow project/directory --language js\n```\n\nTo pull out a subset of the graph, try something like:\n\n```bash\ncode2flow mypythonfile.py --target-function my_func --upstream-depth=1 --downstream-depth=1\n```\n\n\nThere are a ton of command line options, to see them all, run:\n\n```bash\ncode2flow --help\n```\n\nHow code2flow works\n------------\n\nCode2flow approximates the structure of projects in dynamic languages. It is *not possible* to generate a perfect callgraph for a dynamic language.\n\nDetailed algorithm:\n\n1. Generate an AST of the source code\n2. Recursively separate groups and nodes. Groups are files, modules, or classes. More precisely, groups are namespaces where functions live. Nodes are the functions themselves.\n3. For all nodes, identify function calls in those nodes.\n4. For all nodes, identify in-scope variables. Attempt to connect those variables to specific nodes and groups. This is where there is some ambiguity in the algorithm because it is impossible to know the types of variables in dynamic languages. So, instead, heuristics must be used.\n5. For all calls in all nodes, attempt to find a match from the in-scope variables. This will be an edge.\n6. If a definitive match from in-scope variables cannot be found, attempt to find a single match from all other groups and nodes.\n7. Trim orphaned nodes and groups.\n8. Output results.\n\nWhy is it impossible to generate a perfect call graph?\n----------------\n\nConsider this toy example in Python\n```python\ndef func_factory(param):\n    if param < .5:\n        return func_a\n    else:\n        return func_b\n\nfunc = func_factory(important_variable)\nfunc()\n```\n\nWe have no way of knowing whether `func` will point to `func_a` or `func_b` until runtime. In practice, ambiguity like this is common and is present in most non-trivial applications.\n\nKnown limitations\n-----------------\n\nCode2flow is internally powered by ASTs. Most limitations stem from a token not being named what code2flow expects it to be named.\n\n* All functions without definitions are skipped. This most often happens when a file is not included.\n* Functions with identical names in different namespaces are (loudly) skipped. E.g. If you have two classes with identically named methods, code2flow cannot distinguish between these and skips them.\n* Imported functions from outside your project directory (including from standard libraries) which share names with your defined functions may not be handled correctly. Instead, when you call the imported function, code2flow will link to your local functions. For example, if you have a function `search()` and call, `import searcher; searcher.search()`, code2flow may link (incorrectly) to your defined function.\n* Anonymous or generated functions are skipped. This includes lambdas and factories.\n* If a function is renamed, either explicitly or by being passed around as a parameter, it will be skipped.\n\n\nAs an imported library\n-----------------\n\nYou can work with code2flow as an imported Python library in much the same way as you work with it\nfrom the CLI.\n\n```python\nimport code2flow\ncode2flow.code2flow(['path/to/filea', 'path/to/fileb'], 'path/to/outputfile')\n```\n\nThe keyword arguments to `code2flow.code2flow` are roughly the same as the CLI\nparameters. To see all available parameters, refer to the code2flow function in [engine.py](https://github.com/scottrogowski/code2flow/blob/master/code2flow/engine.py).\n\n\nHow to contribute\n-----------------------\n\n1. **Open an issue**: Code2flow is not perfect and there is a lot that can be improved. If you find a problem parsing your source that you can identify with a simplified example, please open an issue.\n2. **Create a PR**: Even better, if you have a fix for the issue you identified that passes unit tests, please open a PR.\n3. **Add a language**: While dense, each language implementation is between 250-400 lines of code including comments. If you want to implement another language, the existing implementations can be your guide.\n\n\nUnit tests\n------------------\n\nTest coverage is 100%. To run:\n\n```bash\n    pip install -r requirements_dev.txt\n    make test\n```\n\nLicense\n-----------------------------\n\nCode2flow is licensed under the MIT license.\nPrior to the rewrite in April 2021, code2flow was licensed under LGPL. The last commit under that license was 24b2cb854c6a872ba6e17409fbddb6659bf64d4c.\nThe April 2021 rewrite was substantial, so it's probably reasonable to treat code2flow as completely MIT-licensed.\n\n\nAcknowledgements\n-----------------------------\n\n\n* In mid 2021, Code2flow was rewritten, and two new languages were added. This was prompted and financially supported by the [Sider Corporation](https://siderlabs.com/).\n* The code2flow pip name was graciously transferred to this project from [Dheeraj Nair](https://github.com/Dheeraj1998). He was using it for his own (unrelated) [code2flow](https://github.com/Dheeraj1998/code2flow) project.\n* Many others have contributed through bug fixes, cleanups, and identifying issues. Thank you!!!\n\n\nUnrelated projects\n-----------------------\n\nThe name, \"code2flow\", has been used for several unrelated projects. Specifically, the domain, code2flow.com, has no association with this project. I've never heard anything from them and it doesn't appear like they use anything from here.\n\n\nFeedback / Issues / Contact\n-----------------------------\n\nIf you have an issue using code2flow or a feature request, please post it in the issues tab. In general, I don't provide help over email. Answering a question publicly helps way more people. For everything else, please do email! scottmrogowski@gmail.com\n\n\nFeature Requests\n----------------\n\nEmail me. Usually, I'm spread thin across a lot of projects, so I will, unfortunately, turn down most requests. However, I am open to paid development for compelling features.\n"
  },
  {
    "path": "c2f",
    "content": "#!/usr/bin/env python3\n\nimport sys\nfrom code2flow.engine import main\n\nif __name__ == \"__main__\":\n    main(sys.argv[1:])\n"
  },
  {
    "path": "code2flow/__init__.py",
    "content": "from .engine import code2flow, VERSION\n\ncode2flow = code2flow\nVERSION = VERSION\n"
  },
  {
    "path": "code2flow/engine.py",
    "content": "import argparse\nimport collections\nimport json\nimport logging\nimport os\nimport subprocess\nimport sys\nimport time\n\nfrom .python import Python\nfrom .javascript import Javascript\nfrom .ruby import Ruby\nfrom .php import PHP\nfrom .model import (TRUNK_COLOR, LEAF_COLOR, NODE_COLOR, GROUP_TYPE, OWNER_CONST,\n                    Edge, Group, Node, Variable, is_installed, flatten)\n\nVERSION = '2.5.1'\n\nIMAGE_EXTENSIONS = ('png', 'svg')\nTEXT_EXTENSIONS = ('dot', 'gv', 'json')\nVALID_EXTENSIONS = IMAGE_EXTENSIONS + TEXT_EXTENSIONS\n\nDESCRIPTION = \"Generate flow charts from your source code. \" \\\n              \"See the README at https://github.com/scottrogowski/code2flow.\"\n\n\nLEGEND = \"\"\"subgraph legend{\n    rank = min;\n    label = \"legend\";\n    Legend [shape=none, margin=0, label = <\n        <table cellspacing=\"0\" cellpadding=\"0\" border=\"1\"><tr><td>Code2flow Legend</td></tr><tr><td>\n        <table cellspacing=\"0\">\n        <tr><td>Regular function</td><td width=\"50px\" bgcolor='%s'></td></tr>\n        <tr><td>Trunk function (nothing calls this)</td><td bgcolor='%s'></td></tr>\n        <tr><td>Leaf function (this calls nothing else)</td><td bgcolor='%s'></td></tr>\n        <tr><td>Function call</td><td><font color='black'>&#8594;</font></td></tr>\n        </table></td></tr></table>\n        >];\n}\"\"\" % (NODE_COLOR, TRUNK_COLOR, LEAF_COLOR)\n\n\nLANGUAGES = {\n    'py': Python,\n    'js': Javascript,\n    'mjs': Javascript,\n    'rb': Ruby,\n    'php': PHP,\n}\n\n\nclass LanguageParams():\n    \"\"\"\n    Shallow structure to make storing language-specific parameters cleaner\n    \"\"\"\n    def __init__(self, source_type='script', ruby_version='27'):\n        self.source_type = source_type\n        self.ruby_version = ruby_version\n\n\nclass SubsetParams():\n    \"\"\"\n    Shallow structure to make storing subset-specific parameters cleaner.\n    \"\"\"\n    def __init__(self, target_function, upstream_depth, downstream_depth):\n        self.target_function = target_function\n        self.upstream_depth = upstream_depth\n        self.downstream_depth = downstream_depth\n\n    @staticmethod\n    def generate(target_function, upstream_depth, downstream_depth):\n        \"\"\"\n        :param target_function str:\n        :param upstream_depth int:\n        :param downstream_depth int:\n        :rtype: SubsetParams|Nonetype\n        \"\"\"\n        if upstream_depth and not target_function:\n            raise AssertionError(\"--upstream-depth requires --target-function\")\n\n        if downstream_depth and not target_function:\n            raise AssertionError(\"--downstream-depth requires --target-function\")\n\n        if not target_function:\n            return None\n\n        if not (upstream_depth or downstream_depth):\n            raise AssertionError(\"--target-function requires --upstream-depth or --downstream-depth\")\n\n        if upstream_depth < 0:\n            raise AssertionError(\"--upstream-depth must be >= 0. Exclude argument for complete depth.\")\n\n        if downstream_depth < 0:\n            raise AssertionError(\"--downstream-depth must be >= 0. Exclude argument for complete depth.\")\n\n        return SubsetParams(target_function, upstream_depth, downstream_depth)\n\n\n\ndef _find_target_node(subset_params, all_nodes):\n    \"\"\"\n    Find the node referenced by subset_params.target_function\n    :param subset_params SubsetParams:\n    :param all_nodes list[Node]:\n    :rtype: Node\n    \"\"\"\n    target_nodes = []\n    for node in all_nodes:\n        if node.token == subset_params.target_function or \\\n           node.token_with_ownership() == subset_params.target_function or \\\n           node.name() == subset_params.target_function:\n            target_nodes.append(node)\n    if not target_nodes:\n        raise AssertionError(\"Could not find node %r to build a subset.\" % subset_params.target_function)\n    if len(target_nodes) > 1:\n        raise AssertionError(\"Found multiple nodes for %r: %r. Try either a `class.func` or \"\n                             \"`filename::class.func`.\" % (subset_params.target_function, target_nodes))\n    return target_nodes[0]\n\n\ndef _filter_nodes_for_subset(subset_params, all_nodes, edges):\n    \"\"\"\n    Given subset_params, return a set of all nodes upstream and downstream of the target node.\n    :param subset_params SubsetParams:\n    :param all_nodes list[Node]:\n    :param edges list[Edge]:\n    :rtype: set[Node]\n    \"\"\"\n    target_node = _find_target_node(subset_params, all_nodes)\n    downstream_dict = collections.defaultdict(set)\n    upstream_dict = collections.defaultdict(set)\n    for edge in edges:\n        upstream_dict[edge.node1].add(edge.node0)\n        downstream_dict[edge.node0].add(edge.node1)\n\n    include_nodes = {target_node}\n    step_nodes = {target_node}\n    next_step_nodes = set()\n\n    for _ in range(subset_params.downstream_depth):\n        for node in step_nodes:\n            next_step_nodes.update(downstream_dict[node])\n        include_nodes.update(next_step_nodes)\n        step_nodes = next_step_nodes\n        next_step_nodes = set()\n\n    step_nodes = {target_node}\n    next_step_nodes = set()\n\n    for _ in range(subset_params.upstream_depth):\n        for node in step_nodes:\n            next_step_nodes.update(upstream_dict[node])\n        include_nodes.update(next_step_nodes)\n        step_nodes = next_step_nodes\n        next_step_nodes = set()\n\n    return include_nodes\n\n\ndef _filter_edges_for_subset(new_nodes, edges):\n    \"\"\"\n    Given the subset of nodes, filter for edges within this subset\n    :param new_nodes set[Node]:\n    :param edges list[Edge]:\n    :rtype: list[Edge]\n    \"\"\"\n    new_edges = []\n    for edge in edges:\n        if edge.node0 in new_nodes and edge.node1 in new_nodes:\n            new_edges.append(edge)\n    return new_edges\n\n\ndef _filter_groups_for_subset(new_nodes, file_groups):\n    \"\"\"\n    Given the subset of nodes, do housekeeping and filter out for groups within this subset\n    :param new_nodes set[Node]:\n    :param file_groups list[Group]:\n    :rtype: list[Group]\n    \"\"\"\n    for file_group in file_groups:\n        for node in file_group.all_nodes():\n            if node not in new_nodes:\n                node.remove_from_parent()\n\n    new_file_groups = [g for g in file_groups if g.all_nodes()]\n\n    for file_group in new_file_groups:\n        for group in file_group.all_groups():\n            if not group.all_nodes():\n                group.remove_from_parent()\n\n    return new_file_groups\n\n\ndef _filter_for_subset(subset_params, all_nodes, edges, file_groups):\n    \"\"\"\n    Given subset_params, return the subset of nodes, edges, and groups\n    upstream and downstream of the target node.\n    :param subset_params SubsetParams:\n    :param all_nodes list[Node]:\n    :param edges list[Edge]:\n    :param file_groups list[Group]:\n    :rtype: list[Group], list[Node], list[Edge]\n    \"\"\"\n    new_nodes = _filter_nodes_for_subset(subset_params, all_nodes, edges)\n    new_edges = _filter_edges_for_subset(new_nodes, edges)\n    new_file_groups = _filter_groups_for_subset(new_nodes, file_groups)\n    return new_file_groups, list(new_nodes), new_edges\n\n\ndef generate_json(nodes, edges):\n    '''\n    Generate a json string from nodes and edges\n    See https://github.com/jsongraph/json-graph-specification\n\n    :param nodes list[Node]: functions\n    :param edges list[Edge]: function calls\n    :rtype: str\n    '''\n    nodes = [n.to_dict() for n in nodes]\n    nodes = {n['uid']: n for n in nodes}\n    edges = [e.to_dict() for e in edges]\n\n    return json.dumps({\"graph\": {\n        \"directed\": True,\n        \"nodes\": nodes,\n        \"edges\": edges,\n    }})\n\n\ndef write_file(outfile, nodes, edges, groups, hide_legend=False,\n               no_grouping=False, as_json=False):\n    '''\n    Write a dot file that can be read by graphviz\n\n    :param outfile File:\n    :param nodes list[Node]: functions\n    :param edges list[Edge]: function calls\n    :param groups list[Group]: classes and files\n    :param hide_legend bool:\n    :rtype: None\n    '''\n\n    if as_json:\n        content = generate_json(nodes, edges)\n        outfile.write(content)\n        return\n\n    splines = \"polyline\" if len(edges) >= 500 else \"ortho\"\n\n    content = \"digraph G {\\n\"\n    content += \"concentrate=true;\\n\"\n    content += f'splines=\"{splines}\";\\n'\n    content += 'rankdir=\"LR\";\\n'\n    if not hide_legend:\n        content += LEGEND\n    for node in nodes:\n        content += node.to_dot() + ';\\n'\n    for edge in edges:\n        content += edge.to_dot() + ';\\n'\n    if not no_grouping:\n        for group in groups:\n            content += group.to_dot()\n    content += '}\\n'\n    outfile.write(content)\n\n\ndef determine_language(individual_files):\n    \"\"\"\n    Given a list of filepaths, determine the language from the first\n    valid extension\n\n    :param list[str] individual_files:\n    :rtype: str\n    \"\"\"\n    for source, _ in individual_files:\n        suffix = source.rsplit('.', 1)[-1]\n        if suffix in LANGUAGES:\n            logging.info(\"Implicitly detected language as %r.\", suffix)\n            return suffix\n    raise AssertionError(f\"Language could not be detected from input {individual_files}. \",\n                         \"Try explicitly passing the language flag.\")\n\n\ndef get_sources_and_language(raw_source_paths, language):\n    \"\"\"\n    Given a list of files and directories, return just files.\n    If we are not passed a language, determine it.\n    Filter out files that are not of that language\n\n    :param list[str] raw_source_paths: file or directory paths\n    :param str|None language: Input language\n    :rtype: (list, str)\n    \"\"\"\n\n    individual_files = []\n    for source in sorted(raw_source_paths):\n        if os.path.isfile(source):\n            individual_files.append((source, True))\n            continue\n        for root, _, files in os.walk(source):\n            for f in files:\n                individual_files.append((os.path.join(root, f), False))\n\n    if not individual_files:\n        raise AssertionError(\"No source files found from %r\" % raw_source_paths)\n    logging.info(\"Found %d files from sources argument.\", len(individual_files))\n\n    if not language:\n        language = determine_language(individual_files)\n\n    sources = set()\n    for source, explicity_added in individual_files:\n        if explicity_added or source.endswith('.' + language):\n            sources.add(source)\n        else:\n            logging.info(\"Skipping %r which is not a %s file. \"\n                         \"If this is incorrect, include it explicitly.\",\n                         source, language)\n\n    if not sources:\n        raise AssertionError(\"Could not find any source files given {raw_source_paths} \"\n                             \"and language {language}.\")\n\n    sources = sorted(list(sources))\n    logging.info(\"Processing %d source file(s).\" % (len(sources)))\n    for source in sources:\n        logging.info(\"  \" + source)\n\n    return sources, language\n\n\ndef make_file_group(tree, filename, extension):\n    \"\"\"\n    Given an AST for the entire file, generate a file group complete with\n    subgroups, nodes, etc.\n\n    :param tree ast:\n    :param filename str:\n    :param extension str:\n\n    :rtype: Group\n    \"\"\"\n    language = LANGUAGES[extension]\n\n    subgroup_trees, node_trees, body_trees = language.separate_namespaces(tree)\n    group_type = GROUP_TYPE.FILE\n    token = os.path.split(filename)[-1].rsplit('.' + extension, 1)[0]\n    line_number = 0\n    display_name = 'File'\n    import_tokens = language.file_import_tokens(filename)\n\n    file_group = Group(token, group_type, display_name, import_tokens,\n                       line_number, parent=None)\n    for node_tree in node_trees:\n        for new_node in language.make_nodes(node_tree, parent=file_group):\n            file_group.add_node(new_node)\n\n    file_group.add_node(language.make_root_node(body_trees, parent=file_group), is_root=True)\n\n    for subgroup_tree in subgroup_trees:\n        file_group.add_subgroup(language.make_class_group(subgroup_tree, parent=file_group))\n    return file_group\n\n\ndef _find_link_for_call(call, node_a, all_nodes):\n    \"\"\"\n    Given a call that happened on a node (node_a), return the node\n    that the call links to and the call itself if >1 node matched.\n\n    :param call Call:\n    :param node_a Node:\n    :param all_nodes list[Node]:\n\n    :returns: The node it links to and the call if >1 node matched.\n    :rtype: (Node|None, Call|None)\n    \"\"\"\n\n    all_vars = node_a.get_variables(call.line_number)\n\n    for var in all_vars:\n        var_match = call.matches_variable(var)\n        if var_match:\n            # Unknown modules (e.g. third party) we don't want to match)\n            if var_match == OWNER_CONST.UNKNOWN_MODULE:\n                return None, None\n            assert isinstance(var_match, Node)\n            return var_match, None\n\n    possible_nodes = []\n    if call.is_attr():\n        for node in all_nodes:\n            # checking node.parent != node_a.file_group() prevents self linkage in cases like\n            # function a() {b = Obj(); b.a()}\n            if call.token == node.token and node.parent != node_a.file_group():\n                possible_nodes.append(node)\n    else:\n        for node in all_nodes:\n            if call.token == node.token \\\n               and isinstance(node.parent, Group)  \\\n               and node.parent.group_type == GROUP_TYPE.FILE:\n                possible_nodes.append(node)\n            elif call.token == node.parent.token and node.is_constructor:\n                possible_nodes.append(node)\n\n    if len(possible_nodes) == 1:\n        return possible_nodes[0], None\n    if len(possible_nodes) > 1:\n        return None, call\n    return None, None\n\n\ndef _find_links(node_a, all_nodes):\n    \"\"\"\n    Iterate through the calls on node_a to find everything the node links to.\n    This will return a list of tuples of nodes and calls that were ambiguous.\n\n    :param Node node_a:\n    :param list[Node] all_nodes:\n    :param BaseLanguage language:\n    :rtype: list[(Node, Call)]\n    \"\"\"\n\n    links = []\n    for call in node_a.calls:\n        lfc = _find_link_for_call(call, node_a, all_nodes)\n        assert not isinstance(lfc, Group)\n        links.append(lfc)\n    return list(filter(None, links))\n\n\ndef map_it(sources, extension, no_trimming, exclude_namespaces, exclude_functions,\n           include_only_namespaces, include_only_functions,\n           skip_parse_errors, lang_params):\n    '''\n    Given a language implementation and a list of filenames, do these things:\n    1. Read/parse source ASTs\n    2. Find all groups (classes/modules) and nodes (functions) (a lot happens here)\n    3. Trim namespaces / functions that we don't want\n    4. Consolidate groups / nodes given all we know so far\n    5. Attempt to resolve the variables (point them to a node or group)\n    6. Find all calls between all nodes\n    7. Loudly complain about duplicate edges that were skipped\n    8. Trim nodes that didn't connect to anything\n\n    :param list[str] sources:\n    :param str extension:\n    :param bool no_trimming:\n    :param list exclude_namespaces:\n    :param list exclude_functions:\n    :param list include_only_namespaces:\n    :param list include_only_functions:\n    :param bool skip_parse_errors:\n    :param LanguageParams lang_params:\n\n    :rtype: (list[Group], list[Node], list[Edge])\n    '''\n\n    language = LANGUAGES[extension]\n\n    # 0. Assert dependencies\n    language.assert_dependencies()\n\n    # 1. Read/parse source ASTs\n    file_ast_trees = []\n    for source in sources:\n        try:\n            file_ast_trees.append((source, language.get_tree(source, lang_params)))\n        except Exception as ex:\n            if skip_parse_errors:\n                logging.warning(\"Could not parse %r. (%r) Skipping...\", source, ex)\n            else:\n                raise ex\n\n    # 2. Find all groups (classes/modules) and nodes (functions) (a lot happens here)\n    file_groups = []\n    for source, file_ast_tree in file_ast_trees:\n        file_group = make_file_group(file_ast_tree, source, extension)\n        file_groups.append(file_group)\n\n    # 3. Trim namespaces / functions to exactly what we want\n    if exclude_namespaces or include_only_namespaces:\n        file_groups = _limit_namespaces(file_groups, exclude_namespaces, include_only_namespaces)\n    if exclude_functions or include_only_functions:\n        file_groups = _limit_functions(file_groups, exclude_functions, include_only_functions)\n\n    # 4. Consolidate structures\n    all_subgroups = flatten(g.all_groups() for g in file_groups)\n    all_nodes = flatten(g.all_nodes() for g in file_groups)\n\n    nodes_by_subgroup_token = collections.defaultdict(list)\n    for subgroup in all_subgroups:\n        if subgroup.token in nodes_by_subgroup_token:\n            logging.warning(\"Duplicate group name %r. Naming collision possible.\",\n                            subgroup.token)\n        nodes_by_subgroup_token[subgroup.token] += subgroup.nodes\n\n    for group in file_groups:\n        for subgroup in group.all_groups():\n            subgroup.inherits = [nodes_by_subgroup_token.get(g) for g in subgroup.inherits]\n            subgroup.inherits = list(filter(None, subgroup.inherits))\n            for inherit_nodes in subgroup.inherits:\n                for node in subgroup.nodes:\n                    node.variables += [Variable(n.token, n, n.line_number) for n in inherit_nodes]\n\n    # 5. Attempt to resolve the variables (point them to a node or group)\n    for node in all_nodes:\n        node.resolve_variables(file_groups)\n\n    # Not a step. Just log what we know so far\n    logging.info(\"Found groups %r.\" % [g.label() for g in all_subgroups])\n    logging.info(\"Found nodes %r.\" % sorted(n.token_with_ownership() for n in all_nodes))\n    logging.info(\"Found calls %r.\" % sorted(list(set(c.to_string() for c in\n                                                     flatten(n.calls for n in all_nodes)))))\n    logging.info(\"Found variables %r.\" % sorted(list(set(v.to_string() for v in\n                                                         flatten(n.variables for n in all_nodes)))))\n\n    # 6. Find all calls between all nodes\n    bad_calls = []\n    edges = []\n    for node_a in list(all_nodes):\n        links = _find_links(node_a, all_nodes)\n        for node_b, bad_call in links:\n            if bad_call:\n                bad_calls.append(bad_call)\n            if not node_b:\n                continue\n            edges.append(Edge(node_a, node_b))\n\n    # 7. Loudly complain about duplicate edges that were skipped\n    bad_calls_strings = set()\n    for bad_call in bad_calls:\n        bad_calls_strings.add(bad_call.to_string())\n    bad_calls_strings = list(sorted(list(bad_calls_strings)))\n    if bad_calls_strings:\n        logging.info(\"Skipped processing these calls because the algorithm \"\n                     \"linked them to multiple function definitions: %r.\" % bad_calls_strings)\n\n    if no_trimming:\n        return file_groups, all_nodes, edges\n\n    # 8. Trim nodes that didn't connect to anything\n    nodes_with_edges = set()\n    for edge in edges:\n        nodes_with_edges.add(edge.node0)\n        nodes_with_edges.add(edge.node1)\n\n    for node in all_nodes:\n        if node not in nodes_with_edges:\n            node.remove_from_parent()\n\n    for file_group in file_groups:\n        for group in file_group.all_groups():\n            if not group.all_nodes():\n                group.remove_from_parent()\n\n    file_groups = [g for g in file_groups if g.all_nodes()]\n    all_nodes = list(nodes_with_edges)\n\n    if not all_nodes:\n        logging.warning(\"No functions found! Most likely, your file(s) do not have \"\n                        \"functions that call each other. Note that to generate a flowchart, \"\n                        \"you need to have both the function calls and the function \"\n                        \"definitions. Or, you might be excluding too many \"\n                        \"with --exclude-* / --include-* / --target-function arguments. \")\n        logging.warning(\"Code2flow will generate an empty output file.\")\n\n    return file_groups, all_nodes, edges\n\n\ndef _limit_namespaces(file_groups, exclude_namespaces, include_only_namespaces):\n    \"\"\"\n    Exclude namespaces (classes/modules) which match any of the exclude_namespaces\n\n    :param list[Group] file_groups:\n    :param list exclude_namespaces:\n    :param list include_only_namespaces:\n    :rtype: list[Group]\n    \"\"\"\n\n    removed_namespaces = set()\n\n    for group in list(file_groups):\n        if group.token in exclude_namespaces:\n            for node in group.all_nodes():\n                node.remove_from_parent()\n            removed_namespaces.add(group.token)\n        if include_only_namespaces and group.token not in include_only_namespaces:\n            for node in group.nodes:\n                node.remove_from_parent()\n            removed_namespaces.add(group.token)\n\n        for subgroup in group.all_groups():\n            print(subgroup, subgroup.all_parents())\n            if subgroup.token in exclude_namespaces:\n                for node in subgroup.all_nodes():\n                    node.remove_from_parent()\n                removed_namespaces.add(subgroup.token)\n            if include_only_namespaces and \\\n               subgroup.token not in include_only_namespaces and \\\n               all(p.token not in include_only_namespaces for p in subgroup.all_parents()):\n                for node in subgroup.nodes:\n                    node.remove_from_parent()\n                removed_namespaces.add(group.token)\n\n    for namespace in exclude_namespaces:\n        if namespace not in removed_namespaces:\n            logging.warning(f\"Could not exclude namespace '{namespace}' \"\n                             \"because it was not found.\")\n    return file_groups\n\n\ndef _limit_functions(file_groups, exclude_functions, include_only_functions):\n    \"\"\"\n    Exclude nodes (functions) which match any of the exclude_functions\n\n    :param list[Group] file_groups:\n    :param list exclude_functions:\n    :param list include_only_functions:\n    :rtype: list[Group]\n    \"\"\"\n\n    removed_functions = set()\n\n    for group in list(file_groups):\n        for node in group.all_nodes():\n            if node.token in exclude_functions or \\\n               (include_only_functions and node.token not in include_only_functions):\n                node.remove_from_parent()\n                removed_functions.add(node.token)\n\n    for function_name in exclude_functions:\n        if function_name not in removed_functions:\n            logging.warning(f\"Could not exclude function '{function_name}' \"\n                             \"because it was not found.\")\n    return file_groups\n\n\ndef _generate_graphviz(output_file, extension, final_img_filename):\n    \"\"\"\n    Write the graphviz file\n    :param str output_file:\n    :param str extension:\n    :param str final_img_filename:\n    \"\"\"\n    start_time = time.time()\n    logging.info(\"Running graphviz to make the image...\")\n    command = [\"dot\", \"-T\" + extension, output_file]\n    with open(final_img_filename, 'w') as f:\n        try:\n            subprocess.run(command, stdout=f, check=True)\n            logging.info(\"Graphviz finished in %.2f seconds.\" % (time.time() - start_time))\n        except subprocess.CalledProcessError:\n            logging.warning(\"*** Graphviz returned non-zero exit code! \"\n                            \"Try running %r for more detail ***\", ' '.join(command + ['-v', '-O']))\n\n\ndef _generate_final_img(output_file, extension, final_img_filename, num_edges):\n    \"\"\"\n    Write the graphviz file\n    :param str output_file:\n    :param str extension:\n    :param str final_img_filename:\n    :param int num_edges:\n    \"\"\"\n    _generate_graphviz(output_file, extension, final_img_filename)\n    logging.info(\"Completed your flowchart! To see it, open %r.\",\n                 final_img_filename)\n\n\ndef code2flow(raw_source_paths, output_file, language=None, hide_legend=True,\n              exclude_namespaces=None, exclude_functions=None,\n              include_only_namespaces=None, include_only_functions=None,\n              no_grouping=False, no_trimming=False, skip_parse_errors=False,\n              lang_params=None, subset_params=None, level=logging.INFO):\n    \"\"\"\n    Top-level function. Generate a diagram based on source code.\n    Can generate either a dotfile or an image.\n\n    :param list[str] raw_source_paths: file or directory paths\n    :param str|file output_file: path to the output file. SVG/PNG will generate an image.\n    :param str language: input language extension\n    :param bool hide_legend: Omit the legend from the output\n    :param list exclude_namespaces: List of namespaces to exclude\n    :param list exclude_functions: List of functions to exclude\n    :param list include_only_namespaces: List of namespaces to include\n    :param list include_only_functions: List of functions to include\n    :param bool no_grouping: Don't group functions into namespaces in the final output\n    :param bool no_trimming: Don't trim orphaned functions / namespaces\n    :param bool skip_parse_errors: If a language parser fails to parse a file, skip it\n    :param lang_params LanguageParams: Object to store lang-specific params\n    :param subset_params SubsetParams: Object to store subset-specific params\n    :param int level: logging level\n    :rtype: None\n    \"\"\"\n    start_time = time.time()\n\n    if not isinstance(raw_source_paths, list):\n        raw_source_paths = [raw_source_paths]\n    lang_params = lang_params or LanguageParams()\n\n    exclude_namespaces = exclude_namespaces or []\n    assert isinstance(exclude_namespaces, list)\n    exclude_functions = exclude_functions or []\n    assert isinstance(exclude_functions, list)\n    include_only_namespaces = include_only_namespaces or []\n    assert isinstance(include_only_namespaces, list)\n    include_only_functions = include_only_functions or []\n    assert isinstance(include_only_functions, list)\n\n    logging.basicConfig(format=\"Code2Flow: %(message)s\", level=level)\n\n    sources, language = get_sources_and_language(raw_source_paths, language)\n\n    output_ext = None\n    if isinstance(output_file, str):\n        assert '.' in output_file, \"Output filename must end in one of: %r.\" % set(VALID_EXTENSIONS)\n        output_ext = output_file.rsplit('.', 1)[1] or ''\n        assert output_ext in VALID_EXTENSIONS, \"Output filename must end in one of: %r.\" % \\\n                                               set(VALID_EXTENSIONS)\n\n    final_img_filename = None\n    if output_ext and output_ext in IMAGE_EXTENSIONS:\n        if not is_installed('dot') and not is_installed('dot.exe'):\n            raise AssertionError(\n                \"Can't generate a flowchart image because neither `dot` nor \"\n                \"`dot.exe` was found. Either install graphviz (see the README) \"\n                \"or, if you just want an intermediate text file, set your --output \"\n                \"file to use a supported text extension: %r\" % set(TEXT_EXTENSIONS))\n        final_img_filename = output_file\n        output_file, extension = output_file.rsplit('.', 1)\n        output_file += '.gv'\n\n    file_groups, all_nodes, edges = map_it(sources, language, no_trimming,\n                                           exclude_namespaces, exclude_functions,\n                                           include_only_namespaces, include_only_functions,\n                                           skip_parse_errors, lang_params)\n\n    if subset_params:\n        logging.info(\"Filtering into subset...\")\n        file_groups, all_nodes, edges = _filter_for_subset(subset_params, all_nodes, edges, file_groups)\n\n    file_groups.sort()\n    all_nodes.sort()\n    edges.sort()\n\n    logging.info(\"Generating output file...\")\n\n    if isinstance(output_file, str):\n        with open(output_file, 'w') as fh:\n            as_json = output_ext == 'json'\n            write_file(fh, nodes=all_nodes, edges=edges,\n                       groups=file_groups, hide_legend=hide_legend,\n                       no_grouping=no_grouping, as_json=as_json)\n    else:\n        write_file(output_file, nodes=all_nodes, edges=edges,\n                   groups=file_groups, hide_legend=hide_legend,\n                   no_grouping=no_grouping)\n\n    logging.info(\"Wrote output file %r with %d nodes and %d edges.\",\n                 output_file, len(all_nodes), len(edges))\n    if not output_ext == 'json':\n        logging.info(\"For better machine readability, you can also try outputting in a json format.\")\n    logging.info(\"Code2flow finished processing in %.2f seconds.\" % (time.time() - start_time))\n\n    # translate to an image if that was requested\n    if final_img_filename:\n        _generate_final_img(output_file, extension, final_img_filename, len(edges))\n\n\ndef main(sys_argv=None):\n    \"\"\"\n    CLI interface. Sys_argv is a parameter for the sake of unittest coverage.\n    :param sys_argv list:\n    :rtype: None\n    \"\"\"\n    parser = argparse.ArgumentParser(\n        description=DESCRIPTION,\n        formatter_class=argparse.ArgumentDefaultsHelpFormatter)\n    parser.add_argument(\n        'sources', metavar='sources', nargs='+',\n        help='source code file/directory paths.')\n    parser.add_argument(\n        '--output', '-o', default='out.png',\n        help=f'output file path. Supported types are {VALID_EXTENSIONS}.')\n    parser.add_argument(\n        '--language', choices=['py', 'js', 'rb', 'php'],\n        help='process this language and ignore all other files.'\n             'If omitted, use the suffix of the first source file.')\n    parser.add_argument(\n        '--target-function',\n        help='output a subset of the graph centered on this function. '\n             'Valid formats include `func`, `class.func`, and `file::class.func`. '\n             'Requires --upstream-depth and/or --downstream-depth. ')\n    parser.add_argument(\n        '--upstream-depth', type=int, default=0,\n        help='include n nodes upstream of --target-function.')\n    parser.add_argument(\n        '--downstream-depth', type=int, default=0,\n        help='include n nodes downstream of --target-function.')\n    parser.add_argument(\n        '--exclude-functions',\n        help='exclude functions from the output. Comma delimited.')\n    parser.add_argument(\n        '--exclude-namespaces',\n        help='exclude namespaces (Classes, modules, etc) from the output. Comma delimited.')\n    parser.add_argument(\n        '--include-only-functions',\n        help='include only functions in the output. Comma delimited.')\n    parser.add_argument(\n        '--include-only-namespaces',\n        help='include only namespaces (Classes, modules, etc) in the output. Comma delimited.')\n    parser.add_argument(\n        '--no-grouping', action='store_true',\n        help='instead of grouping functions into namespaces, let functions float.')\n    parser.add_argument(\n        '--no-trimming', action='store_true',\n        help='show all functions/namespaces whether or not they connect to anything.')\n    parser.add_argument(\n        '--hide-legend', action='store_true',\n        help='by default, Code2flow generates a small legend. This flag hides it.')\n    parser.add_argument(\n        '--skip-parse-errors', action='store_true',\n        help='skip files that the language parser fails on.')\n    parser.add_argument(\n        '--source-type', choices=['script', 'module'], default='script',\n        help='js only. Parse the source as scripts (commonJS) or modules (es6)')\n    parser.add_argument(\n        '--ruby-version', default='27',\n        help='ruby only. Which ruby version to parse? This is passed directly into ruby-parse. '\n             'Use numbers like 25, 27, or 31.')\n    parser.add_argument(\n        '--quiet', '-q', action='store_true',\n        help='suppress most logging')\n    parser.add_argument(\n        '--verbose', '-v', action='store_true',\n        help='add more logging')\n    parser.add_argument(\n        '--version', action='version', version='%(prog)s ' + VERSION)\n\n    sys_argv = sys_argv or sys.argv[1:]\n    args = parser.parse_args(sys_argv)\n    level = logging.INFO\n    if args.verbose and args.quiet:\n        raise AssertionError(\"Passed both --verbose and --quiet flags\")\n    if args.verbose:\n        level = logging.DEBUG\n    if args.quiet:\n        level = logging.WARNING\n\n    exclude_namespaces = list(filter(None, (args.exclude_namespaces or \"\").split(',')))\n    exclude_functions = list(filter(None, (args.exclude_functions or \"\").split(',')))\n    include_only_namespaces = list(filter(None, (args.include_only_namespaces or \"\").split(',')))\n    include_only_functions = list(filter(None, (args.include_only_functions or \"\").split(',')))\n\n    lang_params = LanguageParams(args.source_type, args.ruby_version)\n    subset_params = SubsetParams.generate(args.target_function, args.upstream_depth,\n                                          args.downstream_depth)\n\n    code2flow(\n        raw_source_paths=args.sources,\n        output_file=args.output,\n        language=args.language,\n        hide_legend=args.hide_legend,\n        exclude_namespaces=exclude_namespaces,\n        exclude_functions=exclude_functions,\n        include_only_namespaces=include_only_namespaces,\n        include_only_functions=include_only_functions,\n        no_grouping=args.no_grouping,\n        no_trimming=args.no_trimming,\n        skip_parse_errors=args.skip_parse_errors,\n        lang_params=lang_params,\n        subset_params=subset_params,\n        level=level,\n    )\n"
  },
  {
    "path": "code2flow/get_ast.js",
    "content": "const fs = require('fs');\nconst {Parser} = require(\"acorn\")\n\nconst sourceType = process.argv[2]\nconst src = fs.readFileSync(process.argv[3], 'utf8')\nconst tree = Parser.parse(src, {'locations': true, 'sourceType': sourceType,\n                                'ecmaVersion': '2020'})\n\nprocess.stdout.write(JSON.stringify(tree))\n"
  },
  {
    "path": "code2flow/get_ast.php",
    "content": "<?php\nrequire_once __DIR__ . '/vendor/autoload.php';\n\nuse PhpParser\\Error;\nuse PhpParser\\NodeDumper;\nuse PhpParser\\ParserFactory;\n\n$code = file_get_contents($argv[1]);\n$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);\n\ntry {\n    $stmts = $parser->parse($code);\n    echo json_encode($stmts, JSON_PRETTY_PRINT), \"\\n\";\n} catch (PhpParser\\Error $e) {\n    echo 'Parse Error: ', $e->getMessage();\n    exit(1);\n}\n\n?>\n"
  },
  {
    "path": "code2flow/javascript.py",
    "content": "import logging\nimport os\nimport json\nimport subprocess\n\nfrom .model import (Group, Node, Call, Variable, BaseLanguage,\n                    OWNER_CONST, GROUP_TYPE, is_installed, djoin, flatten)\n\n\ndef lineno(el):\n    \"\"\"\n    Get the first line number of ast element\n\n    :param ast el:\n    :rtype: int\n    \"\"\"\n    if isinstance(el, list):\n        el = el[0]\n    ret = el['loc']['start']['line']\n    assert type(ret) == int\n    return ret\n\n\ndef walk(tree):\n    \"\"\"\n    Walk through the ast tree and return all nodes\n    :param ast tree:\n    :rtype: list[ast]\n    \"\"\"\n    ret = []\n    if type(tree) == list:\n        for el in tree:\n            if el.get('type'):\n                ret.append(el)\n                ret += walk(el)\n    elif type(tree) == dict:\n        for k, v in tree.items():\n            if type(v) == dict and v.get('type'):\n                ret.append(v)\n                ret += walk(v)\n            if type(v) == list:\n                ret += walk(v)\n    return ret\n\n\ndef resolve_owner(callee):\n    \"\"\"\n    Resolve who owns the call object.\n    So if the expression is i_ate.pie(). And i_ate is a Person, the callee is Person.\n    This is returned as a string and eventually set to the owner_token in the call\n\n    :param ast callee:\n    :rtype: str\n    \"\"\"\n\n    if callee['object']['type'] == 'ThisExpression':\n        return 'this'\n    if callee['object']['type'] == 'Identifier':\n        return callee['object']['name']\n    if callee['object']['type'] == 'MemberExpression':\n        if 'object' in callee['object'] and 'name' in callee['object']['property']:\n            return djoin((resolve_owner(callee['object']) or ''),\n                         callee['object']['property']['name'])\n        return OWNER_CONST.UNKNOWN_VAR\n    if callee['object']['type'] == 'CallExpression':\n        return OWNER_CONST.UNKNOWN_VAR\n\n    if callee['object']['type'] == 'NewExpression':\n        if 'name' in callee['object']['callee']:\n            return callee['object']['callee']['name']\n        return djoin(callee['object']['callee']['object']['name'],\n                     callee['object']['callee']['property']['name'])\n\n    return OWNER_CONST.UNKNOWN_VAR\n\n\ndef get_call_from_func_element(func):\n    \"\"\"\n    Given a javascript ast that represents a function call, clear and create our\n    generic Call object. Some calls have no chance at resolution (e.g. array[2](param))\n    so we return nothing instead.\n\n    :param func dict:\n    :rtype: Call|None\n    \"\"\"\n    callee = func['callee']\n    if callee['type'] == 'MemberExpression' and 'name' in callee['property']:\n        owner_token = resolve_owner(callee)\n        return Call(token=callee['property']['name'],\n                    line_number=lineno(callee),\n                    owner_token=owner_token)\n    if callee['type'] == 'Identifier':\n        return Call(token=callee['name'], line_number=lineno(callee))\n    return None\n\n\ndef make_calls(body):\n    \"\"\"\n    Given a list of lines, find all calls in this list.\n\n    :param list|dict body:\n    :rtype: list[Call]\n    \"\"\"\n    calls = []\n    for element in walk(body):\n        if element['type'] == 'CallExpression':\n            call = get_call_from_func_element(element)\n            if call:\n                calls.append(call)\n        elif element['type'] == 'NewExpression' and element['callee']['type'] == 'Identifier':\n            calls.append(Call(token=element['callee']['name'],\n                              line_number=lineno(element)))\n    return calls\n\n\ndef process_assign(element):\n    \"\"\"\n    Given an element from the ast which is an assignment statement, return a\n    Variable that points_to the type of object being assigned. The\n    points_to is often a string but that is resolved later.\n\n    :param element ast:\n    :rtype: Variable\n    \"\"\"\n\n    if len(element['declarations']) > 1:\n        return []\n    target = element['declarations'][0]\n    assert target['type'] == 'VariableDeclarator'\n    if target['init'] is None:\n        return []\n\n    if target['init']['type'] == 'NewExpression':\n        token = target['id']['name']\n        call = get_call_from_func_element(target['init'])\n        if call:\n            return [Variable(token, call, lineno(element))]\n\n    # this block is for require (as in: import) expressions\n    if target['init']['type'] == 'CallExpression' \\\n       and target['init']['callee'].get('name') == 'require':\n        import_src_str = target['init']['arguments'][0]['value']\n        if 'name' in target['id']:\n            imported_name = target['id']['name']\n            points_to_str = djoin(import_src_str, imported_name)\n            return [Variable(imported_name, points_to_str, lineno(element))]\n        ret = []\n        for prop in target['id'].get('properties', []):\n            imported_name = prop['key']['name']\n            points_to_str = djoin(import_src_str, imported_name)\n            ret.append(Variable(imported_name, points_to_str, lineno(element)))\n        return ret\n\n    # For the other type of import expressions\n    if target['init']['type'] == 'ImportExpression':\n        import_src_str = target['init']['source']['raw']\n        imported_name = target['id']['name']\n        points_to_str = djoin(import_src_str, imported_name)\n        return [Variable(imported_name, points_to_str, lineno(element))]\n\n    if target['init']['type'] == 'CallExpression':\n        if 'name' not in target['id']:\n            return []\n        call = get_call_from_func_element(target['init'])\n        if call:\n            return [Variable(target['id']['name'], call, lineno(element))]\n\n    if target['init']['type'] == 'ThisExpression':\n        assert set(target['init'].keys()) == {'start', 'end', 'loc', 'type'}\n        return []\n    return []\n\n\ndef make_local_variables(tree, parent):\n    \"\"\"\n    Given an ast of all the lines in a function, generate a list of\n    variables in that function. Variables are tokens and what they link to.\n    In this case, what it links to is just a string. However, that is resolved\n    later.\n\n    Also return variables for the outer scope parent\n\n    :param tree list|dict:\n    :param parent Group:\n    :rtype: list[Variable]\n    \"\"\"\n    if not tree:\n        return []\n\n    variables = []\n    for element in walk(tree):\n        if element['type'] == 'VariableDeclaration':\n            variables += process_assign(element)\n\n    # Make a 'this' variable for use anywhere we need it that points to the class\n    if isinstance(parent, Group) and parent.group_type == GROUP_TYPE.CLASS:\n        variables.append(Variable('this', parent, lineno(tree)))\n\n    variables = list(filter(None, variables))\n    return variables\n\n\ndef children(tree):\n    \"\"\"\n    The acorn AST is tricky. This returns all the children of an element\n    :param ast tree:\n    :rtype: list[ast]\n    \"\"\"\n    assert type(tree) == dict\n    ret = []\n    for k, v in tree.items():\n        if type(v) == dict and v.get('type'):\n            ret.append(v)\n        if type(v) == list:\n            ret += filter(None, v)\n    return ret\n\n\ndef get_inherits(tree):\n    \"\"\"\n    Gets the superclass of the class. In js, this will be max 1 element\n    :param ast tree:\n    :rtype: list[str]\n    \"\"\"\n    if tree['superClass']:\n        if 'name' in tree['superClass']:\n            return [tree['superClass']['name']]\n        return [djoin(tree['superClass']['object']['name'], tree['superClass']['property']['name'])]\n    return []\n\n\ndef get_acorn_version():\n    \"\"\"\n    Get the version of installed acorn\n    :rtype: str\n    \"\"\"\n    proc = subprocess.Popen(['node', '-p', 'require(\\'acorn/package.json\\').version'],\n                            stdout=subprocess.PIPE, stderr=subprocess.PIPE,\n                            cwd=os.path.dirname(os.path.realpath(__file__)))\n    assert proc.wait() == 0, \"Acorn is required to parse javascript files. \" \\\n                             \"It was found on the path but could not be imported \" \\\n                             \"in node.\\n\" + proc.stderr.read().decode()\n    return proc.stdout.read().decode().strip()\n\n\nclass Javascript(BaseLanguage):\n    @staticmethod\n    def assert_dependencies():\n        \"\"\"Assert that acorn is installed and the correct version\"\"\"\n        assert is_installed('acorn'), \"Acorn is required to parse javascript files \" \\\n                                      \"but was not found on the path. Install it \" \\\n                                      \"from npm and try again.\"\n        version = get_acorn_version()\n        if not version.startswith('8.'):\n            logging.warning(\"Acorn is required to parse javascript files. \"\n                            \"Version %r was found but code2flow has only been \"\n                            \"tested on 8.*\", version)\n        logging.info(\"Using Acorn %s\" % version)\n\n    @staticmethod\n    def get_tree(filename, lang_params):\n        \"\"\"\n        Get the entire AST for this file\n\n        :param filename str:\n        :param lang_params LanguageParams:\n        :rtype: ast\n        \"\"\"\n        script_loc = os.path.join(os.path.dirname(os.path.realpath(__file__)),\n                                  \"get_ast.js\")\n        cmd = [\"node\", script_loc, lang_params.source_type, filename]\n        try:\n            output = subprocess.check_output(cmd, stderr=subprocess.PIPE)\n        except subprocess.CalledProcessError:\n            raise AssertionError(\n                \"Acorn could not parse file %r. You may have a JS syntax error or \"\n                \"if this is an es6-style source, you may need to run code2flow \"\n                \"with --source-type=module. \"\n                \"For more detail, try running the command \"\n                \"\\n  acorn %s\\n\"\n                \"Warning: Acorn CANNOT parse all javascript files. See their docs. \" %\n                (filename, filename)) from None\n        tree = json.loads(output)\n        assert isinstance(tree, dict)\n        assert tree['type'] == 'Program'\n        return tree\n\n    @staticmethod\n    def separate_namespaces(tree):\n        \"\"\"\n        Given an AST, recursively separate that AST into lists of ASTs for the\n        subgroups, nodes, and body. This is an intermediate step to allow for\n        cleaner processing downstream\n\n        :param tree ast:\n        :returns: tuple of group, node, and body trees. These are processed\n                  downstream into real Groups and Nodes.\n        :rtype: (list[ast], list[ast], list[ast])\n        \"\"\"\n\n        groups = []\n        nodes = []\n        body = []\n        for el in children(tree):\n            if el['type'] in ('MethodDefinition', 'FunctionDeclaration'):\n                nodes.append(el)\n            elif el['type'] == 'ClassDeclaration':\n                groups.append(el)\n            else:\n                tup = Javascript.separate_namespaces(el)\n                if tup[0] or tup[1]:\n                    groups += tup[0]\n                    nodes += tup[1]\n                    body += tup[2]\n                else:\n                    body.append(el)\n        return groups, nodes, body\n\n    @staticmethod\n    def make_nodes(tree, parent):\n        \"\"\"\n        Given an ast of all the lines in a function, create the node along with the\n        calls and variables internal to it.\n        Also make the nested subnodes\n\n        :param tree ast:\n        :param parent Group:\n        :rtype: list[Node]\n        \"\"\"\n        is_constructor = False\n        if tree.get('kind') == 'constructor':\n            token = '(constructor)'\n            is_constructor = True\n        elif tree['type'] == 'FunctionDeclaration':\n            token = tree['id']['name']\n        elif tree['type'] == 'MethodDefinition':\n            token = tree['key']['name']\n\n        if tree['type'] == 'FunctionDeclaration':\n            full_node_body = tree['body']\n        else:\n            full_node_body = tree['value']\n\n        subgroup_trees, subnode_trees, this_scope_body = Javascript.separate_namespaces(full_node_body)\n        if subgroup_trees:\n            # TODO - this is when a class is defined within a function\n            # It's unusual but should probably be handled in the future.\n            # Handling this use case would require some code reorganziation.\n            # Take a look at class_in_function.js to better understand.\n            logging.warning(\"Skipping class defined within a function!\")\n\n        line_number = lineno(tree)\n        calls = make_calls(this_scope_body)\n        variables = make_local_variables(this_scope_body, parent)\n        node = Node(token, calls, variables, parent=parent, line_number=line_number,\n                    is_constructor=is_constructor)\n        subnodes = flatten([Javascript.make_nodes(t, node) for t in subnode_trees])\n\n        return [node] + subnodes\n\n    @staticmethod\n    def make_root_node(lines, parent):\n        \"\"\"\n        The \"root_node\" is an implict node of lines which are executed in the global\n        scope on the file itself and not otherwise part of any function.\n\n        :param lines list[ast]:\n        :param parent Group:\n        :rtype: Node\n        \"\"\"\n        token = \"(global)\"\n        calls = make_calls(lines)\n        variables = make_local_variables(lines, parent)\n        root_node = Node(token, calls, variables,\n                         line_number=0, parent=parent)\n        return root_node\n\n    @staticmethod\n    def make_class_group(tree, parent):\n        \"\"\"\n        Given an AST for the subgroup (a class), generate that subgroup.\n        In this function, we will also need to generate all of the nodes internal\n        to the group.\n\n        :param tree ast:\n        :param parent Group:\n        :rtype: Group\n        \"\"\"\n        assert tree['type'] == 'ClassDeclaration'\n        subgroup_trees, node_trees, body_trees = Javascript.separate_namespaces(tree)\n        assert not subgroup_trees\n\n        group_type = GROUP_TYPE.CLASS\n        token = tree['id']['name']\n        display_name = 'Class'\n        line_number = lineno(tree)\n        inherits = get_inherits(tree)\n        class_group = Group(token, group_type, display_name,\n                            inherits=inherits, line_number=line_number, parent=parent)\n\n        for node_tree in node_trees:\n            for new_node in Javascript.make_nodes(node_tree, parent=class_group):\n                class_group.add_node(new_node)\n\n        return class_group\n\n    @staticmethod\n    def file_import_tokens(filename):\n        \"\"\"\n        Returns the token(s) we would use if importing this file from another.\n\n        :param filename str:\n        :rtype: list[str]\n        \"\"\"\n        return []\n"
  },
  {
    "path": "code2flow/model.py",
    "content": "import abc\nimport os\n\n\nTRUNK_COLOR = '#966F33'\nLEAF_COLOR = '#6db33f'\nEDGE_COLORS = [\"#000000\", \"#E69F00\", \"#56B4E9\", \"#009E73\",\n               \"#F0E442\", \"#0072B2\", \"#D55E00\", \"#CC79A7\"]\nNODE_COLOR = \"#cccccc\"\n\n\nclass Namespace(dict):\n    \"\"\"\n    Abstract constants class\n    Constants can be accessed via .attribute or [key] and can be iterated over.\n    \"\"\"\n    def __init__(self, *args, **kwargs):\n        d = {k: k for k in args}\n        d.update(dict(kwargs.items()))\n        super().__init__(d)\n\n    def __getattr__(self, item):\n        return self[item]\n\n\nOWNER_CONST = Namespace(\"UNKNOWN_VAR\", \"UNKNOWN_MODULE\")\nGROUP_TYPE = Namespace(\"FILE\", \"CLASS\", \"NAMESPACE\")\n\n\ndef is_installed(executable_cmd):\n    \"\"\"\n    Determine whether a command can be run or not\n\n    :param list[str] individual_files:\n    :rtype: str\n    \"\"\"\n    for path in os.environ[\"PATH\"].split(os.pathsep):\n        path = path.strip('\"')\n        exe_file = os.path.join(path, executable_cmd)\n        if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK):\n            return True\n    return False\n\n\ndef djoin(*tup):\n    \"\"\"\n    Convenience method to join strings with dots\n    :rtype: str\n    \"\"\"\n    if len(tup) == 1 and isinstance(tup[0], list):\n        return '.'.join(tup[0])\n    return '.'.join(tup)\n\n\ndef flatten(list_of_lists):\n    \"\"\"\n    Return a list from a list of lists\n    :param list[list[Value]] list_of_lists:\n    :rtype: list[Value]\n    \"\"\"\n    return [el for sublist in list_of_lists for el in sublist]\n\n\ndef _resolve_str_variable(variable, file_groups):\n    \"\"\"\n    String variables are when variable.points_to is a string\n    This happens ONLY when we have imports that we delayed processing for\n\n    This function looks through all files to see if any particular node matches\n    the variable.points_to string\n\n    :param Variable variable:\n    :param list[Group] file_groups:\n    :rtype: Node|Group|str\n    \"\"\"\n    for file_group in file_groups:\n        for node in file_group.all_nodes():\n            if any(ot == variable.points_to for ot in node.import_tokens):\n                return node\n        for group in file_group.all_groups():\n            if any(ot == variable.points_to for ot in group.import_tokens):\n                return group\n    return OWNER_CONST.UNKNOWN_MODULE\n\n\nclass BaseLanguage(abc.ABC):\n    \"\"\"\n    Languages are individual implementations for different dynamic languages.\n    This is the superclass of Python, Javascript, PHP, and Ruby.\n    Every implementation must implement all of these methods.\n    For more detail, see the individual implementations.\n    Note that the 'Tree' type is generic and will be a different\n    type for different languages. In Python, it is an ast.AST.\n    \"\"\"\n\n    @staticmethod\n    @abc.abstractmethod\n    def assert_dependencies():\n        \"\"\"\n        :rtype: None\n        \"\"\"\n\n    @staticmethod\n    @abc.abstractmethod\n    def get_tree(filename, lang_params):\n        \"\"\"\n        :param filename str:\n        :rtype: Tree\n        \"\"\"\n\n    @staticmethod\n    @abc.abstractmethod\n    def separate_namespaces(tree):\n        \"\"\"\n        :param tree Tree:\n        :rtype: (list[tree], list[tree], list[tree])\n        \"\"\"\n\n    @staticmethod\n    @abc.abstractmethod\n    def make_nodes(tree, parent):\n        \"\"\"\n        :param tree Tree:\n        :param parent Group:\n        :rtype: list[Node]\n        \"\"\"\n\n    @staticmethod\n    @abc.abstractmethod\n    def make_root_node(lines, parent):\n        \"\"\"\n        :param lines list[Tree]:\n        :param parent Group:\n        :rtype: Node\n        \"\"\"\n\n    @staticmethod\n    @abc.abstractmethod\n    def make_class_group(tree, parent):\n        \"\"\"\n        :param tree Tree:\n        :param parent Group:\n        :rtype: Group\n        \"\"\"\n\n\nclass Variable():\n    \"\"\"\n    Variables represent named tokens that are accessible to their scope.\n    They may either point to a string or, once resolved, a Group/Node.\n    Not all variables can be resolved\n    \"\"\"\n    def __init__(self, token, points_to, line_number=None):\n        \"\"\"\n        :param str token:\n        :param str|Call|Node|Group points_to: (str/Call is eventually resolved to Nodes|Groups)\n        :param int|None line_number:\n        \"\"\"\n        assert token\n        assert points_to\n        self.token = token\n        self.points_to = points_to\n        self.line_number = line_number\n\n    def __repr__(self):\n        return f\"<Variable token={self.token} points_to={repr(self.points_to)}\"\n\n    def to_string(self):\n        \"\"\"\n        For logging\n        :rtype: str\n        \"\"\"\n        if self.points_to and isinstance(self.points_to, (Group, Node)):\n            return f'{self.token}->{self.points_to.token}'\n        return f'{self.token}->{self.points_to}'\n\n\nclass Call():\n    \"\"\"\n    Calls represent function call expressions.\n    They can be an attribute call like\n        object.do_something()\n    Or a \"naked\" call like\n        do_something()\n\n    \"\"\"\n    def __init__(self, token, line_number=None, owner_token=None, definite_constructor=False):\n        self.token = token\n        self.owner_token = owner_token\n        self.line_number = line_number\n        self.definite_constructor = definite_constructor\n\n    def __repr__(self):\n        return f\"<Call owner_token={self.owner_token} token={self.token}>\"\n\n    def to_string(self):\n        \"\"\"\n        Returns a representation of this call to be printed by the engine\n        in logging.\n        :rtype: str\n        \"\"\"\n        if self.owner_token:\n            return f\"{self.owner_token}.{self.token}()\"\n        return f\"{self.token}()\"\n\n    def is_attr(self):\n        \"\"\"\n        Attribute calls are like `a.do_something()` rather than `do_something()`\n        :rtype: bool\n        \"\"\"\n        return self.owner_token is not None\n\n    def matches_variable(self, variable):\n        \"\"\"\n        Check whether this variable is what the call is acting on.\n        For example, if we had 'obj' from\n            obj = Obj()\n        as a variable and a call of\n            obj.do_something()\n        Those would match and we would return the \"do_something\" node from obj.\n\n        :param variable Variable:\n        :rtype: Node\n        \"\"\"\n\n        if self.is_attr():\n            if self.owner_token == variable.token:\n                for node in getattr(variable.points_to, 'nodes', []):\n                    if self.token == node.token:\n                        return node\n                for inherit_nodes in getattr(variable.points_to, 'inherits', []):\n                    for node in inherit_nodes:\n                        if self.token == node.token:\n                            return node\n                if variable.points_to in OWNER_CONST:\n                    return variable.points_to\n\n            # This section is specifically for resolving namespace variables\n            if isinstance(variable.points_to, Group) \\\n               and variable.points_to.group_type == GROUP_TYPE.NAMESPACE:\n                parts = self.owner_token.split('.')\n                if len(parts) != 2:\n                    return None\n                if parts[0] != variable.token:\n                    return None\n                for node in variable.points_to.all_nodes():\n                    if parts[1] == node.namespace_ownership() \\\n                       and self.token == node.token:\n                        return node\n\n            return None\n        if self.token == variable.token:\n            if isinstance(variable.points_to, Node):\n                return variable.points_to\n            if isinstance(variable.points_to, Group) \\\n               and variable.points_to.group_type == GROUP_TYPE.CLASS \\\n               and variable.points_to.get_constructor():\n                return variable.points_to.get_constructor()\n        return None\n\n\nclass Node():\n    def __init__(self, token, calls, variables, parent, import_tokens=None,\n                 line_number=None, is_constructor=False):\n        self.token = token\n        self.line_number = line_number\n        self.calls = calls\n        self.variables = variables\n        self.import_tokens = import_tokens or []\n        self.parent = parent\n        self.is_constructor = is_constructor\n\n        self.uid = \"node_\" + os.urandom(4).hex()\n\n        # Assume it is a leaf and a trunk. These are modified later\n        self.is_leaf = True  # it calls nothing else\n        self.is_trunk = True  # nothing calls it\n\n    def __repr__(self):\n        return f\"<Node token={self.token} parent={self.parent}>\"\n\n    def __lt__(self, other):\n            return self.name() < other.name()\n\n    def name(self):\n        \"\"\"\n        Names exist largely for unit tests and deterministic node sorting\n        :rtype: str\n        \"\"\"\n        return f\"{self.first_group().filename()}::{self.token_with_ownership()}\"\n\n    def first_group(self):\n        \"\"\"\n        The first group that contains this node.\n        :rtype: Group\n        \"\"\"\n        parent = self.parent\n        while not isinstance(parent, Group):\n            parent = parent.parent\n        return parent\n\n    def file_group(self):\n        \"\"\"\n        Get the file group that this node is in.\n        :rtype: Group\n        \"\"\"\n        parent = self.parent\n        while parent.parent:\n            parent = parent.parent\n        return parent\n\n    def is_attr(self):\n        \"\"\"\n        Whether this node is attached to something besides the file\n        :rtype: bool\n        \"\"\"\n        return (self.parent\n                and isinstance(self.parent, Group)\n                and self.parent.group_type in (GROUP_TYPE.CLASS, GROUP_TYPE.NAMESPACE))\n\n    def token_with_ownership(self):\n        \"\"\"\n        Token which includes what group this is a part of\n        :rtype: str\n        \"\"\"\n        if self.is_attr():\n            return djoin(self.parent.token, self.token)\n        return self.token\n\n    def namespace_ownership(self):\n        \"\"\"\n        Get the ownership excluding namespace\n        :rtype: str\n        \"\"\"\n        parent = self.parent\n        ret = []\n        while parent and parent.group_type == GROUP_TYPE.CLASS:\n            ret = [parent.token] + ret\n            parent = parent.parent\n        return djoin(ret)\n\n    def label(self):\n        \"\"\"\n        Labels are what you see on the graph\n        :rtype: str\n        \"\"\"\n        if self.line_number is not None:\n            return f\"{self.line_number}: {self.token}()\"\n        return f\"{self.token}()\"\n\n    def remove_from_parent(self):\n        \"\"\"\n        Remove this node from it's parent. This effectively deletes the node.\n        :rtype: None\n        \"\"\"\n        self.first_group().nodes = [n for n in self.first_group().nodes if n != self]\n\n    def get_variables(self, line_number=None):\n        \"\"\"\n        Get variables in-scope on the line number.\n        This includes all local variables as-well-as outer-scope variables\n        :rtype: list[Variable]\n        \"\"\"\n        if line_number is None:\n            ret = list(self.variables)\n        else:\n            # TODO variables should be sorted by scope before line_number\n            ret = list([v for v in self.variables if v.line_number <= line_number])\n        if any(v.line_number for v in ret):\n            ret.sort(key=lambda v: v.line_number, reverse=True)\n\n        parent = self.parent\n        while parent:\n            ret += parent.get_variables()\n            parent = parent.parent\n        return ret\n\n    def resolve_variables(self, file_groups):\n        \"\"\"\n        For all variables, attempt to resolve the Node/Group on points_to.\n        There is a good chance this will be unsuccessful.\n\n        :param list[Group] file_groups:\n        :rtype: None\n        \"\"\"\n        for variable in self.variables:\n            if isinstance(variable.points_to, str):\n                variable.points_to = _resolve_str_variable(variable, file_groups)\n            elif isinstance(variable.points_to, Call):\n                # else, this is a call variable\n                call = variable.points_to\n                # Only process Class(); Not a.Class()\n                if call.is_attr() and not call.definite_constructor:\n                    continue\n                # Else, assume the call is a constructor.\n                # iterate through to find the right group\n                for file_group in file_groups:\n                    for group in file_group.all_groups():\n                        if group.token == call.token:\n                            variable.points_to = group\n            else:\n                assert isinstance(variable.points_to, (Node, Group))\n\n    def to_dot(self):\n        \"\"\"\n        Output for graphviz (.dot) files\n        :rtype: str\n        \"\"\"\n        attributes = {\n            'label': self.label(),\n            'name': self.name(),\n            'shape': \"rect\",\n            'style': 'rounded,filled',\n            'fillcolor': NODE_COLOR,\n        }\n        if self.is_trunk:\n            attributes['fillcolor'] = TRUNK_COLOR\n        elif self.is_leaf:\n            attributes['fillcolor'] = LEAF_COLOR\n\n        ret = self.uid + ' ['\n        for k, v in attributes.items():\n            ret += f'{k}=\"{v}\" '\n        ret += ']'\n        return ret\n\n    def to_dict(self):\n        \"\"\"\n        Output for json files (json graph specification)\n        :rtype: dict\n        \"\"\"\n        return {\n            'uid': self.uid,\n            'label': self.label(),\n            'name': self.name(),\n        }\n\n\ndef _wrap_as_variables(sequence):\n    \"\"\"\n    Given a list of either Nodes or Groups, wrap them in variables.\n    This is used in the get_variables method to allow all defined\n    functions and classes to be defined as variables\n    :param list[Group|Node] sequence:\n    :rtype: list[Variable]\n    \"\"\"\n    return [Variable(el.token, el, el.line_number) for el in sequence]\n\n\nclass Edge():\n    def __init__(self, node0, node1):\n        self.node0 = node0\n        self.node1 = node1\n\n        # When we draw the edge, we know the calling function is definitely not a leaf...\n        # and the called function is definitely not a trunk\n        node0.is_leaf = False\n        node1.is_trunk = False\n\n    def __repr__(self):\n        return f\"<Edge {self.node0} -> {self.node1}\"\n\n    def __lt__(self, other):\n        if self.node0 == other.node0:\n            return self.node1 < other.node1\n        return self.node0 < other.node0\n\n    def to_dot(self):\n        '''\n        Returns string format for embedding in a dotfile. Example output:\n        node_uid_a -> node_uid_b [color='#aaa' penwidth='2']\n        :rtype: str\n        '''\n        ret = self.node0.uid + ' -> ' + self.node1.uid\n        source_color = int(self.node0.uid.split(\"_\")[-1], 16) % len(EDGE_COLORS)\n        ret += f' [color=\"{EDGE_COLORS[source_color]}\" penwidth=\"2\"]'\n        return ret\n\n    def to_dict(self):\n        \"\"\"\n        :rtype: dict\n        \"\"\"\n        return {\n            'source': self.node0.uid,\n            'target': self.node1.uid,\n            'directed': True,\n        }\n\n\nclass Group():\n    \"\"\"\n    Groups represent namespaces (classes and modules/files)\n    \"\"\"\n    def __init__(self, token, group_type, display_type, import_tokens=None,\n                 line_number=None, parent=None, inherits=None):\n        self.token = token\n        self.line_number = line_number\n        self.nodes = []\n        self.root_node = None\n        self.subgroups = []\n        self.parent = parent\n        self.group_type = group_type\n        self.display_type = display_type\n        self.import_tokens = import_tokens or []\n        self.inherits = inherits or []\n        assert group_type in GROUP_TYPE\n\n        self.uid = \"cluster_\" + os.urandom(4).hex()  # group doesn't work by syntax rules\n\n    def __repr__(self):\n        return f\"<Group token={self.token} type={self.display_type}>\"\n\n    def __lt__(self, other):\n        return self.label() < other.label()\n\n    def label(self):\n        \"\"\"\n        Labels are what you see on the graph\n        :rtype: str\n        \"\"\"\n        return f\"{self.display_type}: {self.token}\"\n\n    def filename(self):\n        \"\"\"\n        The ultimate filename of this group.\n        :rtype: str\n        \"\"\"\n        if self.group_type == GROUP_TYPE.FILE:\n            return self.token\n        return self.parent.filename()\n\n    def add_subgroup(self, sg):\n        \"\"\"\n        Subgroups are found after initialization. This is how they are added.\n        :param sg Group:\n        \"\"\"\n        self.subgroups.append(sg)\n\n    def add_node(self, node, is_root=False):\n        \"\"\"\n        Nodes are found after initialization. This is how they are added.\n        :param node Node:\n        :param is_root bool:\n        \"\"\"\n        self.nodes.append(node)\n        if is_root:\n            self.root_node = node\n\n    def all_nodes(self):\n        \"\"\"\n        List of nodes that are part of this group + all subgroups\n        :rtype: list[Node]\n        \"\"\"\n        ret = list(self.nodes)\n        for subgroup in self.subgroups:\n            ret += subgroup.all_nodes()\n        return ret\n\n    def get_constructor(self):\n        \"\"\"\n        Return the first constructor for this group - if any\n        TODO, this excludes the possibility of multiple constructors like\n        __init__ vs __new__\n        :rtype: Node|None\n        \"\"\"\n        assert self.group_type == GROUP_TYPE.CLASS\n        constructors = [n for n in self.nodes if n.is_constructor]\n        if constructors:\n            return constructors[0]\n\n    def all_groups(self):\n        \"\"\"\n        List of groups that are part of this group + all subgroups\n        :rtype: list[Group]\n        \"\"\"\n        ret = [self]\n        for subgroup in self.subgroups:\n            ret += subgroup.all_groups()\n        return ret\n\n    def get_variables(self, line_number=None):\n        \"\"\"\n        Get in-scope variables from this group.\n        This assumes every variable will be in-scope in nested functions\n        line_number is included for compatibility with Node.get_variables but is not used\n\n        :param int line_number:\n        :rtype: list[Variable]\n        \"\"\"\n\n        if self.root_node:\n            variables = (self.root_node.variables\n                         + _wrap_as_variables(self.subgroups)\n                         + _wrap_as_variables(n for n in self.nodes if n != self.root_node))\n            if any(v.line_number for v in variables):\n                return sorted(variables, key=lambda v: v.line_number, reverse=True)\n            return variables\n        else:\n            return []\n\n    def remove_from_parent(self):\n        \"\"\"\n        Remove this group from it's parent. This is effectively a deletion\n        :rtype: None\n        \"\"\"\n        if self.parent:\n            self.parent.subgroups = [g for g in self.parent.subgroups if g != self]\n\n    def all_parents(self):\n        \"\"\"\n        Recursively get the entire inheritance tree of this group\n        :rtype: list[Group]\n        \"\"\"\n        if self.parent:\n            return [self.parent] + self.parent.all_parents()\n        return []\n\n    def to_dot(self):\n        \"\"\"\n        Returns string format for embedding in a dotfile. Example output:\n        subgraph group_uid_a {\n            node_uid_b node_uid_c;\n            label='class_name';\n            ...\n            subgraph group_uid_z {\n                ...\n            }\n            ...\n        }\n        :rtype: str\n        \"\"\"\n\n        ret = 'subgraph ' + self.uid + ' {\\n'\n        if self.nodes:\n            ret += '    '\n            ret += ' '.join(node.uid for node in self.nodes)\n            ret += ';\\n'\n        attributes = {\n            'label': self.label(),\n            'name': self.token,\n            'style': 'filled',\n        }\n        for k, v in attributes.items():\n            ret += f'    {k}=\"{v}\";\\n'\n        ret += '    graph[style=dotted];\\n'\n        for subgroup in self.subgroups:\n            ret += '    ' + ('\\n'.join('    ' + ln for ln in\n                                       subgroup.to_dot().split('\\n'))).strip() + '\\n'\n        ret += '};\\n'\n        return ret\n"
  },
  {
    "path": "code2flow/package.json",
    "content": "{\n  \"dependencies\": {\n    \"acorn\": \"^8.6.0\"\n  }\n}\n"
  },
  {
    "path": "code2flow/php.py",
    "content": "import json\nimport os\nimport subprocess\n\nfrom .model import (Group, Node, Call, Variable, BaseLanguage,\n                    OWNER_CONST, GROUP_TYPE, is_installed, flatten, djoin)\n\n\ndef lineno(tree):\n    \"\"\"\n    Return the line number of the AST\n    :param tree ast:\n    :rtype: int\n    \"\"\"\n    return tree['attributes']['startLine']\n\n\ndef get_name(tree, from_='name'):\n    \"\"\"\n    Get the name (token) of the AST node.\n    :param tree ast:\n    :rtype: str|None\n    \"\"\"\n    # return tree['name']['name']\n    if 'name' in tree and isinstance(tree['name'], str):\n        return tree['name']\n\n    if 'parts' in tree:\n        return djoin(tree['parts'])\n\n    if from_ in tree:\n        return get_name(tree[from_])\n\n    return None\n\n\ndef get_call_from_expr(func_expr):\n    \"\"\"\n    Given an ast that represents a send call, clear and create our\n    generic Call object. Some calls have no chance at resolution (e.g. array[2](param))\n    so we return nothing instead.\n\n    :param func_expr ast:\n    :rtype: Call|None\n    \"\"\"\n    if func_expr['nodeType'] == 'Expr_FuncCall':\n        token = get_name(func_expr)\n        owner_token = None\n    elif func_expr['nodeType'] == 'Expr_New' and func_expr['class'].get('parts'):\n        token = '__construct'\n        owner_token = get_name(func_expr['class'])\n    elif func_expr['nodeType'] == 'Expr_MethodCall':\n        # token = func_expr['name']['name']\n        token = get_name(func_expr)\n        if 'var' in func_expr['var']:\n            owner_token = OWNER_CONST.UNKNOWN_VAR\n        else:\n            owner_token = get_name(func_expr['var'])\n    elif func_expr['nodeType'] == 'Expr_BinaryOp_Concat' and func_expr['right']['nodeType'] == 'Expr_FuncCall':\n        token = get_name(func_expr['right'])\n        if 'class' in func_expr['left']:\n            owner_token = get_name(func_expr['left']['class'])\n        else:\n            owner_token = get_name(func_expr['left'])\n    elif func_expr['nodeType'] == 'Expr_StaticCall':\n        token = get_name(func_expr)\n        owner_token = get_name(func_expr['class'])\n    else:\n        return None\n\n    if owner_token and token == '__construct':\n        # Taking out owner_token for constructors as a little hack to make it work\n        return Call(token=owner_token,\n                    line_number=lineno(func_expr))\n    ret = Call(token=token,\n               owner_token=owner_token,\n               line_number=lineno(func_expr))\n    return ret\n\n\ndef walk(tree):\n    \"\"\"\n    Given an ast tree walk it to get every node. For PHP, the exception\n    is that we return Expr_BinaryOp_Concat which has internal nodes but\n    is important to process as a whole.\n\n    :param tree_el ast:\n    :rtype: list[ast]\n    \"\"\"\n\n    if isinstance(tree, list):\n        ret = []\n        for el in tree:\n            if isinstance(el, dict) and el.get('nodeType'):\n                ret += walk(el)\n        return ret\n\n    assert isinstance(tree, dict)\n    assert tree['nodeType']\n    ret = [tree]\n\n    if tree['nodeType'] == 'Expr_BinaryOp_Concat':\n        return ret\n\n    for v in tree.values():\n        if isinstance(v, list) or (isinstance(v, dict) and v.get('nodeType')):\n            ret += walk(v)\n    return ret\n\n\ndef children(tree):\n    \"\"\"\n    Given an ast tree get all children. For PHP, children are anything\n    with a nodeType.\n\n    :param tree_el ast:\n    :rtype: list[ast]\n    \"\"\"\n    assert isinstance(tree, dict)\n    ret = []\n    for v in tree.values():\n        if isinstance(v, list):\n            for el in v:\n                if isinstance(el, dict) and el.get('nodeType'):\n                    ret.append(el)\n        elif isinstance(v, dict) and v.get('nodeType'):\n            ret.append(v)\n    return ret\n\n\ndef make_calls(body_el):\n    \"\"\"\n    Given a list of lines, find all calls in this list.\n\n    :param body_el ast:\n    :rtype: list[Call]\n    \"\"\"\n    calls = []\n    for expr in walk(body_el):\n        call = get_call_from_expr(expr)\n        calls.append(call)\n    ret = list(filter(None, calls))\n\n    return ret\n\n\ndef process_assign(assignment_el):\n    \"\"\"\n    Given an assignment statement, return a\n    Variable that points_to the type of object being assigned.\n\n    :param assignment_el ast:\n    :rtype: Variable\n    \"\"\"\n    assert assignment_el['nodeType'] == 'Expr_Assign'\n    if 'name' not in assignment_el['var']:\n        return None\n\n    varname = assignment_el['var']['name']\n    call = get_call_from_expr(assignment_el['expr'])\n    if call:\n        return Variable(varname, call, line_number=lineno(assignment_el))\n    # else is something like `varname = num`\n    return None\n\n\ndef make_local_variables(tree_el, parent):\n    \"\"\"\n    Given an ast of all the lines in a function, generate a list of\n    variables in that function. Variables are tokens and what they link to.\n\n    :param tree_el ast:\n    :param parent Group:\n    :rtype: list[Variable]\n    \"\"\"\n    variables = []\n    for el in walk(tree_el):\n        if el['nodeType'] == 'Expr_Assign':\n            variables.append(process_assign(el))\n        if el['nodeType'] == 'Stmt_Use':\n            for use in el['uses']:\n                owner_token = djoin(use['name']['parts'])\n                token = use['alias']['name'] if use['alias'] else owner_token\n                variables.append(Variable(token, points_to=owner_token,\n                                          line_number=lineno(el)))\n\n    # Make a 'this'/'self' variable for use anywhere we need it that points to the class\n    if isinstance(parent, Group) and parent.group_type in GROUP_TYPE.CLASS:\n        variables.append(Variable('this', parent, line_number=parent.line_number))\n        variables.append(Variable('self', parent, line_number=parent.line_number))\n\n    return list(filter(None, variables))\n\n\ndef get_inherits(tree):\n    \"\"\"\n    Get the various types of inheritances this class/namespace/trait can have\n\n    :param tree ast:\n    :rtype: list[str]\n    \"\"\"\n    ret = []\n\n    if tree.get('extends', {}):\n        ret.append(djoin(tree['extends']['parts']))\n\n    for stmt in tree.get('stmts', []):\n        if stmt['nodeType'] == 'Stmt_TraitUse':\n            for trait in stmt['traits']:\n                ret.append(djoin(trait['parts']))\n    return ret\n\n\ndef run_ast_parser(filename):\n    \"\"\"\n    Parse the php file and return the output + the returncode\n    Separate function b/c unittesting and asserting php-parser installation.\n    :param filename str:\n    :type: str, int\n    \"\"\"\n\n    script_loc = os.path.join(os.path.dirname(os.path.realpath(__file__)),\n                              \"get_ast.php\")\n    cmd = [\"php\", script_loc, filename]\n    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    return proc.communicate()[0], proc.returncode\n\n\nclass PHP(BaseLanguage):\n    @staticmethod\n    def assert_dependencies():\n        \"\"\"Assert that php and php-parser are installed\"\"\"\n        assert is_installed('php'), \"No php installation could be found\"\n        self_ref = os.path.join(os.path.dirname(os.path.realpath(__file__)),\n                                \"get_ast.php\")\n        outp, returncode = run_ast_parser(self_ref)\n        path = os.path.dirname(os.path.realpath(__file__))\n        assert_msg = 'Error running the PHP parser. From the `%s` directory, run ' \\\n                     '`composer require nikic/php-parser \"^4.10\"`.' % path\n        assert not returncode, assert_msg\n        return outp\n\n    @staticmethod\n    def get_tree(filename, lang_params):\n        \"\"\"\n        Get the entire AST for this file\n\n        :param filename str:\n        :param lang_params LanguageParams:\n        :rtype: ast\n        \"\"\"\n\n        outp, returncode = run_ast_parser(filename)\n        if returncode:\n            raise AssertionError(\n                \"Could not parse file %r. You may have a syntax error. \"\n                \"For more detail, try running with `php %s`. \" %\n                (filename, filename))\n\n        tree = json.loads(outp)\n        assert isinstance(tree, list)\n        if len(tree) == 1 and tree[0]['nodeType'] == 'Stmt_InlineHTML':\n            raise AssertionError(\"Tried to parse a file that is not likely PHP\")\n        return tree\n\n    @staticmethod\n    def separate_namespaces(tree):\n        \"\"\"\n        Given a tree element, recursively separate that AST into lists of ASTs for the\n        subgroups, nodes, and body. This is an intermediate step to allow for\n        cleaner processing downstream\n\n        :param tree ast:\n        :returns: tuple of group, node, and body trees. These are processed\n                  downstream into real Groups and Nodes.\n        :rtype: (list[ast], list[ast], list[ast])\n        \"\"\"\n        tree = tree or []  # if its abstract, it comes in with no body\n\n        groups = []\n        nodes = []\n        body = []\n        for el in tree:\n            if el['nodeType'] in ('Stmt_Function', 'Stmt_ClassMethod', 'Expr_Closure'):\n                nodes.append(el)\n            elif el['nodeType'] in ('Stmt_Class', 'Stmt_Namespace', 'Stmt_Trait'):\n                groups.append(el)\n            else:\n                tup = PHP.separate_namespaces(children(el))\n                if tup[0] or tup[1]:\n                    groups += tup[0]\n                    nodes += tup[1]\n                    body += tup[2]\n                else:\n                    body.append(el)\n        return groups, nodes, body\n\n    @staticmethod\n    def make_nodes(tree, parent):\n        \"\"\"\n        Given a tree element of all the lines in a function, create the node along\n        with the calls and variables internal to it.\n        Also make the nested subnodes\n\n        :param tree ast:\n        :param parent Group:\n        :rtype: list[Node]\n        \"\"\"\n        assert tree['nodeType'] in ('Stmt_Function', 'Stmt_ClassMethod', 'Expr_Closure')\n\n        if tree['nodeType'] == 'Expr_Closure':\n            token = '(Closure)'\n        else:\n            token = tree['name']['name']\n        is_constructor = token == '__construct' and parent.group_type == GROUP_TYPE.CLASS\n\n        tree_body = tree['stmts']\n        subgroup_trees, subnode_trees, this_scope_body = PHP.separate_namespaces(tree_body)\n        assert not subgroup_trees\n        calls = make_calls(this_scope_body)\n        variables = make_local_variables(this_scope_body, parent)\n\n        if parent.group_type == GROUP_TYPE.CLASS and parent.parent.group_type == GROUP_TYPE.NAMESPACE:\n            import_tokens = [djoin(parent.parent.token, parent.token, token)]\n        if parent.group_type in (GROUP_TYPE.NAMESPACE, GROUP_TYPE.CLASS):\n            import_tokens = [djoin(parent.token, token)]\n        else:\n            import_tokens = [token]\n\n        node = Node(token, calls, variables, parent, import_tokens=import_tokens,\n                    is_constructor=is_constructor, line_number=lineno(tree))\n\n        subnodes = flatten([PHP.make_nodes(t, parent) for t in subnode_trees])\n        return [node] + subnodes\n\n    @staticmethod\n    def make_root_node(lines, parent):\n        \"\"\"\n        The \"root_node\" is an implict node of lines which are executed in the global\n        scope on the file itself and not otherwise part of any function.\n\n        :param lines list[ast]:\n        :param parent Group:\n        :rtype: Node\n        \"\"\"\n        token = \"(global)\"\n        line_number = lineno(lines[0]) if lines else 0\n        calls = make_calls(lines)\n        variables = make_local_variables(lines, parent)\n        root_node = Node(token, calls, variables, parent,\n                         line_number=line_number)\n        return root_node\n\n    @staticmethod\n    def make_class_group(tree, parent):\n        \"\"\"\n        Given an AST for the subgroup (a class), generate that subgroup.\n        In this function, we will also need to generate all of the nodes internal\n        to the group.\n\n        Specific to PHP, this can also be a namespace or class.\n\n        :param tree ast:\n        :param parent Group:\n        :rtype: Group\n        \"\"\"\n        assert tree['nodeType'] in ('Stmt_Class', 'Stmt_Namespace', 'Stmt_Trait')\n        subgroup_trees, node_trees, body_trees = PHP.separate_namespaces(tree['stmts'])\n\n        token = get_name(tree['name'])\n        display_type = tree['nodeType'][5:]\n\n        inherits = get_inherits(tree)\n\n        group_type = GROUP_TYPE.CLASS\n        if display_type == 'Namespace':\n            group_type = GROUP_TYPE.NAMESPACE\n\n        import_tokens = [token]\n        if display_type == 'Class' and parent.group_type == GROUP_TYPE.NAMESPACE:\n            import_tokens = [djoin(parent.token, token)]\n\n        class_group = Group(token, group_type, display_type, import_tokens=import_tokens,\n                            parent=parent, inherits=inherits, line_number=lineno(tree))\n\n        for subgroup_tree in subgroup_trees:\n            class_group.add_subgroup(PHP.make_class_group(subgroup_tree, class_group))\n\n        for node_tree in node_trees:\n            for new_node in PHP.make_nodes(node_tree, parent=class_group):\n                class_group.add_node(new_node)\n\n        if group_type == GROUP_TYPE.NAMESPACE:\n            class_group.add_node(PHP.make_root_node(body_trees, class_group))\n            for node in class_group.nodes:\n                node.variables += [Variable(n.token, n, line_number=n.line_number)\n                                   for n in class_group.nodes]\n\n        return class_group\n\n    @staticmethod\n    def file_import_tokens(filename):\n        \"\"\"\n        Returns the token(s) we would use if importing this file from another.\n\n        :param filename str:\n        :rtype: list[str]\n        \"\"\"\n        return []\n"
  },
  {
    "path": "code2flow/python.py",
    "content": "import ast\nimport logging\nimport os\n\nfrom .model import (OWNER_CONST, GROUP_TYPE, Group, Node, Call, Variable,\n                    BaseLanguage, djoin)\n\n\ndef get_call_from_func_element(func):\n    \"\"\"\n    Given a python ast that represents a function call, clear and create our\n    generic Call object. Some calls have no chance at resolution (e.g. array[2](param))\n    so we return nothing instead.\n\n    :param func ast:\n    :rtype: Call|None\n    \"\"\"\n    assert type(func) in (ast.Attribute, ast.Name, ast.Subscript, ast.Call)\n    if type(func) == ast.Attribute:\n        owner_token = []\n        val = func.value\n        while True:\n            try:\n                owner_token.append(getattr(val, 'attr', val.id))\n            except AttributeError:\n                pass\n            val = getattr(val, 'value', None)\n            if not val:\n                break\n        if owner_token:\n            owner_token = djoin(*reversed(owner_token))\n        else:\n            owner_token = OWNER_CONST.UNKNOWN_VAR\n        return Call(token=func.attr, line_number=func.lineno, owner_token=owner_token)\n    if type(func) == ast.Name:\n        return Call(token=func.id, line_number=func.lineno)\n    if type(func) in (ast.Subscript, ast.Call):\n        return None\n\n\ndef make_calls(lines):\n    \"\"\"\n    Given a list of lines, find all calls in this list.\n\n    :param lines list[ast]:\n    :rtype: list[Call]\n    \"\"\"\n\n    calls = []\n    for tree in lines:\n        for element in ast.walk(tree):\n            if type(element) != ast.Call:\n                continue\n            call = get_call_from_func_element(element.func)\n            if call:\n                calls.append(call)\n    return calls\n\n\ndef process_assign(element):\n    \"\"\"\n    Given an element from the ast which is an assignment statement, return a\n    Variable that points_to the type of object being assigned. For now, the\n    points_to is a string but that is resolved later.\n\n    :param element ast:\n    :rtype: Variable\n    \"\"\"\n\n    if type(element.value) != ast.Call:\n        return []\n    call = get_call_from_func_element(element.value.func)\n    if not call:\n        return []\n\n    ret = []\n    for target in element.targets:\n        if type(target) != ast.Name:\n            continue\n        token = target.id\n        ret.append(Variable(token, call, element.lineno))\n    return ret\n\n\ndef process_import(element):\n    \"\"\"\n    Given an element from the ast which is an import statement, return a\n    Variable that points_to the module being imported. For now, the\n    points_to is a string but that is resolved later.\n\n    :param element ast:\n    :rtype: Variable\n    \"\"\"\n    ret = []\n\n    for single_import in element.names:\n        assert isinstance(single_import, ast.alias)\n        token = single_import.asname or single_import.name\n        rhs = single_import.name\n\n        if hasattr(element, 'module') and element.module:\n            rhs = djoin(element.module, rhs)\n        ret.append(Variable(token, points_to=rhs, line_number=element.lineno))\n    return ret\n\n\ndef make_local_variables(lines, parent):\n    \"\"\"\n    Given an ast of all the lines in a function, generate a list of\n    variables in that function. Variables are tokens and what they link to.\n    In this case, what it links to is just a string. However, that is resolved\n    later.\n\n    :param lines list[ast]:\n    :param parent Group:\n    :rtype: list[Variable]\n    \"\"\"\n    variables = []\n    for tree in lines:\n        for element in ast.walk(tree):\n            if type(element) == ast.Assign:\n                variables += process_assign(element)\n            if type(element) in (ast.Import, ast.ImportFrom):\n                variables += process_import(element)\n    if parent.group_type == GROUP_TYPE.CLASS:\n        variables.append(Variable('self', parent, lines[0].lineno))\n\n    variables = list(filter(None, variables))\n    return variables\n\n\ndef get_inherits(tree):\n    \"\"\"\n    Get what superclasses this class inherits\n    This handles exact names like 'MyClass' but skips things like 'cls' and 'mod.MyClass'\n    Resolving those would be difficult\n    :param tree ast:\n    :rtype: list[str]\n    \"\"\"\n    return [base.id for base in tree.bases if type(base) == ast.Name]\n\n\nclass Python(BaseLanguage):\n    @staticmethod\n    def assert_dependencies():\n        pass\n\n    @staticmethod\n    def get_tree(filename, _):\n        \"\"\"\n        Get the entire AST for this file\n\n        :param filename str:\n        :rtype: ast\n        \"\"\"\n        try:\n            with open(filename) as f:\n                raw = f.read()\n        except ValueError:\n            with open(filename, encoding='UTF-8') as f:\n                raw = f.read()\n        return ast.parse(raw)\n\n    @staticmethod\n    def separate_namespaces(tree):\n        \"\"\"\n        Given an AST, recursively separate that AST into lists of ASTs for the\n        subgroups, nodes, and body. This is an intermediate step to allow for\n        cleaner processing downstream\n\n        :param tree ast:\n        :returns: tuple of group, node, and body trees. These are processed\n                  downstream into real Groups and Nodes.\n        :rtype: (list[ast], list[ast], list[ast])\n        \"\"\"\n        groups = []\n        nodes = []\n        body = []\n        for el in tree.body:\n            if type(el) in (ast.FunctionDef, ast.AsyncFunctionDef):\n                nodes.append(el)\n            elif type(el) == ast.ClassDef:\n                groups.append(el)\n            elif getattr(el, 'body', None):\n                tup = Python.separate_namespaces(el)\n                groups += tup[0]\n                nodes += tup[1]\n                body += tup[2]\n            else:\n                body.append(el)\n        return groups, nodes, body\n\n    @staticmethod\n    def make_nodes(tree, parent):\n        \"\"\"\n        Given an ast of all the lines in a function, create the node along with the\n        calls and variables internal to it.\n\n        :param tree ast:\n        :param parent Group:\n        :rtype: list[Node]\n        \"\"\"\n        token = tree.name\n        line_number = tree.lineno\n        calls = make_calls(tree.body)\n        variables = make_local_variables(tree.body, parent)\n        is_constructor = False\n        if parent.group_type == GROUP_TYPE.CLASS and token in ['__init__', '__new__']:\n            is_constructor = True\n\n        import_tokens = []\n        if parent.group_type == GROUP_TYPE.FILE:\n            import_tokens = [djoin(parent.token, token)]\n\n        return [Node(token, calls, variables, parent, import_tokens=import_tokens,\n                     line_number=line_number, is_constructor=is_constructor)]\n\n    @staticmethod\n    def make_root_node(lines, parent):\n        \"\"\"\n        The \"root_node\" is an implict node of lines which are executed in the global\n        scope on the file itself and not otherwise part of any function.\n\n        :param lines list[ast]:\n        :param parent Group:\n        :rtype: Node\n        \"\"\"\n        token = \"(global)\"\n        line_number = 0\n        calls = make_calls(lines)\n        variables = make_local_variables(lines, parent)\n        return Node(token, calls, variables, line_number=line_number, parent=parent)\n\n    @staticmethod\n    def make_class_group(tree, parent):\n        \"\"\"\n        Given an AST for the subgroup (a class), generate that subgroup.\n        In this function, we will also need to generate all of the nodes internal\n        to the group.\n\n        :param tree ast:\n        :param parent Group:\n        :rtype: Group\n        \"\"\"\n        assert type(tree) == ast.ClassDef\n        subgroup_trees, node_trees, body_trees = Python.separate_namespaces(tree)\n\n        group_type = GROUP_TYPE.CLASS\n        token = tree.name\n        display_name = 'Class'\n        line_number = tree.lineno\n\n        import_tokens = [djoin(parent.token, token)]\n        inherits = get_inherits(tree)\n\n        class_group = Group(token, group_type, display_name, import_tokens=import_tokens,\n                            inherits=inherits, line_number=line_number, parent=parent)\n\n        for node_tree in node_trees:\n            class_group.add_node(Python.make_nodes(node_tree, parent=class_group)[0])\n\n        for subgroup_tree in subgroup_trees:\n            logging.warning(\"Code2flow does not support nested classes. Skipping %r in %r.\",\n                            subgroup_tree.name, parent.token)\n        return class_group\n\n    @staticmethod\n    def file_import_tokens(filename):\n        \"\"\"\n        Returns the token(s) we would use if importing this file from another.\n\n        :param filename str:\n        :rtype: list[str]\n        \"\"\"\n        return [os.path.split(filename)[-1].rsplit('.py', 1)[0]]\n"
  },
  {
    "path": "code2flow/ruby.py",
    "content": "import json\nimport subprocess\n\nfrom .model import (Group, Node, Call, Variable, BaseLanguage,\n                    OWNER_CONST, GROUP_TYPE, is_installed, flatten)\n\n\ndef resolve_owner(owner_el):\n    \"\"\"\n    Resolve who owns the call, if anyone.\n    So if the expression is i_ate.pie(). And i_ate is a Person, the callee is Person.\n    This is returned as a string and eventually set to the owner_token in the call\n\n    :param owner_el ast:\n    :rtype: str|None\n    \"\"\"\n    if not owner_el or not isinstance(owner_el, list):\n        return None\n    if owner_el[0] == 'begin':\n        # skip complex ownership\n        return OWNER_CONST.UNKNOWN_VAR\n    if owner_el[0] == 'send':\n        # sends are complex too\n        return OWNER_CONST.UNKNOWN_VAR\n    if owner_el[0] == 'lvar':\n        # var.func()\n        return owner_el[1]\n    if owner_el[0] == 'ivar':\n        # @var.func()\n        return owner_el[1]\n    if owner_el[0] == 'self':\n        return 'self'\n    if owner_el[0] == 'const':\n        return owner_el[2]\n\n    return OWNER_CONST.UNKNOWN_VAR\n\n\ndef get_call_from_send_el(func_el):\n    \"\"\"\n    Given an ast that represents a send call, clear and create our\n    generic Call object. Some calls have no chance at resolution (e.g. array[2](param))\n    so we return nothing instead.\n\n    :param func_el ast:\n    :rtype: Call|None\n    \"\"\"\n    owner_el = func_el[1]\n    token = func_el[2]\n    owner = resolve_owner(owner_el)\n    if owner and token == 'new':\n        # Taking out owner_token for constructors as a little hack to make it work\n        return Call(token=owner)\n    return Call(token=token,\n                owner_token=owner)\n\n\ndef walk(tree_el):\n    \"\"\"\n    Given an ast element (list), walk it in a dfs to get every el (list) out of it\n\n    :param tree_el ast:\n    :rtype: list[ast]\n    \"\"\"\n\n    if not tree_el:\n        return []\n    ret = [tree_el]\n    for el in tree_el:\n        if isinstance(el, list):\n            ret += walk(el)\n    return ret\n\n\ndef make_calls(body_el):\n    \"\"\"\n    Given a list of lines, find all calls in this list.\n\n    :param body_el ast:\n    :rtype: list[Call]\n    \"\"\"\n    calls = []\n    for el in walk(body_el):\n        if el[0] == 'send':\n            calls.append(get_call_from_send_el(el))\n    return calls\n\n\ndef process_assign(assignment_el):\n    \"\"\"\n    Given an assignment statement, return a\n    Variable that points_to the type of object being assigned. The\n    points_to is often a string but that is resolved later.\n\n    :param assignment_el ast:\n    :rtype: Variable\n    \"\"\"\n\n    assert assignment_el[0] == 'lvasgn'\n    varname = assignment_el[1]\n    if assignment_el[2][0] == 'send':\n        call = get_call_from_send_el(assignment_el[2])\n        return Variable(varname, call)\n    # else is something like `varname = num`\n    return None\n\n\ndef make_local_variables(tree_el, parent):\n    \"\"\"\n    Given an ast of all the lines in a function, generate a list of\n    variables in that function. Variables are tokens and what they link to.\n    In this case, what it links to is just a string. However, that is resolved\n    later.\n\n    Also return variables for the outer scope parent\n\n    :param tree_el ast:\n    :param parent Group:\n    :rtype: list[Variable]\n    \"\"\"\n    variables = []\n    for el in tree_el:\n        if el[0] == 'lvasgn':\n            variables.append(process_assign(el))\n\n    # Make a 'self' variable for use anywhere we need it that points to the class\n    if isinstance(parent, Group) and parent.group_type == GROUP_TYPE.CLASS:\n        variables.append(Variable('self', parent))\n\n    variables = list(filter(None, variables))\n    return variables\n\n\ndef as_lines(tree_el):\n    \"\"\"\n    Ruby ast bodies are structured differently depending on circumstances.\n    This ensures that they are structured as a list of statements\n\n    :param tree_el ast:\n    :rtype: list[tree_el]\n    \"\"\"\n    if not tree_el:\n        return []\n    if isinstance(tree_el[0], list):\n        return tree_el\n    if tree_el[0] == 'begin':\n        return tree_el\n    return [tree_el]\n\n\ndef get_tree_body(tree_el):\n    \"\"\"\n    Depending on the type of element, get the body of that element\n\n    :param tree_el ast:\n    :rtype: list[tree_el]\n    \"\"\"\n\n    if tree_el[0] == 'module':\n        body_struct = tree_el[2]\n    elif tree_el[0] == 'defs':\n        body_struct = tree_el[4]\n    else:\n        body_struct = tree_el[3]\n    return as_lines(body_struct)\n\n\ndef get_inherits(tree, body_tree):\n    \"\"\"\n    Get the various types of inheritances this class/module can have\n\n    :param tree ast:\n    :param body_tree list[ast]\n    :rtype: list[str]\n    \"\"\"\n    inherits = []\n\n    # extends\n    if tree[0] == 'class' and tree[2]:\n        inherits.append(tree[2][2])\n\n    # module automatically extends same-named modules\n    if tree[0] == 'module':\n        inherits.append(tree[1][2])\n\n    # mixins\n    for el in body_tree:\n        if el[0] == 'send' and el[2] == 'include':\n            inherits.append(el[3][2])\n\n    return inherits\n\n\nclass Ruby(BaseLanguage):\n    @staticmethod\n    def assert_dependencies():\n        \"\"\"Assert that ruby-parse is installed\"\"\"\n        assert is_installed('ruby-parse'), \"The 'parser' gem is requred to \" \\\n                                           \"parse ruby files but was not found \" \\\n                                           \"on the path. Install it from gem \" \\\n                                           \"and try again.\"\n\n    @staticmethod\n    def get_tree(filename, lang_params):\n        \"\"\"\n        Get the entire AST for this file\n\n        :param filename str:\n        :param lang_params LanguageParams:\n        :rtype: ast\n        \"\"\"\n        version_flag = \"--\" + lang_params.ruby_version\n        cmd = [\"ruby-parse\", \"--emit-json\", version_flag, filename]\n        output = subprocess.check_output(cmd, stderr=subprocess.PIPE)\n        try:\n            tree = json.loads(output)\n        except json.decoder.JSONDecodeError:\n            raise AssertionError(\n                \"Ruby-parse could not parse file %r. You may have a syntax error. \"\n                \"For more detail, try running the command `ruby-parse %s`. \" %\n                (filename, filename)) from None\n        assert isinstance(tree, list)\n\n        if tree[0] not in ('module', 'begin'):\n            # one-line files\n            tree = [tree]\n        return tree\n\n    @staticmethod\n    def separate_namespaces(tree):\n        \"\"\"\n        Given a tree element, recursively separate that AST into lists of ASTs for the\n        subgroups, nodes, and body. This is an intermediate step to allow for\n        cleaner processing downstream\n\n        :param tree ast:\n        :returns: tuple of group, node, and body trees. These are processed\n                  downstream into real Groups and Nodes.\n        :rtype: (list[ast], list[ast], list[ast])\n        \"\"\"\n        groups = []\n        nodes = []\n        body = []\n        for el in as_lines(tree):\n            if el[0] in ('def', 'defs'):\n                nodes.append(el)\n            elif el[0] in ('class', 'module'):\n                groups.append(el)\n            else:\n                body.append(el)\n        return groups, nodes, body\n\n    @staticmethod\n    def make_nodes(tree, parent):\n        \"\"\"\n        Given a tree element of all the lines in a function, create the node along\n        with the calls and variables internal to it.\n        Also make the nested subnodes\n\n        :param tree ast:\n        :param parent Group:\n        :rtype: list[Node]\n        \"\"\"\n        if tree[0] == 'defs':\n            token = tree[2]  # def self.func\n        else:\n            token = tree[1]  # def func\n\n        is_constructor = token == 'initialize' and parent.group_type == GROUP_TYPE.CLASS\n\n        tree_body = get_tree_body(tree)\n        subgroup_trees, subnode_trees, this_scope_body = Ruby.separate_namespaces(tree_body)\n        assert not subgroup_trees\n        calls = make_calls(this_scope_body)\n        variables = make_local_variables(this_scope_body, parent)\n        node = Node(token, calls, variables,\n                    parent=parent, is_constructor=is_constructor)\n\n        # This is a little different from the other languages in that\n        # the node is now on the parent\n        subnodes = flatten([Ruby.make_nodes(t, parent) for t in subnode_trees])\n        return [node] + subnodes\n\n    @staticmethod\n    def make_root_node(lines, parent):\n        \"\"\"\n        The \"root_node\" is an implict node of lines which are executed in the global\n        scope on the file itself and not otherwise part of any function.\n\n        :param lines list[ast]:\n        :param parent Group:\n        :rtype: Node\n        \"\"\"\n        token = \"(global)\"\n        calls = make_calls(lines)\n        variables = make_local_variables(lines, parent)\n        root_node = Node(token, calls, variables, parent=parent)\n        return root_node\n\n    @staticmethod\n    def make_class_group(tree, parent):\n        \"\"\"\n        Given an AST for the subgroup (a class), generate that subgroup.\n        In this function, we will also need to generate all of the nodes internal\n        to the group.\n\n        :param tree ast:\n        :param parent Group:\n        :rtype: Group\n        \"\"\"\n        assert tree[0] in ('class', 'module')\n        tree_body = get_tree_body(tree)\n        subgroup_trees, node_trees, body_trees = Ruby.separate_namespaces(tree_body)\n\n        group_type = GROUP_TYPE.CLASS\n        if tree[0] == 'module':\n            group_type = GROUP_TYPE.NAMESPACE\n        display_type = tree[0].capitalize()\n        assert tree[1][0] == 'const'\n        token = tree[1][2]\n\n        inherits = get_inherits(tree, body_trees)\n        class_group = Group(token, group_type, display_type,\n                            inherits=inherits, parent=parent)\n\n        for subgroup_tree in subgroup_trees:\n            class_group.add_subgroup(Ruby.make_class_group(subgroup_tree, class_group))\n\n        for node_tree in node_trees:\n            for new_node in Ruby.make_nodes(node_tree, parent=class_group):\n                class_group.add_node(new_node)\n        for node in class_group.nodes:\n            node.variables += [Variable(n.token, n) for n in class_group.nodes]\n\n        return class_group\n\n    @staticmethod\n    def file_import_tokens(filename):\n        \"\"\"\n        Returns the token(s) we would use if importing this file from another.\n\n        :param filename str:\n        :rtype: list[str]\n        \"\"\"\n        return []\n"
  },
  {
    "path": "make_expected.py",
    "content": "#!/usr/bin/env python3\n\nimport os\nimport pprint\nimport sys\nimport tempfile\n\nfrom code2flow.engine import main\nfrom tests.test_graphs import get_edges_set_from_file, get_nodes_set_from_file\n\nDESCRIPTION = \"\"\"\nThis file is a tool to generate test cases given a directory\n\"\"\"\n\nif __name__ == '__main__':\n    output_filename = tempfile.NamedTemporaryFile(suffix='.gv').name\n    args = sys.argv[1:] + ['--output', output_filename]\n    main(args)\n    output_file = open(output_filename, 'r')\n\n    generated_edges = get_edges_set_from_file(output_file)\n    generated_nodes = get_nodes_set_from_file(output_file)\n    directory = os.path.split(sys.argv[1])[-1]\n\n    ret = {\n        'test_name': directory,\n        'directory': directory,\n        'kwargs': sys.argv[2:],\n        'expected_edges': list(map(list, generated_edges)),\n        'expected_nodes': list(generated_nodes),\n    }\n\n    ret = pprint.pformat(ret, sort_dicts=False)\n    ret = \" \" + ret.replace(\"'\", '\"')[1:-1]\n    print('\\n'.join(\"           \" + l for l in ret.split('\\n')))\n"
  },
  {
    "path": "requirements_dev.txt",
    "content": "# Testing\npytest>=6.2.5,<7.0\npygraphviz>=1.7,<2.0\npytest-cov>=2.10.1,<3.0\npytest-mock>=3.6.1,<4.0\npytest-xdist>=2.5.0,<3.0\n\n# Development\nipdb>=0.13.9,<1.0\nicecream>=2.1.1,<3.0\n"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup\n\nfrom code2flow.engine import VERSION\n\nurl_base = 'https://github.com/scottrogowski/code2flow'\ndownload_url = '%s/archive/code2flow-%s.tar.gz' % (url_base, VERSION)\n\nsetup(\n    name='code2flow',\n    version=VERSION,\n    description='Visualize your source code as DOT flowcharts',\n    long_description=open('README.md', encoding='utf-8').read(),\n    long_description_content_type=\"text/markdown\",\n    entry_points={\n        'console_scripts': ['code2flow=code2flow.engine:main'],\n    },\n    license='MIT',\n    author='Scott Rogowski',\n    author_email='scottmrogowski@gmail.com',\n    url=url_base,\n    download_url=download_url,\n    packages=['code2flow'],\n    python_requires='>=3.6',\n    include_package_data=True,\n    classifiers=[\n        'Natural Language :: English',\n        \"Programming Language :: Python :: 3\",\n        \"License :: OSI Approved :: MIT License\",\n        \"Operating System :: OS Independent\",\n    ]\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_code/js/ambiguous_names/ambiguous_names.js",
    "content": "class Abra {\n    constructor() {\n        this.abra_it();\n        }\n    magic() {\n        console.log('magic 1');\n    }\n\n    abra_it() {\n\n    }\n}\n\n\nclass Cadabra {\n    magic() {\n        console.log('magic 2');\n    }\n\n    cadabra_it(a) {\n        let b = a.abra_it()\n        let c = \"d\"\n    }\n}\n\nfunction main(cls) {\n    obj = cls()\n    obj.magic()\n    obj.cadabra_it()\n}\n\nmain()\n"
  },
  {
    "path": "tests/test_code/js/bad_parse/file_a_good.js",
    "content": "function a() {}\na()\n"
  },
  {
    "path": "tests/test_code/js/bad_parse/file_b_bad.js",
    "content": "function a() {\na)\n"
  },
  {
    "path": "tests/test_code/js/chained/chained.js",
    "content": "class Chain {\n    constructor(val) {\n        this.val = val;\n    }\n\n    add(b) {\n        this.val += b\n        return this;\n    }\n\n    sub(b) {\n        this.val -= b;\n        return this;\n    }\n\n    mul(b) {\n        this.val *= b;\n        return this;\n    }\n}\n\nconsole.log((new Chain(5)).add(5).sub(2).mul(10));\n"
  },
  {
    "path": "tests/test_code/js/class_in_function/class_in_function.js",
    "content": "function rectangleClassFactory() {\n    class Rectangle {\n        constructor(height, width) {\n            this.height = height;\n            this.width = width;\n        }\n    }\n    return Rectangle\n}\n\nrectangleClassFactory()\n"
  },
  {
    "path": "tests/test_code/js/classes/classes.js",
    "content": "function print_hi() {\n    console.log(\"HI\")\n}\n\nclass Rectangle {\n  constructor(height, width) {\n    this.height = height;\n    this.width = width;\n    this.i = 0;\n    print_hi();\n    this.calcArea()\n  }\n  calcArea() {\n    this.incr();\n    return this.height * this.width\n  }\n  incr() {\n    this.i++;\n  }\n}\n\nfunction do_calc() {\n    const the_area = square.calcArea()\n    calcit()\n    const square = new Rectangle(10, 10);\n}\n\nconst do_calc_wrapper = function() {\n    console.log(\"BANANAS\")\n    do_calc();\n}\n\nconst square = new Rectangle(10, 10);\nsquare.calcArea()\ndo_calc_wrapper()\n"
  },
  {
    "path": "tests/test_code/js/complex_ownership/complex_ownership.js",
    "content": "class ABC {\n    constructor() {\n        this.b = 5;\n    }\n    doit() {\n        let _this = this;\n        return _this.b;\n    }\n    apply() {\n        return 5;\n    }\n    ret_def() {\n        return DEF\n    }\n}\n\nclass DEF {\n    toABC() {\n        calls = 5;\n        return new ABC();\n    }\n}\n\nclass GHI {\n    doit2(varname) {\n        return varname.apply()\n    }\n    doit3() {\n        console.log(\"\");\n    }\n}\n\nvar empty_var;\nvar double_decl = [], empty_var;\ncalls = 5;\nlet abc = new DEF();\nabc.toABC().doit(calls);\n\nnew GHI().doit2()\nvar inp = AbsentClass()\ninp.a.b.c.apply(null, arguments);\n\nvar jsism = (function() {\n    return \"no other language does this crazy nonsense\";\n})()\n\nvar jsism_2 = (function() {\n    return \"no other language does this crazy nonsense\";\n}).anything()\n\n\nvar obj_calls = {\n    'abc': ABC,\n    'def': DEF,\n    'ghi': GHI,\n}\nobj_calls['ghi'].doit3();\n\n// This below shouldn't match anything because it's too complex of a constructor\nvar def = new abc.ret_def()\n"
  },
  {
    "path": "tests/test_code/js/exclude_modules/exclude_modules.js",
    "content": "//Node/CommonJS\nconst fs = require('fs')\nconst {readFile, chmod} = require('fs')\n\n\nfunction readFileSync() {\n    console.log(\"This is the local readFileSync\");\n}\n\n\nfunction beta() {\n    print(\"this still connects\")\n    readFileSync()\n    b = Nothing()\n    b.beta()\n    chmod();\n}\n\n\nfunction alpha() {\n    fs.readFileSync(\"exclude_modules.js\");\n    beta()\n    match()\n    alpha()\n}\n\n\nalpha()\nmodule.exports = {alpha}\n"
  },
  {
    "path": "tests/test_code/js/exclude_modules_es6/exclude_modules_es6.js",
    "content": "//Node/CommonJS\nconst fs = import('fs');\nimport {readFile, chmod} from 'fs';\n\n\nfunction readFileSync() {\n    console.log(\"This is the local readFileSync\");\n}\n\n\nfunction beta() {\n    print(\"this still connects\")\n    readFileSync()\n    b = Nothing()\n    b.beta()\n    chmod();\n}\n\n\nfunction alpha() {\n    fs.readFileSync(\"exclude_modules.js\");\n    beta()\n    match()\n    alpha()\n}\n\n\nalpha()\nmodule.exports = {alpha}\n"
  },
  {
    "path": "tests/test_code/js/globals/globals.js",
    "content": "var g = (function() {\n    function a() {\n        function c() {\n            function d() {\n                console.log('d');\n            }\n            d()\n        }\n        b();\n        c();\n    }\n\n    function b() {\n        console.log(\"c\");\n    }\n\n    return {\n        'a': a,\n        'b': b\n        }\n})()\n"
  },
  {
    "path": "tests/test_code/js/inheritance/inheritance.js",
    "content": "// from https://ruby-doc.com/docs/ProgrammingRuby/html/tut_modules.html\n\nfunction majorNum() {}\n\nfunction pentaNum() {}\n\nclass MajorScales {\n  majorNum() {\n    this.numNotes = 7;\n    return this.numNotes;\n  }\n}\n\nclass FakeMajorScales {\n  majorNum() {\n    console.log(\"Not this one\")\n  }\n}\n\nclass ScaleDemo extends MajorScales {\n  constructor() {\n    console.log(this.majorNum())\n  }\n}\n\nlet sd = new ScaleDemo();\npentaNum()\n"
  },
  {
    "path": "tests/test_code/js/inheritance_attr/inheritance_attr.js",
    "content": "\nclass ClsA {\n    bark() {\n        console.log(\"woof\");\n    }\n}\nclass ClsB {\n    meow() {\n        console.log(\"meow\");\n    }\n}\n\nClsA.B = ClsB;\n\n\nclass ClsC extends ClsA.B {}\n\nlet c = new ClsC();\nc.meow();\n"
  },
  {
    "path": "tests/test_code/js/moment/moment.js",
    "content": "//! moment.js\n//! version : 2.29.1\n//! authors : Tim Wood, Iskren Chernev, Moment.js contributors\n//! license : MIT\n//! momentjs.com\n\n;(function (global, factory) {\n    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n    typeof define === 'function' && define.amd ? define(factory) :\n    global.moment = factory()\n}(this, (function () { 'use strict';\n\n    var hookCallback;\n\n    function hooks() {\n        return hookCallback.apply(null, arguments);\n    }\n\n    // This is done to register the method called with moment()\n    // without creating circular dependencies.\n    function setHookCallback(callback) {\n        hookCallback = callback;\n    }\n\n    function isArray(input) {\n        return (\n            input instanceof Array ||\n            Object.prototype.toString.call(input) === '[object Array]'\n        );\n    }\n\n    function isObject(input) {\n        // IE8 will treat undefined and null as object if it wasn't for\n        // input != null\n        return (\n            input != null &&\n            Object.prototype.toString.call(input) === '[object Object]'\n        );\n    }\n\n    function hasOwnProp(a, b) {\n        return Object.prototype.hasOwnProperty.call(a, b);\n    }\n\n    function isObjectEmpty(obj) {\n        if (Object.getOwnPropertyNames) {\n            return Object.getOwnPropertyNames(obj).length === 0;\n        } else {\n            var k;\n            for (k in obj) {\n                if (hasOwnProp(obj, k)) {\n                    return false;\n                }\n            }\n            return true;\n        }\n    }\n\n    function isUndefined(input) {\n        return input === void 0;\n    }\n\n    function isNumber(input) {\n        return (\n            typeof input === 'number' ||\n            Object.prototype.toString.call(input) === '[object Number]'\n        );\n    }\n\n    function isDate(input) {\n        return (\n            input instanceof Date ||\n            Object.prototype.toString.call(input) === '[object Date]'\n        );\n    }\n\n    function map(arr, fn) {\n        var res = [],\n            i;\n        for (i = 0; i < arr.length; ++i) {\n            res.push(fn(arr[i], i));\n        }\n        return res;\n    }\n\n    function extend(a, b) {\n        for (var i in b) {\n            if (hasOwnProp(b, i)) {\n                a[i] = b[i];\n            }\n        }\n\n        if (hasOwnProp(b, 'toString')) {\n            a.toString = b.toString;\n        }\n\n        if (hasOwnProp(b, 'valueOf')) {\n            a.valueOf = b.valueOf;\n        }\n\n        return a;\n    }\n\n    function createUTC(input, format, locale, strict) {\n        return createLocalOrUTC(input, format, locale, strict, true).utc();\n    }\n\n    function defaultParsingFlags() {\n        // We need to deep clone this object.\n        return {\n            empty: false,\n            unusedTokens: [],\n            unusedInput: [],\n            overflow: -2,\n            charsLeftOver: 0,\n            nullInput: false,\n            invalidEra: null,\n            invalidMonth: null,\n            invalidFormat: false,\n            userInvalidated: false,\n            iso: false,\n            parsedDateParts: [],\n            era: null,\n            meridiem: null,\n            rfc2822: false,\n            weekdayMismatch: false,\n        };\n    }\n\n    function getParsingFlags(m) {\n        if (m._pf == null) {\n            m._pf = defaultParsingFlags();\n        }\n        return m._pf;\n    }\n\n    var some;\n    if (Array.prototype.some) {\n        some = Array.prototype.some;\n    } else {\n        some = function (fun) {\n            var t = Object(this),\n                len = t.length >>> 0,\n                i;\n\n            for (i = 0; i < len; i++) {\n                if (i in t && fun.call(this, t[i], i, t)) {\n                    return true;\n                }\n            }\n\n            return false;\n        };\n    }\n\n    function isValid(m) {\n        if (m._isValid == null) {\n            var flags = getParsingFlags(m),\n                parsedParts = some.call(flags.parsedDateParts, function (i) {\n                    return i != null;\n                }),\n                isNowValid =\n                    !isNaN(m._d.getTime()) &&\n                    flags.overflow < 0 &&\n                    !flags.empty &&\n                    !flags.invalidEra &&\n                    !flags.invalidMonth &&\n                    !flags.invalidWeekday &&\n                    !flags.weekdayMismatch &&\n                    !flags.nullInput &&\n                    !flags.invalidFormat &&\n                    !flags.userInvalidated &&\n                    (!flags.meridiem || (flags.meridiem && parsedParts));\n\n            if (m._strict) {\n                isNowValid =\n                    isNowValid &&\n                    flags.charsLeftOver === 0 &&\n                    flags.unusedTokens.length === 0 &&\n                    flags.bigHour === undefined;\n            }\n\n            if (Object.isFrozen == null || !Object.isFrozen(m)) {\n                m._isValid = isNowValid;\n            } else {\n                return isNowValid;\n            }\n        }\n        return m._isValid;\n    }\n\n    function createInvalid(flags) {\n        var m = createUTC(NaN);\n        if (flags != null) {\n            extend(getParsingFlags(m), flags);\n        } else {\n            getParsingFlags(m).userInvalidated = true;\n        }\n\n        return m;\n    }\n\n    // Plugins that add properties should also add the key here (null value),\n    // so we can properly clone ourselves.\n    var momentProperties = (hooks.momentProperties = []),\n        updateInProgress = false;\n\n    function copyConfig(to, from) {\n        var i, prop, val;\n\n        if (!isUndefined(from._isAMomentObject)) {\n            to._isAMomentObject = from._isAMomentObject;\n        }\n        if (!isUndefined(from._i)) {\n            to._i = from._i;\n        }\n        if (!isUndefined(from._f)) {\n            to._f = from._f;\n        }\n        if (!isUndefined(from._l)) {\n            to._l = from._l;\n        }\n        if (!isUndefined(from._strict)) {\n            to._strict = from._strict;\n        }\n        if (!isUndefined(from._tzm)) {\n            to._tzm = from._tzm;\n        }\n        if (!isUndefined(from._isUTC)) {\n            to._isUTC = from._isUTC;\n        }\n        if (!isUndefined(from._offset)) {\n            to._offset = from._offset;\n        }\n        if (!isUndefined(from._pf)) {\n            to._pf = getParsingFlags(from);\n        }\n        if (!isUndefined(from._locale)) {\n            to._locale = from._locale;\n        }\n\n        if (momentProperties.length > 0) {\n            for (i = 0; i < momentProperties.length; i++) {\n                prop = momentProperties[i];\n                val = from[prop];\n                if (!isUndefined(val)) {\n                    to[prop] = val;\n                }\n            }\n        }\n\n        return to;\n    }\n\n    // Moment prototype object\n    function Moment(config) {\n        copyConfig(this, config);\n        this._d = new Date(config._d != null ? config._d.getTime() : NaN);\n        if (!this.isValid()) {\n            this._d = new Date(NaN);\n        }\n        // Prevent infinite loop in case updateOffset creates new moment\n        // objects.\n        if (updateInProgress === false) {\n            updateInProgress = true;\n            hooks.updateOffset(this);\n            updateInProgress = false;\n        }\n    }\n\n    function isMoment(obj) {\n        return (\n            obj instanceof Moment || (obj != null && obj._isAMomentObject != null)\n        );\n    }\n\n    function warn(msg) {\n        if (\n            hooks.suppressDeprecationWarnings === false &&\n            typeof console !== 'undefined' &&\n            console.warn\n        ) {\n            console.warn('Deprecation warning: ' + msg);\n        }\n    }\n\n    function deprecate(msg, fn) {\n        var firstTime = true;\n\n        return extend(function () {\n            if (hooks.deprecationHandler != null) {\n                hooks.deprecationHandler(null, msg);\n            }\n            if (firstTime) {\n                var args = [],\n                    arg,\n                    i,\n                    key;\n                for (i = 0; i < arguments.length; i++) {\n                    arg = '';\n                    if (typeof arguments[i] === 'object') {\n                        arg += '\\n[' + i + '] ';\n                        for (key in arguments[0]) {\n                            if (hasOwnProp(arguments[0], key)) {\n                                arg += key + ': ' + arguments[0][key] + ', ';\n                            }\n                        }\n                        arg = arg.slice(0, -2); // Remove trailing comma and space\n                    } else {\n                        arg = arguments[i];\n                    }\n                    args.push(arg);\n                }\n                warn(\n                    msg +\n                        '\\nArguments: ' +\n                        Array.prototype.slice.call(args).join('') +\n                        '\\n' +\n                        new Error().stack\n                );\n                firstTime = false;\n            }\n            return fn.apply(this, arguments);\n        }, fn);\n    }\n\n    var deprecations = {};\n\n    function deprecateSimple(name, msg) {\n        if (hooks.deprecationHandler != null) {\n            hooks.deprecationHandler(name, msg);\n        }\n        if (!deprecations[name]) {\n            warn(msg);\n            deprecations[name] = true;\n        }\n    }\n\n    hooks.suppressDeprecationWarnings = false;\n    hooks.deprecationHandler = null;\n\n    function isFunction(input) {\n        return (\n            (typeof Function !== 'undefined' && input instanceof Function) ||\n            Object.prototype.toString.call(input) === '[object Function]'\n        );\n    }\n\n    function set(config) {\n        var prop, i;\n        for (i in config) {\n            if (hasOwnProp(config, i)) {\n                prop = config[i];\n                if (isFunction(prop)) {\n                    this[i] = prop;\n                } else {\n                    this['_' + i] = prop;\n                }\n            }\n        }\n        this._config = config;\n        // Lenient ordinal parsing accepts just a number in addition to\n        // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.\n        // TODO: Remove \"ordinalParse\" fallback in next major release.\n        this._dayOfMonthOrdinalParseLenient = new RegExp(\n            (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +\n                '|' +\n                /\\d{1,2}/.source\n        );\n    }\n\n    function mergeConfigs(parentConfig, childConfig) {\n        var res = extend({}, parentConfig),\n            prop;\n        for (prop in childConfig) {\n            if (hasOwnProp(childConfig, prop)) {\n                if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {\n                    res[prop] = {};\n                    extend(res[prop], parentConfig[prop]);\n                    extend(res[prop], childConfig[prop]);\n                } else if (childConfig[prop] != null) {\n                    res[prop] = childConfig[prop];\n                } else {\n                    delete res[prop];\n                }\n            }\n        }\n        for (prop in parentConfig) {\n            if (\n                hasOwnProp(parentConfig, prop) &&\n                !hasOwnProp(childConfig, prop) &&\n                isObject(parentConfig[prop])\n            ) {\n                // make sure changes to properties don't modify parent config\n                res[prop] = extend({}, res[prop]);\n            }\n        }\n        return res;\n    }\n\n    function Locale(config) {\n        if (config != null) {\n            this.set(config);\n        }\n    }\n\n    var keys;\n\n    if (Object.keys) {\n        keys = Object.keys;\n    } else {\n        keys = function (obj) {\n            var i,\n                res = [];\n            for (i in obj) {\n                if (hasOwnProp(obj, i)) {\n                    res.push(i);\n                }\n            }\n            return res;\n        };\n    }\n\n    var defaultCalendar = {\n        sameDay: '[Today at] LT',\n        nextDay: '[Tomorrow at] LT',\n        nextWeek: 'dddd [at] LT',\n        lastDay: '[Yesterday at] LT',\n        lastWeek: '[Last] dddd [at] LT',\n        sameElse: 'L',\n    };\n\n    function calendar(key, mom, now) {\n        var output = this._calendar[key] || this._calendar['sameElse'];\n        return isFunction(output) ? output.call(mom, now) : output;\n    }\n\n    function zeroFill(number, targetLength, forceSign) {\n        var absNumber = '' + Math.abs(number),\n            zerosToFill = targetLength - absNumber.length,\n            sign = number >= 0;\n        return (\n            (sign ? (forceSign ? '+' : '') : '-') +\n            Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) +\n            absNumber\n        );\n    }\n\n    var formattingTokens = /(\\[[^\\[]*\\])|(\\\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,\n        localFormattingTokens = /(\\[[^\\[]*\\])|(\\\\)?(LTS|LT|LL?L?L?|l{1,4})/g,\n        formatFunctions = {},\n        formatTokenFunctions = {};\n\n    // token:    'M'\n    // padded:   ['MM', 2]\n    // ordinal:  'Mo'\n    // callback: function () { this.month() + 1 }\n    function addFormatToken(token, padded, ordinal, callback) {\n        var func = callback;\n        if (typeof callback === 'string') {\n            func = function () {\n                return this[callback]();\n            };\n        }\n        if (token) {\n            formatTokenFunctions[token] = func;\n        }\n        if (padded) {\n            formatTokenFunctions[padded[0]] = function () {\n                return zeroFill(func.apply(this, arguments), padded[1], padded[2]);\n            };\n        }\n        if (ordinal) {\n            formatTokenFunctions[ordinal] = function () {\n                return this.localeData().ordinal(\n                    func.apply(this, arguments),\n                    token\n                );\n            };\n        }\n    }\n\n    function removeFormattingTokens(input) {\n        if (input.match(/\\[[\\s\\S]/)) {\n            return input.replace(/^\\[|\\]$/g, '');\n        }\n        return input.replace(/\\\\/g, '');\n    }\n\n    function makeFormatFunction(format) {\n        var array = format.match(formattingTokens),\n            i,\n            length;\n\n        for (i = 0, length = array.length; i < length; i++) {\n            if (formatTokenFunctions[array[i]]) {\n                array[i] = formatTokenFunctions[array[i]];\n            } else {\n                array[i] = removeFormattingTokens(array[i]);\n            }\n        }\n\n        return function (mom) {\n            var output = '',\n                i;\n            for (i = 0; i < length; i++) {\n                output += isFunction(array[i])\n                    ? array[i].call(mom, format)\n                    : array[i];\n            }\n            return output;\n        };\n    }\n\n    // format date using native date object\n    function formatMoment(m, format) {\n        if (!m.isValid()) {\n            return m.localeData().invalidDate();\n        }\n\n        format = expandFormat(format, m.localeData());\n        formatFunctions[format] =\n            formatFunctions[format] || makeFormatFunction(format);\n\n        return formatFunctions[format](m);\n    }\n\n    function expandFormat(format, locale) {\n        var i = 5;\n\n        function replaceLongDateFormatTokens(input) {\n            return locale.longDateFormat(input) || input;\n        }\n\n        localFormattingTokens.lastIndex = 0;\n        while (i >= 0 && localFormattingTokens.test(format)) {\n            format = format.replace(\n                localFormattingTokens,\n                replaceLongDateFormatTokens\n            );\n            localFormattingTokens.lastIndex = 0;\n            i -= 1;\n        }\n\n        return format;\n    }\n\n    var defaultLongDateFormat = {\n        LTS: 'h:mm:ss A',\n        LT: 'h:mm A',\n        L: 'MM/DD/YYYY',\n        LL: 'MMMM D, YYYY',\n        LLL: 'MMMM D, YYYY h:mm A',\n        LLLL: 'dddd, MMMM D, YYYY h:mm A',\n    };\n\n    function longDateFormat(key) {\n        var format = this._longDateFormat[key],\n            formatUpper = this._longDateFormat[key.toUpperCase()];\n\n        if (format || !formatUpper) {\n            return format;\n        }\n\n        this._longDateFormat[key] = formatUpper\n            .match(formattingTokens)\n            .map(function (tok) {\n                if (\n                    tok === 'MMMM' ||\n                    tok === 'MM' ||\n                    tok === 'DD' ||\n                    tok === 'dddd'\n                ) {\n                    return tok.slice(1);\n                }\n                return tok;\n            })\n            .join('');\n\n        return this._longDateFormat[key];\n    }\n\n    var defaultInvalidDate = 'Invalid date';\n\n    function invalidDate() {\n        return this._invalidDate;\n    }\n\n    var defaultOrdinal = '%d',\n        defaultDayOfMonthOrdinalParse = /\\d{1,2}/;\n\n    function ordinal(number) {\n        return this._ordinal.replace('%d', number);\n    }\n\n    var defaultRelativeTime = {\n        future: 'in %s',\n        past: '%s ago',\n        s: 'a few seconds',\n        ss: '%d seconds',\n        m: 'a minute',\n        mm: '%d minutes',\n        h: 'an hour',\n        hh: '%d hours',\n        d: 'a day',\n        dd: '%d days',\n        w: 'a week',\n        ww: '%d weeks',\n        M: 'a month',\n        MM: '%d months',\n        y: 'a year',\n        yy: '%d years',\n    };\n\n    function relativeTime(number, withoutSuffix, string, isFuture) {\n        var output = this._relativeTime[string];\n        return isFunction(output)\n            ? output(number, withoutSuffix, string, isFuture)\n            : output.replace(/%d/i, number);\n    }\n\n    function pastFuture(diff, output) {\n        var format = this._relativeTime[diff > 0 ? 'future' : 'past'];\n        return isFunction(format) ? format(output) : format.replace(/%s/i, output);\n    }\n\n    var aliases = {};\n\n    function addUnitAlias(unit, shorthand) {\n        var lowerCase = unit.toLowerCase();\n        aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;\n    }\n\n    function normalizeUnits(units) {\n        return typeof units === 'string'\n            ? aliases[units] || aliases[units.toLowerCase()]\n            : undefined;\n    }\n\n    function normalizeObjectUnits(inputObject) {\n        var normalizedInput = {},\n            normalizedProp,\n            prop;\n\n        for (prop in inputObject) {\n            if (hasOwnProp(inputObject, prop)) {\n                normalizedProp = normalizeUnits(prop);\n                if (normalizedProp) {\n                    normalizedInput[normalizedProp] = inputObject[prop];\n                }\n            }\n        }\n\n        return normalizedInput;\n    }\n\n    var priorities = {};\n\n    function addUnitPriority(unit, priority) {\n        priorities[unit] = priority;\n    }\n\n    function getPrioritizedUnits(unitsObj) {\n        var units = [],\n            u;\n        for (u in unitsObj) {\n            if (hasOwnProp(unitsObj, u)) {\n                units.push({ unit: u, priority: priorities[u] });\n            }\n        }\n        units.sort(function (a, b) {\n            return a.priority - b.priority;\n        });\n        return units;\n    }\n\n    function isLeapYear(year) {\n        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n    }\n\n    function absFloor(number) {\n        if (number < 0) {\n            // -0 -> 0\n            return Math.ceil(number) || 0;\n        } else {\n            return Math.floor(number);\n        }\n    }\n\n    function toInt(argumentForCoercion) {\n        var coercedNumber = +argumentForCoercion,\n            value = 0;\n\n        if (coercedNumber !== 0 && isFinite(coercedNumber)) {\n            value = absFloor(coercedNumber);\n        }\n\n        return value;\n    }\n\n    function makeGetSet(unit, keepTime) {\n        return function (value) {\n            if (value != null) {\n                set$1(this, unit, value);\n                hooks.updateOffset(this, keepTime);\n                return this;\n            } else {\n                return get(this, unit);\n            }\n        };\n    }\n\n    function get(mom, unit) {\n        return mom.isValid()\n            ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()\n            : NaN;\n    }\n\n    function set$1(mom, unit, value) {\n        if (mom.isValid() && !isNaN(value)) {\n            if (\n                unit === 'FullYear' &&\n                isLeapYear(mom.year()) &&\n                mom.month() === 1 &&\n                mom.date() === 29\n            ) {\n                value = toInt(value);\n                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](\n                    value,\n                    mom.month(),\n                    daysInMonth(value, mom.month())\n                );\n            } else {\n                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);\n            }\n        }\n    }\n\n    // MOMENTS\n\n    function stringGet(units) {\n        units = normalizeUnits(units);\n        if (isFunction(this[units])) {\n            return this[units]();\n        }\n        return this;\n    }\n\n    function stringSet(units, value) {\n        if (typeof units === 'object') {\n            units = normalizeObjectUnits(units);\n            var prioritized = getPrioritizedUnits(units),\n                i;\n            for (i = 0; i < prioritized.length; i++) {\n                this[prioritized[i].unit](units[prioritized[i].unit]);\n            }\n        } else {\n            units = normalizeUnits(units);\n            if (isFunction(this[units])) {\n                return this[units](value);\n            }\n        }\n        return this;\n    }\n\n    var match1 = /\\d/, //       0 - 9\n        match2 = /\\d\\d/, //      00 - 99\n        match3 = /\\d{3}/, //     000 - 999\n        match4 = /\\d{4}/, //    0000 - 9999\n        match6 = /[+-]?\\d{6}/, // -999999 - 999999\n        match1to2 = /\\d\\d?/, //       0 - 99\n        match3to4 = /\\d\\d\\d\\d?/, //     999 - 9999\n        match5to6 = /\\d\\d\\d\\d\\d\\d?/, //   99999 - 999999\n        match1to3 = /\\d{1,3}/, //       0 - 999\n        match1to4 = /\\d{1,4}/, //       0 - 9999\n        match1to6 = /[+-]?\\d{1,6}/, // -999999 - 999999\n        matchUnsigned = /\\d+/, //       0 - inf\n        matchSigned = /[+-]?\\d+/, //    -inf - inf\n        matchOffset = /Z|[+-]\\d\\d:?\\d\\d/gi, // +00:00 -00:00 +0000 -0000 or Z\n        matchShortOffset = /Z|[+-]\\d\\d(?::?\\d\\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z\n        matchTimestamp = /[+-]?\\d+(\\.\\d{1,3})?/, // 123456789 123456789.123\n        // any word (or two) characters or numbers including two/three word month in arabic.\n        // includes scottish gaelic two word and hyphenated months\n        matchWord = /[0-9]{0,256}['a-z\\u00A0-\\u05FF\\u0700-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFF07\\uFF10-\\uFFEF]{1,256}|[\\u0600-\\u06FF\\/]{1,256}(\\s*?[\\u0600-\\u06FF]{1,256}){1,2}/i,\n        regexes;\n\n    regexes = {};\n\n    function addRegexToken(token, regex, strictRegex) {\n        regexes[token] = isFunction(regex)\n            ? regex\n            : function (isStrict, localeData) {\n                  return isStrict && strictRegex ? strictRegex : regex;\n              };\n    }\n\n    function getParseRegexForToken(token, config) {\n        if (!hasOwnProp(regexes, token)) {\n            return new RegExp(unescapeFormat(token));\n        }\n\n        return regexes[token](config._strict, config._locale);\n    }\n\n    // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript\n    function unescapeFormat(s) {\n        return regexEscape(\n            s\n                .replace('\\\\', '')\n                .replace(/\\\\(\\[)|\\\\(\\])|\\[([^\\]\\[]*)\\]|\\\\(.)/g, function (\n                    matched,\n                    p1,\n                    p2,\n                    p3,\n                    p4\n                ) {\n                    return p1 || p2 || p3 || p4;\n                })\n        );\n    }\n\n    function regexEscape(s) {\n        return s.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n    }\n\n    var tokens = {};\n\n    function addParseToken(token, callback) {\n        var i,\n            func = callback;\n        if (typeof token === 'string') {\n            token = [token];\n        }\n        if (isNumber(callback)) {\n            func = function (input, array) {\n                array[callback] = toInt(input);\n            };\n        }\n        for (i = 0; i < token.length; i++) {\n            tokens[token[i]] = func;\n        }\n    }\n\n    function addWeekParseToken(token, callback) {\n        addParseToken(token, function (input, array, config, token) {\n            config._w = config._w || {};\n            callback(input, config._w, config, token);\n        });\n    }\n\n    function addTimeToArrayFromToken(token, input, config) {\n        if (input != null && hasOwnProp(tokens, token)) {\n            tokens[token](input, config._a, config, token);\n        }\n    }\n\n    var YEAR = 0,\n        MONTH = 1,\n        DATE = 2,\n        HOUR = 3,\n        MINUTE = 4,\n        SECOND = 5,\n        MILLISECOND = 6,\n        WEEK = 7,\n        WEEKDAY = 8;\n\n    function mod(n, x) {\n        return ((n % x) + x) % x;\n    }\n\n    var indexOf;\n\n    if (Array.prototype.indexOf) {\n        indexOf = Array.prototype.indexOf;\n    } else {\n        indexOf = function (o) {\n            // I know\n            var i;\n            for (i = 0; i < this.length; ++i) {\n                if (this[i] === o) {\n                    return i;\n                }\n            }\n            return -1;\n        };\n    }\n\n    function daysInMonth(year, month) {\n        if (isNaN(year) || isNaN(month)) {\n            return NaN;\n        }\n        var modMonth = mod(month, 12);\n        year += (month - modMonth) / 12;\n        return modMonth === 1\n            ? isLeapYear(year)\n                ? 29\n                : 28\n            : 31 - ((modMonth % 7) % 2);\n    }\n\n    // FORMATTING\n\n    addFormatToken('M', ['MM', 2], 'Mo', function () {\n        return this.month() + 1;\n    });\n\n    addFormatToken('MMM', 0, 0, function (format) {\n        return this.localeData().monthsShort(this, format);\n    });\n\n    addFormatToken('MMMM', 0, 0, function (format) {\n        return this.localeData().months(this, format);\n    });\n\n    // ALIASES\n\n    addUnitAlias('month', 'M');\n\n    // PRIORITY\n\n    addUnitPriority('month', 8);\n\n    // PARSING\n\n    addRegexToken('M', match1to2);\n    addRegexToken('MM', match1to2, match2);\n    addRegexToken('MMM', function (isStrict, locale) {\n        return locale.monthsShortRegex(isStrict);\n    });\n    addRegexToken('MMMM', function (isStrict, locale) {\n        return locale.monthsRegex(isStrict);\n    });\n\n    addParseToken(['M', 'MM'], function (input, array) {\n        array[MONTH] = toInt(input) - 1;\n    });\n\n    addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {\n        var month = config._locale.monthsParse(input, token, config._strict);\n        // if we didn't find a month name, mark the date as invalid.\n        if (month != null) {\n            array[MONTH] = month;\n        } else {\n            getParsingFlags(config).invalidMonth = input;\n        }\n    });\n\n    // LOCALES\n\n    var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split(\n            '_'\n        ),\n        defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split(\n            '_'\n        ),\n        MONTHS_IN_FORMAT = /D[oD]?(\\[[^\\[\\]]*\\]|\\s)+MMMM?/,\n        defaultMonthsShortRegex = matchWord,\n        defaultMonthsRegex = matchWord;\n\n    function localeMonths(m, format) {\n        if (!m) {\n            return isArray(this._months)\n                ? this._months\n                : this._months['standalone'];\n        }\n        return isArray(this._months)\n            ? this._months[m.month()]\n            : this._months[\n                  (this._months.isFormat || MONTHS_IN_FORMAT).test(format)\n                      ? 'format'\n                      : 'standalone'\n              ][m.month()];\n    }\n\n    function localeMonthsShort(m, format) {\n        if (!m) {\n            return isArray(this._monthsShort)\n                ? this._monthsShort\n                : this._monthsShort['standalone'];\n        }\n        return isArray(this._monthsShort)\n            ? this._monthsShort[m.month()]\n            : this._monthsShort[\n                  MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'\n              ][m.month()];\n    }\n\n    function handleStrictParse(monthName, format, strict) {\n        var i,\n            ii,\n            mom,\n            llc = monthName.toLocaleLowerCase();\n        if (!this._monthsParse) {\n            // this is not used\n            this._monthsParse = [];\n            this._longMonthsParse = [];\n            this._shortMonthsParse = [];\n            for (i = 0; i < 12; ++i) {\n                mom = createUTC([2000, i]);\n                this._shortMonthsParse[i] = this.monthsShort(\n                    mom,\n                    ''\n                ).toLocaleLowerCase();\n                this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();\n            }\n        }\n\n        if (strict) {\n            if (format === 'MMM') {\n                ii = indexOf.call(this._shortMonthsParse, llc);\n                return ii !== -1 ? ii : null;\n            } else {\n                ii = indexOf.call(this._longMonthsParse, llc);\n                return ii !== -1 ? ii : null;\n            }\n        } else {\n            if (format === 'MMM') {\n                ii = indexOf.call(this._shortMonthsParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._longMonthsParse, llc);\n                return ii !== -1 ? ii : null;\n            } else {\n                ii = indexOf.call(this._longMonthsParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._shortMonthsParse, llc);\n                return ii !== -1 ? ii : null;\n            }\n        }\n    }\n\n    function localeMonthsParse(monthName, format, strict) {\n        var i, mom, regex;\n\n        if (this._monthsParseExact) {\n            return handleStrictParse.call(this, monthName, format, strict);\n        }\n\n        if (!this._monthsParse) {\n            this._monthsParse = [];\n            this._longMonthsParse = [];\n            this._shortMonthsParse = [];\n        }\n\n        // TODO: add sorting\n        // Sorting makes sure if one month (or abbr) is a prefix of another\n        // see sorting in computeMonthsParse\n        for (i = 0; i < 12; i++) {\n            // make the regex if we don't have it already\n            mom = createUTC([2000, i]);\n            if (strict && !this._longMonthsParse[i]) {\n                this._longMonthsParse[i] = new RegExp(\n                    '^' + this.months(mom, '').replace('.', '') + '$',\n                    'i'\n                );\n                this._shortMonthsParse[i] = new RegExp(\n                    '^' + this.monthsShort(mom, '').replace('.', '') + '$',\n                    'i'\n                );\n            }\n            if (!strict && !this._monthsParse[i]) {\n                regex =\n                    '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');\n                this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');\n            }\n            // test the regex\n            if (\n                strict &&\n                format === 'MMMM' &&\n                this._longMonthsParse[i].test(monthName)\n            ) {\n                return i;\n            } else if (\n                strict &&\n                format === 'MMM' &&\n                this._shortMonthsParse[i].test(monthName)\n            ) {\n                return i;\n            } else if (!strict && this._monthsParse[i].test(monthName)) {\n                return i;\n            }\n        }\n    }\n\n    // MOMENTS\n\n    function setMonth(mom, value) {\n        var dayOfMonth;\n\n        if (!mom.isValid()) {\n            // No op\n            return mom;\n        }\n\n        if (typeof value === 'string') {\n            if (/^\\d+$/.test(value)) {\n                value = toInt(value);\n            } else {\n                value = mom.localeData().monthsParse(value);\n                // TODO: Another silent failure?\n                if (!isNumber(value)) {\n                    return mom;\n                }\n            }\n        }\n\n        dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));\n        mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);\n        return mom;\n    }\n\n    function getSetMonth(value) {\n        if (value != null) {\n            setMonth(this, value);\n            hooks.updateOffset(this, true);\n            return this;\n        } else {\n            return get(this, 'Month');\n        }\n    }\n\n    function getDaysInMonth() {\n        return daysInMonth(this.year(), this.month());\n    }\n\n    function monthsShortRegex(isStrict) {\n        if (this._monthsParseExact) {\n            if (!hasOwnProp(this, '_monthsRegex')) {\n                computeMonthsParse.call(this);\n            }\n            if (isStrict) {\n                return this._monthsShortStrictRegex;\n            } else {\n                return this._monthsShortRegex;\n            }\n        } else {\n            if (!hasOwnProp(this, '_monthsShortRegex')) {\n                this._monthsShortRegex = defaultMonthsShortRegex;\n            }\n            return this._monthsShortStrictRegex && isStrict\n                ? this._monthsShortStrictRegex\n                : this._monthsShortRegex;\n        }\n    }\n\n    function monthsRegex(isStrict) {\n        if (this._monthsParseExact) {\n            if (!hasOwnProp(this, '_monthsRegex')) {\n                computeMonthsParse.call(this);\n            }\n            if (isStrict) {\n                return this._monthsStrictRegex;\n            } else {\n                return this._monthsRegex;\n            }\n        } else {\n            if (!hasOwnProp(this, '_monthsRegex')) {\n                this._monthsRegex = defaultMonthsRegex;\n            }\n            return this._monthsStrictRegex && isStrict\n                ? this._monthsStrictRegex\n                : this._monthsRegex;\n        }\n    }\n\n    function computeMonthsParse() {\n        function cmpLenRev(a, b) {\n            return b.length - a.length;\n        }\n\n        var shortPieces = [],\n            longPieces = [],\n            mixedPieces = [],\n            i,\n            mom;\n        for (i = 0; i < 12; i++) {\n            // make the regex if we don't have it already\n            mom = createUTC([2000, i]);\n            shortPieces.push(this.monthsShort(mom, ''));\n            longPieces.push(this.months(mom, ''));\n            mixedPieces.push(this.months(mom, ''));\n            mixedPieces.push(this.monthsShort(mom, ''));\n        }\n        // Sorting makes sure if one month (or abbr) is a prefix of another it\n        // will match the longer piece.\n        shortPieces.sort(cmpLenRev);\n        longPieces.sort(cmpLenRev);\n        mixedPieces.sort(cmpLenRev);\n        for (i = 0; i < 12; i++) {\n            shortPieces[i] = regexEscape(shortPieces[i]);\n            longPieces[i] = regexEscape(longPieces[i]);\n        }\n        for (i = 0; i < 24; i++) {\n            mixedPieces[i] = regexEscape(mixedPieces[i]);\n        }\n\n        this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');\n        this._monthsShortRegex = this._monthsRegex;\n        this._monthsStrictRegex = new RegExp(\n            '^(' + longPieces.join('|') + ')',\n            'i'\n        );\n        this._monthsShortStrictRegex = new RegExp(\n            '^(' + shortPieces.join('|') + ')',\n            'i'\n        );\n    }\n\n    // FORMATTING\n\n    addFormatToken('Y', 0, 0, function () {\n        var y = this.year();\n        return y <= 9999 ? zeroFill(y, 4) : '+' + y;\n    });\n\n    addFormatToken(0, ['YY', 2], 0, function () {\n        return this.year() % 100;\n    });\n\n    addFormatToken(0, ['YYYY', 4], 0, 'year');\n    addFormatToken(0, ['YYYYY', 5], 0, 'year');\n    addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');\n\n    // ALIASES\n\n    addUnitAlias('year', 'y');\n\n    // PRIORITIES\n\n    addUnitPriority('year', 1);\n\n    // PARSING\n\n    addRegexToken('Y', matchSigned);\n    addRegexToken('YY', match1to2, match2);\n    addRegexToken('YYYY', match1to4, match4);\n    addRegexToken('YYYYY', match1to6, match6);\n    addRegexToken('YYYYYY', match1to6, match6);\n\n    addParseToken(['YYYYY', 'YYYYYY'], YEAR);\n    addParseToken('YYYY', function (input, array) {\n        array[YEAR] =\n            input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);\n    });\n    addParseToken('YY', function (input, array) {\n        array[YEAR] = hooks.parseTwoDigitYear(input);\n    });\n    addParseToken('Y', function (input, array) {\n        array[YEAR] = parseInt(input, 10);\n    });\n\n    // HELPERS\n\n    function daysInYear(year) {\n        return isLeapYear(year) ? 366 : 365;\n    }\n\n    // HOOKS\n\n    hooks.parseTwoDigitYear = function (input) {\n        return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);\n    };\n\n    // MOMENTS\n\n    var getSetYear = makeGetSet('FullYear', true);\n\n    function getIsLeapYear() {\n        return isLeapYear(this.year());\n    }\n\n    function createDate(y, m, d, h, M, s, ms) {\n        // can't just apply() to create a date:\n        // https://stackoverflow.com/q/181348\n        var date;\n        // the date constructor remaps years 0-99 to 1900-1999\n        if (y < 100 && y >= 0) {\n            // preserve leap years using a full 400 year cycle, then reset\n            date = new Date(y + 400, m, d, h, M, s, ms);\n            if (isFinite(date.getFullYear())) {\n                date.setFullYear(y);\n            }\n        } else {\n            date = new Date(y, m, d, h, M, s, ms);\n        }\n\n        return date;\n    }\n\n    function createUTCDate(y) {\n        var date, args;\n        // the Date.UTC function remaps years 0-99 to 1900-1999\n        if (y < 100 && y >= 0) {\n            args = Array.prototype.slice.call(arguments);\n            // preserve leap years using a full 400 year cycle, then reset\n            args[0] = y + 400;\n            date = new Date(Date.UTC.apply(null, args));\n            if (isFinite(date.getUTCFullYear())) {\n                date.setUTCFullYear(y);\n            }\n        } else {\n            date = new Date(Date.UTC.apply(null, arguments));\n        }\n\n        return date;\n    }\n\n    // start-of-first-week - start-of-year\n    function firstWeekOffset(year, dow, doy) {\n        var // first-week day -- which january is always in the first week (4 for iso, 1 for other)\n            fwd = 7 + dow - doy,\n            // first-week day local weekday -- which local weekday is fwd\n            fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;\n\n        return -fwdlw + fwd - 1;\n    }\n\n    // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday\n    function dayOfYearFromWeeks(year, week, weekday, dow, doy) {\n        var localWeekday = (7 + weekday - dow) % 7,\n            weekOffset = firstWeekOffset(year, dow, doy),\n            dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,\n            resYear,\n            resDayOfYear;\n\n        if (dayOfYear <= 0) {\n            resYear = year - 1;\n            resDayOfYear = daysInYear(resYear) + dayOfYear;\n        } else if (dayOfYear > daysInYear(year)) {\n            resYear = year + 1;\n            resDayOfYear = dayOfYear - daysInYear(year);\n        } else {\n            resYear = year;\n            resDayOfYear = dayOfYear;\n        }\n\n        return {\n            year: resYear,\n            dayOfYear: resDayOfYear,\n        };\n    }\n\n    function weekOfYear(mom, dow, doy) {\n        var weekOffset = firstWeekOffset(mom.year(), dow, doy),\n            week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,\n            resWeek,\n            resYear;\n\n        if (week < 1) {\n            resYear = mom.year() - 1;\n            resWeek = week + weeksInYear(resYear, dow, doy);\n        } else if (week > weeksInYear(mom.year(), dow, doy)) {\n            resWeek = week - weeksInYear(mom.year(), dow, doy);\n            resYear = mom.year() + 1;\n        } else {\n            resYear = mom.year();\n            resWeek = week;\n        }\n\n        return {\n            week: resWeek,\n            year: resYear,\n        };\n    }\n\n    function weeksInYear(year, dow, doy) {\n        var weekOffset = firstWeekOffset(year, dow, doy),\n            weekOffsetNext = firstWeekOffset(year + 1, dow, doy);\n        return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;\n    }\n\n    // FORMATTING\n\n    addFormatToken('w', ['ww', 2], 'wo', 'week');\n    addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');\n\n    // ALIASES\n\n    addUnitAlias('week', 'w');\n    addUnitAlias('isoWeek', 'W');\n\n    // PRIORITIES\n\n    addUnitPriority('week', 5);\n    addUnitPriority('isoWeek', 5);\n\n    // PARSING\n\n    addRegexToken('w', match1to2);\n    addRegexToken('ww', match1to2, match2);\n    addRegexToken('W', match1to2);\n    addRegexToken('WW', match1to2, match2);\n\n    addWeekParseToken(['w', 'ww', 'W', 'WW'], function (\n        input,\n        week,\n        config,\n        token\n    ) {\n        week[token.substr(0, 1)] = toInt(input);\n    });\n\n    // HELPERS\n\n    // LOCALES\n\n    function localeWeek(mom) {\n        return weekOfYear(mom, this._week.dow, this._week.doy).week;\n    }\n\n    var defaultLocaleWeek = {\n        dow: 0, // Sunday is the first day of the week.\n        doy: 6, // The week that contains Jan 6th is the first week of the year.\n    };\n\n    function localeFirstDayOfWeek() {\n        return this._week.dow;\n    }\n\n    function localeFirstDayOfYear() {\n        return this._week.doy;\n    }\n\n    // MOMENTS\n\n    function getSetWeek(input) {\n        var week = this.localeData().week(this);\n        return input == null ? week : this.add((input - week) * 7, 'd');\n    }\n\n    function getSetISOWeek(input) {\n        var week = weekOfYear(this, 1, 4).week;\n        return input == null ? week : this.add((input - week) * 7, 'd');\n    }\n\n    // FORMATTING\n\n    addFormatToken('d', 0, 'do', 'day');\n\n    addFormatToken('dd', 0, 0, function (format) {\n        return this.localeData().weekdaysMin(this, format);\n    });\n\n    addFormatToken('ddd', 0, 0, function (format) {\n        return this.localeData().weekdaysShort(this, format);\n    });\n\n    addFormatToken('dddd', 0, 0, function (format) {\n        return this.localeData().weekdays(this, format);\n    });\n\n    addFormatToken('e', 0, 0, 'weekday');\n    addFormatToken('E', 0, 0, 'isoWeekday');\n\n    // ALIASES\n\n    addUnitAlias('day', 'd');\n    addUnitAlias('weekday', 'e');\n    addUnitAlias('isoWeekday', 'E');\n\n    // PRIORITY\n    addUnitPriority('day', 11);\n    addUnitPriority('weekday', 11);\n    addUnitPriority('isoWeekday', 11);\n\n    // PARSING\n\n    addRegexToken('d', match1to2);\n    addRegexToken('e', match1to2);\n    addRegexToken('E', match1to2);\n    addRegexToken('dd', function (isStrict, locale) {\n        return locale.weekdaysMinRegex(isStrict);\n    });\n    addRegexToken('ddd', function (isStrict, locale) {\n        return locale.weekdaysShortRegex(isStrict);\n    });\n    addRegexToken('dddd', function (isStrict, locale) {\n        return locale.weekdaysRegex(isStrict);\n    });\n\n    addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {\n        var weekday = config._locale.weekdaysParse(input, token, config._strict);\n        // if we didn't get a weekday name, mark the date as invalid\n        if (weekday != null) {\n            week.d = weekday;\n        } else {\n            getParsingFlags(config).invalidWeekday = input;\n        }\n    });\n\n    addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {\n        week[token] = toInt(input);\n    });\n\n    // HELPERS\n\n    function parseWeekday(input, locale) {\n        if (typeof input !== 'string') {\n            return input;\n        }\n\n        if (!isNaN(input)) {\n            return parseInt(input, 10);\n        }\n\n        input = locale.weekdaysParse(input);\n        if (typeof input === 'number') {\n            return input;\n        }\n\n        return null;\n    }\n\n    function parseIsoWeekday(input, locale) {\n        if (typeof input === 'string') {\n            return locale.weekdaysParse(input) % 7 || 7;\n        }\n        return isNaN(input) ? null : input;\n    }\n\n    // LOCALES\n    function shiftWeekdays(ws, n) {\n        return ws.slice(n, 7).concat(ws.slice(0, n));\n    }\n\n    var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split(\n            '_'\n        ),\n        defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),\n        defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),\n        defaultWeekdaysRegex = matchWord,\n        defaultWeekdaysShortRegex = matchWord,\n        defaultWeekdaysMinRegex = matchWord;\n\n    function localeWeekdays(m, format) {\n        var weekdays = isArray(this._weekdays)\n            ? this._weekdays\n            : this._weekdays[\n                  m && m !== true && this._weekdays.isFormat.test(format)\n                      ? 'format'\n                      : 'standalone'\n              ];\n        return m === true\n            ? shiftWeekdays(weekdays, this._week.dow)\n            : m\n            ? weekdays[m.day()]\n            : weekdays;\n    }\n\n    function localeWeekdaysShort(m) {\n        return m === true\n            ? shiftWeekdays(this._weekdaysShort, this._week.dow)\n            : m\n            ? this._weekdaysShort[m.day()]\n            : this._weekdaysShort;\n    }\n\n    function localeWeekdaysMin(m) {\n        return m === true\n            ? shiftWeekdays(this._weekdaysMin, this._week.dow)\n            : m\n            ? this._weekdaysMin[m.day()]\n            : this._weekdaysMin;\n    }\n\n    function handleStrictParse$1(weekdayName, format, strict) {\n        var i,\n            ii,\n            mom,\n            llc = weekdayName.toLocaleLowerCase();\n        if (!this._weekdaysParse) {\n            this._weekdaysParse = [];\n            this._shortWeekdaysParse = [];\n            this._minWeekdaysParse = [];\n\n            for (i = 0; i < 7; ++i) {\n                mom = createUTC([2000, 1]).day(i);\n                this._minWeekdaysParse[i] = this.weekdaysMin(\n                    mom,\n                    ''\n                ).toLocaleLowerCase();\n                this._shortWeekdaysParse[i] = this.weekdaysShort(\n                    mom,\n                    ''\n                ).toLocaleLowerCase();\n                this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();\n            }\n        }\n\n        if (strict) {\n            if (format === 'dddd') {\n                ii = indexOf.call(this._weekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            } else if (format === 'ddd') {\n                ii = indexOf.call(this._shortWeekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            } else {\n                ii = indexOf.call(this._minWeekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            }\n        } else {\n            if (format === 'dddd') {\n                ii = indexOf.call(this._weekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._shortWeekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._minWeekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            } else if (format === 'ddd') {\n                ii = indexOf.call(this._shortWeekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._weekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._minWeekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            } else {\n                ii = indexOf.call(this._minWeekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._weekdaysParse, llc);\n                if (ii !== -1) {\n                    return ii;\n                }\n                ii = indexOf.call(this._shortWeekdaysParse, llc);\n                return ii !== -1 ? ii : null;\n            }\n        }\n    }\n\n    function localeWeekdaysParse(weekdayName, format, strict) {\n        var i, mom, regex;\n\n        if (this._weekdaysParseExact) {\n            return handleStrictParse$1.call(this, weekdayName, format, strict);\n        }\n\n        if (!this._weekdaysParse) {\n            this._weekdaysParse = [];\n            this._minWeekdaysParse = [];\n            this._shortWeekdaysParse = [];\n            this._fullWeekdaysParse = [];\n        }\n\n        for (i = 0; i < 7; i++) {\n            // make the regex if we don't have it already\n\n            mom = createUTC([2000, 1]).day(i);\n            if (strict && !this._fullWeekdaysParse[i]) {\n                this._fullWeekdaysParse[i] = new RegExp(\n                    '^' + this.weekdays(mom, '').replace('.', '\\\\.?') + '$',\n                    'i'\n                );\n                this._shortWeekdaysParse[i] = new RegExp(\n                    '^' + this.weekdaysShort(mom, '').replace('.', '\\\\.?') + '$',\n                    'i'\n                );\n                this._minWeekdaysParse[i] = new RegExp(\n                    '^' + this.weekdaysMin(mom, '').replace('.', '\\\\.?') + '$',\n                    'i'\n                );\n            }\n            if (!this._weekdaysParse[i]) {\n                regex =\n                    '^' +\n                    this.weekdays(mom, '') +\n                    '|^' +\n                    this.weekdaysShort(mom, '') +\n                    '|^' +\n                    this.weekdaysMin(mom, '');\n                this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');\n            }\n            // test the regex\n            if (\n                strict &&\n                format === 'dddd' &&\n                this._fullWeekdaysParse[i].test(weekdayName)\n            ) {\n                return i;\n            } else if (\n                strict &&\n                format === 'ddd' &&\n                this._shortWeekdaysParse[i].test(weekdayName)\n            ) {\n                return i;\n            } else if (\n                strict &&\n                format === 'dd' &&\n                this._minWeekdaysParse[i].test(weekdayName)\n            ) {\n                return i;\n            } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {\n                return i;\n            }\n        }\n    }\n\n    // MOMENTS\n\n    function getSetDayOfWeek(input) {\n        if (!this.isValid()) {\n            return input != null ? this : NaN;\n        }\n        var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();\n        if (input != null) {\n            input = parseWeekday(input, this.localeData());\n            return this.add(input - day, 'd');\n        } else {\n            return day;\n        }\n    }\n\n    function getSetLocaleDayOfWeek(input) {\n        if (!this.isValid()) {\n            return input != null ? this : NaN;\n        }\n        var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;\n        return input == null ? weekday : this.add(input - weekday, 'd');\n    }\n\n    function getSetISODayOfWeek(input) {\n        if (!this.isValid()) {\n            return input != null ? this : NaN;\n        }\n\n        // behaves the same as moment#day except\n        // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)\n        // as a setter, sunday should belong to the previous week.\n\n        if (input != null) {\n            var weekday = parseIsoWeekday(input, this.localeData());\n            return this.day(this.day() % 7 ? weekday : weekday - 7);\n        } else {\n            return this.day() || 7;\n        }\n    }\n\n    function weekdaysRegex(isStrict) {\n        if (this._weekdaysParseExact) {\n            if (!hasOwnProp(this, '_weekdaysRegex')) {\n                computeWeekdaysParse.call(this);\n            }\n            if (isStrict) {\n                return this._weekdaysStrictRegex;\n            } else {\n                return this._weekdaysRegex;\n            }\n        } else {\n            if (!hasOwnProp(this, '_weekdaysRegex')) {\n                this._weekdaysRegex = defaultWeekdaysRegex;\n            }\n            return this._weekdaysStrictRegex && isStrict\n                ? this._weekdaysStrictRegex\n                : this._weekdaysRegex;\n        }\n    }\n\n    function weekdaysShortRegex(isStrict) {\n        if (this._weekdaysParseExact) {\n            if (!hasOwnProp(this, '_weekdaysRegex')) {\n                computeWeekdaysParse.call(this);\n            }\n            if (isStrict) {\n                return this._weekdaysShortStrictRegex;\n            } else {\n                return this._weekdaysShortRegex;\n            }\n        } else {\n            if (!hasOwnProp(this, '_weekdaysShortRegex')) {\n                this._weekdaysShortRegex = defaultWeekdaysShortRegex;\n            }\n            return this._weekdaysShortStrictRegex && isStrict\n                ? this._weekdaysShortStrictRegex\n                : this._weekdaysShortRegex;\n        }\n    }\n\n    function weekdaysMinRegex(isStrict) {\n        if (this._weekdaysParseExact) {\n            if (!hasOwnProp(this, '_weekdaysRegex')) {\n                computeWeekdaysParse.call(this);\n            }\n            if (isStrict) {\n                return this._weekdaysMinStrictRegex;\n            } else {\n                return this._weekdaysMinRegex;\n            }\n        } else {\n            if (!hasOwnProp(this, '_weekdaysMinRegex')) {\n                this._weekdaysMinRegex = defaultWeekdaysMinRegex;\n            }\n            return this._weekdaysMinStrictRegex && isStrict\n                ? this._weekdaysMinStrictRegex\n                : this._weekdaysMinRegex;\n        }\n    }\n\n    function computeWeekdaysParse() {\n        function cmpLenRev(a, b) {\n            return b.length - a.length;\n        }\n\n        var minPieces = [],\n            shortPieces = [],\n            longPieces = [],\n            mixedPieces = [],\n            i,\n            mom,\n            minp,\n            shortp,\n            longp;\n        for (i = 0; i < 7; i++) {\n            // make the regex if we don't have it already\n            mom = createUTC([2000, 1]).day(i);\n            minp = regexEscape(this.weekdaysMin(mom, ''));\n            shortp = regexEscape(this.weekdaysShort(mom, ''));\n            longp = regexEscape(this.weekdays(mom, ''));\n            minPieces.push(minp);\n            shortPieces.push(shortp);\n            longPieces.push(longp);\n            mixedPieces.push(minp);\n            mixedPieces.push(shortp);\n            mixedPieces.push(longp);\n        }\n        // Sorting makes sure if one weekday (or abbr) is a prefix of another it\n        // will match the longer piece.\n        minPieces.sort(cmpLenRev);\n        shortPieces.sort(cmpLenRev);\n        longPieces.sort(cmpLenRev);\n        mixedPieces.sort(cmpLenRev);\n\n        this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');\n        this._weekdaysShortRegex = this._weekdaysRegex;\n        this._weekdaysMinRegex = this._weekdaysRegex;\n\n        this._weekdaysStrictRegex = new RegExp(\n            '^(' + longPieces.join('|') + ')',\n            'i'\n        );\n        this._weekdaysShortStrictRegex = new RegExp(\n            '^(' + shortPieces.join('|') + ')',\n            'i'\n        );\n        this._weekdaysMinStrictRegex = new RegExp(\n            '^(' + minPieces.join('|') + ')',\n            'i'\n        );\n    }\n\n    // FORMATTING\n\n    function hFormat() {\n        return this.hours() % 12 || 12;\n    }\n\n    function kFormat() {\n        return this.hours() || 24;\n    }\n\n    addFormatToken('H', ['HH', 2], 0, 'hour');\n    addFormatToken('h', ['hh', 2], 0, hFormat);\n    addFormatToken('k', ['kk', 2], 0, kFormat);\n\n    addFormatToken('hmm', 0, 0, function () {\n        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);\n    });\n\n    addFormatToken('hmmss', 0, 0, function () {\n        return (\n            '' +\n            hFormat.apply(this) +\n            zeroFill(this.minutes(), 2) +\n            zeroFill(this.seconds(), 2)\n        );\n    });\n\n    addFormatToken('Hmm', 0, 0, function () {\n        return '' + this.hours() + zeroFill(this.minutes(), 2);\n    });\n\n    addFormatToken('Hmmss', 0, 0, function () {\n        return (\n            '' +\n            this.hours() +\n            zeroFill(this.minutes(), 2) +\n            zeroFill(this.seconds(), 2)\n        );\n    });\n\n    function meridiem(token, lowercase) {\n        addFormatToken(token, 0, 0, function () {\n            return this.localeData().meridiem(\n                this.hours(),\n                this.minutes(),\n                lowercase\n            );\n        });\n    }\n\n    meridiem('a', true);\n    meridiem('A', false);\n\n    // ALIASES\n\n    addUnitAlias('hour', 'h');\n\n    // PRIORITY\n    addUnitPriority('hour', 13);\n\n    // PARSING\n\n    function matchMeridiem(isStrict, locale) {\n        return locale._meridiemParse;\n    }\n\n    addRegexToken('a', matchMeridiem);\n    addRegexToken('A', matchMeridiem);\n    addRegexToken('H', match1to2);\n    addRegexToken('h', match1to2);\n    addRegexToken('k', match1to2);\n    addRegexToken('HH', match1to2, match2);\n    addRegexToken('hh', match1to2, match2);\n    addRegexToken('kk', match1to2, match2);\n\n    addRegexToken('hmm', match3to4);\n    addRegexToken('hmmss', match5to6);\n    addRegexToken('Hmm', match3to4);\n    addRegexToken('Hmmss', match5to6);\n\n    addParseToken(['H', 'HH'], HOUR);\n    addParseToken(['k', 'kk'], function (input, array, config) {\n        var kInput = toInt(input);\n        array[HOUR] = kInput === 24 ? 0 : kInput;\n    });\n    addParseToken(['a', 'A'], function (input, array, config) {\n        config._isPm = config._locale.isPM(input);\n        config._meridiem = input;\n    });\n    addParseToken(['h', 'hh'], function (input, array, config) {\n        array[HOUR] = toInt(input);\n        getParsingFlags(config).bigHour = true;\n    });\n    addParseToken('hmm', function (input, array, config) {\n        var pos = input.length - 2;\n        array[HOUR] = toInt(input.substr(0, pos));\n        array[MINUTE] = toInt(input.substr(pos));\n        getParsingFlags(config).bigHour = true;\n    });\n    addParseToken('hmmss', function (input, array, config) {\n        var pos1 = input.length - 4,\n            pos2 = input.length - 2;\n        array[HOUR] = toInt(input.substr(0, pos1));\n        array[MINUTE] = toInt(input.substr(pos1, 2));\n        array[SECOND] = toInt(input.substr(pos2));\n        getParsingFlags(config).bigHour = true;\n    });\n    addParseToken('Hmm', function (input, array, config) {\n        var pos = input.length - 2;\n        array[HOUR] = toInt(input.substr(0, pos));\n        array[MINUTE] = toInt(input.substr(pos));\n    });\n    addParseToken('Hmmss', function (input, array, config) {\n        var pos1 = input.length - 4,\n            pos2 = input.length - 2;\n        array[HOUR] = toInt(input.substr(0, pos1));\n        array[MINUTE] = toInt(input.substr(pos1, 2));\n        array[SECOND] = toInt(input.substr(pos2));\n    });\n\n    // LOCALES\n\n    function localeIsPM(input) {\n        // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays\n        // Using charAt should be more compatible.\n        return (input + '').toLowerCase().charAt(0) === 'p';\n    }\n\n    var defaultLocaleMeridiemParse = /[ap]\\.?m?\\.?/i,\n        // Setting the hour should keep the time, because the user explicitly\n        // specified which hour they want. So trying to maintain the same hour (in\n        // a new timezone) makes sense. Adding/subtracting hours does not follow\n        // this rule.\n        getSetHour = makeGetSet('Hours', true);\n\n    function localeMeridiem(hours, minutes, isLower) {\n        if (hours > 11) {\n            return isLower ? 'pm' : 'PM';\n        } else {\n            return isLower ? 'am' : 'AM';\n        }\n    }\n\n    var baseConfig = {\n        calendar: defaultCalendar,\n        longDateFormat: defaultLongDateFormat,\n        invalidDate: defaultInvalidDate,\n        ordinal: defaultOrdinal,\n        dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,\n        relativeTime: defaultRelativeTime,\n\n        months: defaultLocaleMonths,\n        monthsShort: defaultLocaleMonthsShort,\n\n        week: defaultLocaleWeek,\n\n        weekdays: defaultLocaleWeekdays,\n        weekdaysMin: defaultLocaleWeekdaysMin,\n        weekdaysShort: defaultLocaleWeekdaysShort,\n\n        meridiemParse: defaultLocaleMeridiemParse,\n    };\n\n    // internal storage for locale config files\n    var locales = {},\n        localeFamilies = {},\n        globalLocale;\n\n    function commonPrefix(arr1, arr2) {\n        var i,\n            minl = Math.min(arr1.length, arr2.length);\n        for (i = 0; i < minl; i += 1) {\n            if (arr1[i] !== arr2[i]) {\n                return i;\n            }\n        }\n        return minl;\n    }\n\n    function normalizeLocale(key) {\n        return key ? key.toLowerCase().replace('_', '-') : key;\n    }\n\n    // pick the locale from the array\n    // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each\n    // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root\n    function chooseLocale(names) {\n        var i = 0,\n            j,\n            next,\n            locale,\n            split;\n\n        while (i < names.length) {\n            split = normalizeLocale(names[i]).split('-');\n            j = split.length;\n            next = normalizeLocale(names[i + 1]);\n            next = next ? next.split('-') : null;\n            while (j > 0) {\n                locale = loadLocale(split.slice(0, j).join('-'));\n                if (locale) {\n                    return locale;\n                }\n                if (\n                    next &&\n                    next.length >= j &&\n                    commonPrefix(split, next) >= j - 1\n                ) {\n                    //the next array item is better than a shallower substring of this one\n                    break;\n                }\n                j--;\n            }\n            i++;\n        }\n        return globalLocale;\n    }\n\n    function loadLocale(name) {\n        var oldLocale = null,\n            aliasedRequire;\n        // TODO: Find a better way to register and load all the locales in Node\n        if (\n            locales[name] === undefined &&\n            typeof module !== 'undefined' &&\n            module &&\n            module.exports\n        ) {\n            try {\n                oldLocale = globalLocale._abbr;\n                aliasedRequire = require;\n                aliasedRequire('./locale/' + name);\n                getSetGlobalLocale(oldLocale);\n            } catch (e) {\n                // mark as not found to avoid repeating expensive file require call causing high CPU\n                // when trying to find en-US, en_US, en-us for every format call\n                locales[name] = null; // null means not found\n            }\n        }\n        return locales[name];\n    }\n\n    // This function will load locale and then set the global locale.  If\n    // no arguments are passed in, it will simply return the current global\n    // locale key.\n    function getSetGlobalLocale(key, values) {\n        var data;\n        if (key) {\n            if (isUndefined(values)) {\n                data = getLocale(key);\n            } else {\n                data = defineLocale(key, values);\n            }\n\n            if (data) {\n                // moment.duration._locale = moment._locale = data;\n                globalLocale = data;\n            } else {\n                if (typeof console !== 'undefined' && console.warn) {\n                    //warn user if arguments are passed but the locale could not be set\n                    console.warn(\n                        'Locale ' + key + ' not found. Did you forget to load it?'\n                    );\n                }\n            }\n        }\n\n        return globalLocale._abbr;\n    }\n\n    function defineLocale(name, config) {\n        if (config !== null) {\n            var locale,\n                parentConfig = baseConfig;\n            config.abbr = name;\n            if (locales[name] != null) {\n                deprecateSimple(\n                    'defineLocaleOverride',\n                    'use moment.updateLocale(localeName, config) to change ' +\n                        'an existing locale. moment.defineLocale(localeName, ' +\n                        'config) should only be used for creating a new locale ' +\n                        'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'\n                );\n                parentConfig = locales[name]._config;\n            } else if (config.parentLocale != null) {\n                if (locales[config.parentLocale] != null) {\n                    parentConfig = locales[config.parentLocale]._config;\n                } else {\n                    locale = loadLocale(config.parentLocale);\n                    if (locale != null) {\n                        parentConfig = locale._config;\n                    } else {\n                        if (!localeFamilies[config.parentLocale]) {\n                            localeFamilies[config.parentLocale] = [];\n                        }\n                        localeFamilies[config.parentLocale].push({\n                            name: name,\n                            config: config,\n                        });\n                        return null;\n                    }\n                }\n            }\n            locales[name] = new Locale(mergeConfigs(parentConfig, config));\n\n            if (localeFamilies[name]) {\n                localeFamilies[name].forEach(function (x) {\n                    defineLocale(x.name, x.config);\n                });\n            }\n\n            // backwards compat for now: also set the locale\n            // make sure we set the locale AFTER all child locales have been\n            // created, so we won't end up with the child locale set.\n            getSetGlobalLocale(name);\n\n            return locales[name];\n        } else {\n            // useful for testing\n            delete locales[name];\n            return null;\n        }\n    }\n\n    function updateLocale(name, config) {\n        if (config != null) {\n            var locale,\n                tmpLocale,\n                parentConfig = baseConfig;\n\n            if (locales[name] != null && locales[name].parentLocale != null) {\n                // Update existing child locale in-place to avoid memory-leaks\n                locales[name].set(mergeConfigs(locales[name]._config, config));\n            } else {\n                // MERGE\n                tmpLocale = loadLocale(name);\n                if (tmpLocale != null) {\n                    parentConfig = tmpLocale._config;\n                }\n                config = mergeConfigs(parentConfig, config);\n                if (tmpLocale == null) {\n                    // updateLocale is called for creating a new locale\n                    // Set abbr so it will have a name (getters return\n                    // undefined otherwise).\n                    config.abbr = name;\n                }\n                locale = new Locale(config);\n                locale.parentLocale = locales[name];\n                locales[name] = locale;\n            }\n\n            // backwards compat for now: also set the locale\n            getSetGlobalLocale(name);\n        } else {\n            // pass null for config to unupdate, useful for tests\n            if (locales[name] != null) {\n                if (locales[name].parentLocale != null) {\n                    locales[name] = locales[name].parentLocale;\n                    if (name === getSetGlobalLocale()) {\n                        getSetGlobalLocale(name);\n                    }\n                } else if (locales[name] != null) {\n                    delete locales[name];\n                }\n            }\n        }\n        return locales[name];\n    }\n\n    // returns locale data\n    function getLocale(key) {\n        var locale;\n\n        if (key && key._locale && key._locale._abbr) {\n            key = key._locale._abbr;\n        }\n\n        if (!key) {\n            return globalLocale;\n        }\n\n        if (!isArray(key)) {\n            //short-circuit everything else\n            locale = loadLocale(key);\n            if (locale) {\n                return locale;\n            }\n            key = [key];\n        }\n\n        return chooseLocale(key);\n    }\n\n    function listLocales() {\n        return keys(locales);\n    }\n\n    function checkOverflow(m) {\n        var overflow,\n            a = m._a;\n\n        if (a && getParsingFlags(m).overflow === -2) {\n            overflow =\n                a[MONTH] < 0 || a[MONTH] > 11\n                    ? MONTH\n                    : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH])\n                    ? DATE\n                    : a[HOUR] < 0 ||\n                      a[HOUR] > 24 ||\n                      (a[HOUR] === 24 &&\n                          (a[MINUTE] !== 0 ||\n                              a[SECOND] !== 0 ||\n                              a[MILLISECOND] !== 0))\n                    ? HOUR\n                    : a[MINUTE] < 0 || a[MINUTE] > 59\n                    ? MINUTE\n                    : a[SECOND] < 0 || a[SECOND] > 59\n                    ? SECOND\n                    : a[MILLISECOND] < 0 || a[MILLISECOND] > 999\n                    ? MILLISECOND\n                    : -1;\n\n            if (\n                getParsingFlags(m)._overflowDayOfYear &&\n                (overflow < YEAR || overflow > DATE)\n            ) {\n                overflow = DATE;\n            }\n            if (getParsingFlags(m)._overflowWeeks && overflow === -1) {\n                overflow = WEEK;\n            }\n            if (getParsingFlags(m)._overflowWeekday && overflow === -1) {\n                overflow = WEEKDAY;\n            }\n\n            getParsingFlags(m).overflow = overflow;\n        }\n\n        return m;\n    }\n\n    // iso 8601 regex\n    // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)\n    var extendedIsoRegex = /^\\s*((?:[+-]\\d{6}|\\d{4})-(?:\\d\\d-\\d\\d|W\\d\\d-\\d|W\\d\\d|\\d\\d\\d|\\d\\d))(?:(T| )(\\d\\d(?::\\d\\d(?::\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,\n        basicIsoRegex = /^\\s*((?:[+-]\\d{6}|\\d{4})(?:\\d\\d\\d\\d|W\\d\\d\\d|W\\d\\d|\\d\\d\\d|\\d\\d|))(?:(T| )(\\d\\d(?:\\d\\d(?:\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,\n        tzRegex = /Z|[+-]\\d\\d(?::?\\d\\d)?/,\n        isoDates = [\n            ['YYYYYY-MM-DD', /[+-]\\d{6}-\\d\\d-\\d\\d/],\n            ['YYYY-MM-DD', /\\d{4}-\\d\\d-\\d\\d/],\n            ['GGGG-[W]WW-E', /\\d{4}-W\\d\\d-\\d/],\n            ['GGGG-[W]WW', /\\d{4}-W\\d\\d/, false],\n            ['YYYY-DDD', /\\d{4}-\\d{3}/],\n            ['YYYY-MM', /\\d{4}-\\d\\d/, false],\n            ['YYYYYYMMDD', /[+-]\\d{10}/],\n            ['YYYYMMDD', /\\d{8}/],\n            ['GGGG[W]WWE', /\\d{4}W\\d{3}/],\n            ['GGGG[W]WW', /\\d{4}W\\d{2}/, false],\n            ['YYYYDDD', /\\d{7}/],\n            ['YYYYMM', /\\d{6}/, false],\n            ['YYYY', /\\d{4}/, false],\n        ],\n        // iso time formats and regexes\n        isoTimes = [\n            ['HH:mm:ss.SSSS', /\\d\\d:\\d\\d:\\d\\d\\.\\d+/],\n            ['HH:mm:ss,SSSS', /\\d\\d:\\d\\d:\\d\\d,\\d+/],\n            ['HH:mm:ss', /\\d\\d:\\d\\d:\\d\\d/],\n            ['HH:mm', /\\d\\d:\\d\\d/],\n            ['HHmmss.SSSS', /\\d\\d\\d\\d\\d\\d\\.\\d+/],\n            ['HHmmss,SSSS', /\\d\\d\\d\\d\\d\\d,\\d+/],\n            ['HHmmss', /\\d\\d\\d\\d\\d\\d/],\n            ['HHmm', /\\d\\d\\d\\d/],\n            ['HH', /\\d\\d/],\n        ],\n        aspNetJsonRegex = /^\\/?Date\\((-?\\d+)/i,\n        // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3\n        rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\\s)?(\\d{1,2})\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s(\\d{2,4})\\s(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\\d{4}))$/,\n        obsOffsets = {\n            UT: 0,\n            GMT: 0,\n            EDT: -4 * 60,\n            EST: -5 * 60,\n            CDT: -5 * 60,\n            CST: -6 * 60,\n            MDT: -6 * 60,\n            MST: -7 * 60,\n            PDT: -7 * 60,\n            PST: -8 * 60,\n        };\n\n    // date from iso format\n    function configFromISO(config) {\n        var i,\n            l,\n            string = config._i,\n            match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),\n            allowTime,\n            dateFormat,\n            timeFormat,\n            tzFormat;\n\n        if (match) {\n            getParsingFlags(config).iso = true;\n\n            for (i = 0, l = isoDates.length; i < l; i++) {\n                if (isoDates[i][1].exec(match[1])) {\n                    dateFormat = isoDates[i][0];\n                    allowTime = isoDates[i][2] !== false;\n                    break;\n                }\n            }\n            if (dateFormat == null) {\n                config._isValid = false;\n                return;\n            }\n            if (match[3]) {\n                for (i = 0, l = isoTimes.length; i < l; i++) {\n                    if (isoTimes[i][1].exec(match[3])) {\n                        // match[2] should be 'T' or space\n                        timeFormat = (match[2] || ' ') + isoTimes[i][0];\n                        break;\n                    }\n                }\n                if (timeFormat == null) {\n                    config._isValid = false;\n                    return;\n                }\n            }\n            if (!allowTime && timeFormat != null) {\n                config._isValid = false;\n                return;\n            }\n            if (match[4]) {\n                if (tzRegex.exec(match[4])) {\n                    tzFormat = 'Z';\n                } else {\n                    config._isValid = false;\n                    return;\n                }\n            }\n            config._f = dateFormat + (timeFormat || '') + (tzFormat || '');\n            configFromStringAndFormat(config);\n        } else {\n            config._isValid = false;\n        }\n    }\n\n    function extractFromRFC2822Strings(\n        yearStr,\n        monthStr,\n        dayStr,\n        hourStr,\n        minuteStr,\n        secondStr\n    ) {\n        var result = [\n            untruncateYear(yearStr),\n            defaultLocaleMonthsShort.indexOf(monthStr),\n            parseInt(dayStr, 10),\n            parseInt(hourStr, 10),\n            parseInt(minuteStr, 10),\n        ];\n\n        if (secondStr) {\n            result.push(parseInt(secondStr, 10));\n        }\n\n        return result;\n    }\n\n    function untruncateYear(yearStr) {\n        var year = parseInt(yearStr, 10);\n        if (year <= 49) {\n            return 2000 + year;\n        } else if (year <= 999) {\n            return 1900 + year;\n        }\n        return year;\n    }\n\n    function preprocessRFC2822(s) {\n        // Remove comments and folding whitespace and replace multiple-spaces with a single space\n        return s\n            .replace(/\\([^)]*\\)|[\\n\\t]/g, ' ')\n            .replace(/(\\s\\s+)/g, ' ')\n            .replace(/^\\s\\s*/, '')\n            .replace(/\\s\\s*$/, '');\n    }\n\n    function checkWeekday(weekdayStr, parsedInput, config) {\n        if (weekdayStr) {\n            // TODO: Replace the vanilla JS Date object with an independent day-of-week check.\n            var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),\n                weekdayActual = new Date(\n                    parsedInput[0],\n                    parsedInput[1],\n                    parsedInput[2]\n                ).getDay();\n            if (weekdayProvided !== weekdayActual) {\n                getParsingFlags(config).weekdayMismatch = true;\n                config._isValid = false;\n                return false;\n            }\n        }\n        return true;\n    }\n\n    function calculateOffset(obsOffset, militaryOffset, numOffset) {\n        if (obsOffset) {\n            return obsOffsets[obsOffset];\n        } else if (militaryOffset) {\n            // the only allowed military tz is Z\n            return 0;\n        } else {\n            var hm = parseInt(numOffset, 10),\n                m = hm % 100,\n                h = (hm - m) / 100;\n            return h * 60 + m;\n        }\n    }\n\n    // date and time from ref 2822 format\n    function configFromRFC2822(config) {\n        var match = rfc2822.exec(preprocessRFC2822(config._i)),\n            parsedArray;\n        if (match) {\n            parsedArray = extractFromRFC2822Strings(\n                match[4],\n                match[3],\n                match[2],\n                match[5],\n                match[6],\n                match[7]\n            );\n            if (!checkWeekday(match[1], parsedArray, config)) {\n                return;\n            }\n\n            config._a = parsedArray;\n            config._tzm = calculateOffset(match[8], match[9], match[10]);\n\n            config._d = createUTCDate.apply(null, config._a);\n            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);\n\n            getParsingFlags(config).rfc2822 = true;\n        } else {\n            config._isValid = false;\n        }\n    }\n\n    // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict\n    function configFromString(config) {\n        var matched = aspNetJsonRegex.exec(config._i);\n        if (matched !== null) {\n            config._d = new Date(+matched[1]);\n            return;\n        }\n\n        configFromISO(config);\n        if (config._isValid === false) {\n            delete config._isValid;\n        } else {\n            return;\n        }\n\n        configFromRFC2822(config);\n        if (config._isValid === false) {\n            delete config._isValid;\n        } else {\n            return;\n        }\n\n        if (config._strict) {\n            config._isValid = false;\n        } else {\n            // Final attempt, use Input Fallback\n            hooks.createFromInputFallback(config);\n        }\n    }\n\n    hooks.createFromInputFallback = deprecate(\n        'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +\n            'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +\n            'discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.',\n        function (config) {\n            config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));\n        }\n    );\n\n    // Pick the first defined of two or three arguments.\n    function defaults(a, b, c) {\n        if (a != null) {\n            return a;\n        }\n        if (b != null) {\n            return b;\n        }\n        return c;\n    }\n\n    function currentDateArray(config) {\n        // hooks is actually the exported moment object\n        var nowValue = new Date(hooks.now());\n        if (config._useUTC) {\n            return [\n                nowValue.getUTCFullYear(),\n                nowValue.getUTCMonth(),\n                nowValue.getUTCDate(),\n            ];\n        }\n        return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];\n    }\n\n    // convert an array to a date.\n    // the array should mirror the parameters below\n    // note: all values past the year are optional and will default to the lowest possible value.\n    // [year, month, day , hour, minute, second, millisecond]\n    function configFromArray(config) {\n        var i,\n            date,\n            input = [],\n            currentDate,\n            expectedWeekday,\n            yearToUse;\n\n        if (config._d) {\n            return;\n        }\n\n        currentDate = currentDateArray(config);\n\n        //compute day of the year from weeks and weekdays\n        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {\n            dayOfYearFromWeekInfo(config);\n        }\n\n        //if the day of the year is set, figure out what it is\n        if (config._dayOfYear != null) {\n            yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);\n\n            if (\n                config._dayOfYear > daysInYear(yearToUse) ||\n                config._dayOfYear === 0\n            ) {\n                getParsingFlags(config)._overflowDayOfYear = true;\n            }\n\n            date = createUTCDate(yearToUse, 0, config._dayOfYear);\n            config._a[MONTH] = date.getUTCMonth();\n            config._a[DATE] = date.getUTCDate();\n        }\n\n        // Default to current date.\n        // * if no year, month, day of month are given, default to today\n        // * if day of month is given, default month and year\n        // * if month is given, default only year\n        // * if year is given, don't default anything\n        for (i = 0; i < 3 && config._a[i] == null; ++i) {\n            config._a[i] = input[i] = currentDate[i];\n        }\n\n        // Zero out whatever was not defaulted, including time\n        for (; i < 7; i++) {\n            config._a[i] = input[i] =\n                config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i];\n        }\n\n        // Check for 24:00:00.000\n        if (\n            config._a[HOUR] === 24 &&\n            config._a[MINUTE] === 0 &&\n            config._a[SECOND] === 0 &&\n            config._a[MILLISECOND] === 0\n        ) {\n            config._nextDay = true;\n            config._a[HOUR] = 0;\n        }\n\n        config._d = (config._useUTC ? createUTCDate : createDate).apply(\n            null,\n            input\n        );\n        expectedWeekday = config._useUTC\n            ? config._d.getUTCDay()\n            : config._d.getDay();\n\n        // Apply timezone offset from input. The actual utcOffset can be changed\n        // with parseZone.\n        if (config._tzm != null) {\n            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);\n        }\n\n        if (config._nextDay) {\n            config._a[HOUR] = 24;\n        }\n\n        // check for mismatching day of week\n        if (\n            config._w &&\n            typeof config._w.d !== 'undefined' &&\n            config._w.d !== expectedWeekday\n        ) {\n            getParsingFlags(config).weekdayMismatch = true;\n        }\n    }\n\n    function dayOfYearFromWeekInfo(config) {\n        var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek;\n\n        w = config._w;\n        if (w.GG != null || w.W != null || w.E != null) {\n            dow = 1;\n            doy = 4;\n\n            // TODO: We need to take the current isoWeekYear, but that depends on\n            // how we interpret now (local, utc, fixed offset). So create\n            // a now version of current config (take local/utc/offset flags, and\n            // create now).\n            weekYear = defaults(\n                w.GG,\n                config._a[YEAR],\n                weekOfYear(createLocal(), 1, 4).year\n            );\n            week = defaults(w.W, 1);\n            weekday = defaults(w.E, 1);\n            if (weekday < 1 || weekday > 7) {\n                weekdayOverflow = true;\n            }\n        } else {\n            dow = config._locale._week.dow;\n            doy = config._locale._week.doy;\n\n            curWeek = weekOfYear(createLocal(), dow, doy);\n\n            weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);\n\n            // Default to current week.\n            week = defaults(w.w, curWeek.week);\n\n            if (w.d != null) {\n                // weekday -- low day numbers are considered next week\n                weekday = w.d;\n                if (weekday < 0 || weekday > 6) {\n                    weekdayOverflow = true;\n                }\n            } else if (w.e != null) {\n                // local weekday -- counting starts from beginning of week\n                weekday = w.e + dow;\n                if (w.e < 0 || w.e > 6) {\n                    weekdayOverflow = true;\n                }\n            } else {\n                // default to beginning of week\n                weekday = dow;\n            }\n        }\n        if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {\n            getParsingFlags(config)._overflowWeeks = true;\n        } else if (weekdayOverflow != null) {\n            getParsingFlags(config)._overflowWeekday = true;\n        } else {\n            temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);\n            config._a[YEAR] = temp.year;\n            config._dayOfYear = temp.dayOfYear;\n        }\n    }\n\n    // constant that refers to the ISO standard\n    hooks.ISO_8601 = function () {};\n\n    // constant that refers to the RFC 2822 form\n    hooks.RFC_2822 = function () {};\n\n    // date from string and format string\n    function configFromStringAndFormat(config) {\n        // TODO: Move this to another part of the creation flow to prevent circular deps\n        if (config._f === hooks.ISO_8601) {\n            configFromISO(config);\n            return;\n        }\n        if (config._f === hooks.RFC_2822) {\n            configFromRFC2822(config);\n            return;\n        }\n        config._a = [];\n        getParsingFlags(config).empty = true;\n\n        // This array is used to make a Date, either with `new Date` or `Date.UTC`\n        var string = '' + config._i,\n            i,\n            parsedInput,\n            tokens,\n            token,\n            skipped,\n            stringLength = string.length,\n            totalParsedInputLength = 0,\n            era;\n\n        tokens =\n            expandFormat(config._f, config._locale).match(formattingTokens) || [];\n\n        for (i = 0; i < tokens.length; i++) {\n            token = tokens[i];\n            parsedInput = (string.match(getParseRegexForToken(token, config)) ||\n                [])[0];\n            if (parsedInput) {\n                skipped = string.substr(0, string.indexOf(parsedInput));\n                if (skipped.length > 0) {\n                    getParsingFlags(config).unusedInput.push(skipped);\n                }\n                string = string.slice(\n                    string.indexOf(parsedInput) + parsedInput.length\n                );\n                totalParsedInputLength += parsedInput.length;\n            }\n            // don't parse if it's not a known token\n            if (formatTokenFunctions[token]) {\n                if (parsedInput) {\n                    getParsingFlags(config).empty = false;\n                } else {\n                    getParsingFlags(config).unusedTokens.push(token);\n                }\n                addTimeToArrayFromToken(token, parsedInput, config);\n            } else if (config._strict && !parsedInput) {\n                getParsingFlags(config).unusedTokens.push(token);\n            }\n        }\n\n        // add remaining unparsed input length to the string\n        getParsingFlags(config).charsLeftOver =\n            stringLength - totalParsedInputLength;\n        if (string.length > 0) {\n            getParsingFlags(config).unusedInput.push(string);\n        }\n\n        // clear _12h flag if hour is <= 12\n        if (\n            config._a[HOUR] <= 12 &&\n            getParsingFlags(config).bigHour === true &&\n            config._a[HOUR] > 0\n        ) {\n            getParsingFlags(config).bigHour = undefined;\n        }\n\n        getParsingFlags(config).parsedDateParts = config._a.slice(0);\n        getParsingFlags(config).meridiem = config._meridiem;\n        // handle meridiem\n        config._a[HOUR] = meridiemFixWrap(\n            config._locale,\n            config._a[HOUR],\n            config._meridiem\n        );\n\n        // handle era\n        era = getParsingFlags(config).era;\n        if (era !== null) {\n            config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]);\n        }\n\n        configFromArray(config);\n        checkOverflow(config);\n    }\n\n    function meridiemFixWrap(locale, hour, meridiem) {\n        var isPm;\n\n        if (meridiem == null) {\n            // nothing to do\n            return hour;\n        }\n        if (locale.meridiemHour != null) {\n            return locale.meridiemHour(hour, meridiem);\n        } else if (locale.isPM != null) {\n            // Fallback\n            isPm = locale.isPM(meridiem);\n            if (isPm && hour < 12) {\n                hour += 12;\n            }\n            if (!isPm && hour === 12) {\n                hour = 0;\n            }\n            return hour;\n        } else {\n            // this is not supposed to happen\n            return hour;\n        }\n    }\n\n    // date from string and array of format strings\n    function configFromStringAndArray(config) {\n        var tempConfig,\n            bestMoment,\n            scoreToBeat,\n            i,\n            currentScore,\n            validFormatFound,\n            bestFormatIsValid = false;\n\n        if (config._f.length === 0) {\n            getParsingFlags(config).invalidFormat = true;\n            config._d = new Date(NaN);\n            return;\n        }\n\n        for (i = 0; i < config._f.length; i++) {\n            currentScore = 0;\n            validFormatFound = false;\n            tempConfig = copyConfig({}, config);\n            if (config._useUTC != null) {\n                tempConfig._useUTC = config._useUTC;\n            }\n            tempConfig._f = config._f[i];\n            configFromStringAndFormat(tempConfig);\n\n            if (isValid(tempConfig)) {\n                validFormatFound = true;\n            }\n\n            // if there is any input that was not parsed add a penalty for that format\n            currentScore += getParsingFlags(tempConfig).charsLeftOver;\n\n            //or tokens\n            currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;\n\n            getParsingFlags(tempConfig).score = currentScore;\n\n            if (!bestFormatIsValid) {\n                if (\n                    scoreToBeat == null ||\n                    currentScore < scoreToBeat ||\n                    validFormatFound\n                ) {\n                    scoreToBeat = currentScore;\n                    bestMoment = tempConfig;\n                    if (validFormatFound) {\n                        bestFormatIsValid = true;\n                    }\n                }\n            } else {\n                if (currentScore < scoreToBeat) {\n                    scoreToBeat = currentScore;\n                    bestMoment = tempConfig;\n                }\n            }\n        }\n\n        extend(config, bestMoment || tempConfig);\n    }\n\n    function configFromObject(config) {\n        if (config._d) {\n            return;\n        }\n\n        var i = normalizeObjectUnits(config._i),\n            dayOrDate = i.day === undefined ? i.date : i.day;\n        config._a = map(\n            [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond],\n            function (obj) {\n                return obj && parseInt(obj, 10);\n            }\n        );\n\n        configFromArray(config);\n    }\n\n    function createFromConfig(config) {\n        var res = new Moment(checkOverflow(prepareConfig(config)));\n        if (res._nextDay) {\n            // Adding is smart enough around DST\n            res.add(1, 'd');\n            res._nextDay = undefined;\n        }\n\n        return res;\n    }\n\n    function prepareConfig(config) {\n        var input = config._i,\n            format = config._f;\n\n        config._locale = config._locale || getLocale(config._l);\n\n        if (input === null || (format === undefined && input === '')) {\n            return createInvalid({ nullInput: true });\n        }\n\n        if (typeof input === 'string') {\n            config._i = input = config._locale.preparse(input);\n        }\n\n        if (isMoment(input)) {\n            return new Moment(checkOverflow(input));\n        } else if (isDate(input)) {\n            config._d = input;\n        } else if (isArray(format)) {\n            configFromStringAndArray(config);\n        } else if (format) {\n            configFromStringAndFormat(config);\n        } else {\n            configFromInput(config);\n        }\n\n        if (!isValid(config)) {\n            config._d = null;\n        }\n\n        return config;\n    }\n\n    function configFromInput(config) {\n        var input = config._i;\n        if (isUndefined(input)) {\n            config._d = new Date(hooks.now());\n        } else if (isDate(input)) {\n            config._d = new Date(input.valueOf());\n        } else if (typeof input === 'string') {\n            configFromString(config);\n        } else if (isArray(input)) {\n            config._a = map(input.slice(0), function (obj) {\n                return parseInt(obj, 10);\n            });\n            configFromArray(config);\n        } else if (isObject(input)) {\n            configFromObject(config);\n        } else if (isNumber(input)) {\n            // from milliseconds\n            config._d = new Date(input);\n        } else {\n            hooks.createFromInputFallback(config);\n        }\n    }\n\n    function createLocalOrUTC(input, format, locale, strict, isUTC) {\n        var c = {};\n\n        if (format === true || format === false) {\n            strict = format;\n            format = undefined;\n        }\n\n        if (locale === true || locale === false) {\n            strict = locale;\n            locale = undefined;\n        }\n\n        if (\n            (isObject(input) && isObjectEmpty(input)) ||\n            (isArray(input) && input.length === 0)\n        ) {\n            input = undefined;\n        }\n        // object construction must be done this way.\n        // https://github.com/moment/moment/issues/1423\n        c._isAMomentObject = true;\n        c._useUTC = c._isUTC = isUTC;\n        c._l = locale;\n        c._i = input;\n        c._f = format;\n        c._strict = strict;\n\n        return createFromConfig(c);\n    }\n\n    function createLocal(input, format, locale, strict) {\n        return createLocalOrUTC(input, format, locale, strict, false);\n    }\n\n    var prototypeMin = deprecate(\n            'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',\n            function () {\n                var other = createLocal.apply(null, arguments);\n                if (this.isValid() && other.isValid()) {\n                    return other < this ? this : other;\n                } else {\n                    return createInvalid();\n                }\n            }\n        ),\n        prototypeMax = deprecate(\n            'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',\n            function () {\n                var other = createLocal.apply(null, arguments);\n                if (this.isValid() && other.isValid()) {\n                    return other > this ? this : other;\n                } else {\n                    return createInvalid();\n                }\n            }\n        );\n\n    // Pick a moment m from moments so that m[fn](other) is true for all\n    // other. This relies on the function fn to be transitive.\n    //\n    // moments should either be an array of moment objects or an array, whose\n    // first element is an array of moment objects.\n    function pickBy(fn, moments) {\n        var res, i;\n        if (moments.length === 1 && isArray(moments[0])) {\n            moments = moments[0];\n        }\n        if (!moments.length) {\n            return createLocal();\n        }\n        res = moments[0];\n        for (i = 1; i < moments.length; ++i) {\n            if (!moments[i].isValid() || moments[i][fn](res)) {\n                res = moments[i];\n            }\n        }\n        return res;\n    }\n\n    // TODO: Use [].sort instead?\n    function min() {\n        var args = [].slice.call(arguments, 0);\n\n        return pickBy('isBefore', args);\n    }\n\n    function max() {\n        var args = [].slice.call(arguments, 0);\n\n        return pickBy('isAfter', args);\n    }\n\n    var now = function () {\n        return Date.now ? Date.now() : +new Date();\n    };\n\n    var ordering = [\n        'year',\n        'quarter',\n        'month',\n        'week',\n        'day',\n        'hour',\n        'minute',\n        'second',\n        'millisecond',\n    ];\n\n    function isDurationValid(m) {\n        var key,\n            unitHasDecimal = false,\n            i;\n        for (key in m) {\n            if (\n                hasOwnProp(m, key) &&\n                !(\n                    indexOf.call(ordering, key) !== -1 &&\n                    (m[key] == null || !isNaN(m[key]))\n                )\n            ) {\n                return false;\n            }\n        }\n\n        for (i = 0; i < ordering.length; ++i) {\n            if (m[ordering[i]]) {\n                if (unitHasDecimal) {\n                    return false; // only allow non-integers for smallest unit\n                }\n                if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {\n                    unitHasDecimal = true;\n                }\n            }\n        }\n\n        return true;\n    }\n\n    function isValid$1() {\n        return this._isValid;\n    }\n\n    function createInvalid$1() {\n        return createDuration(NaN);\n    }\n\n    function Duration(duration) {\n        var normalizedInput = normalizeObjectUnits(duration),\n            years = normalizedInput.year || 0,\n            quarters = normalizedInput.quarter || 0,\n            months = normalizedInput.month || 0,\n            weeks = normalizedInput.week || normalizedInput.isoWeek || 0,\n            days = normalizedInput.day || 0,\n            hours = normalizedInput.hour || 0,\n            minutes = normalizedInput.minute || 0,\n            seconds = normalizedInput.second || 0,\n            milliseconds = normalizedInput.millisecond || 0;\n\n        this._isValid = isDurationValid(normalizedInput);\n\n        // representation for dateAddRemove\n        this._milliseconds =\n            +milliseconds +\n            seconds * 1e3 + // 1000\n            minutes * 6e4 + // 1000 * 60\n            hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978\n        // Because of dateAddRemove treats 24 hours as different from a\n        // day when working around DST, we need to store them separately\n        this._days = +days + weeks * 7;\n        // It is impossible to translate months into days without knowing\n        // which months you are are talking about, so we have to store\n        // it separately.\n        this._months = +months + quarters * 3 + years * 12;\n\n        this._data = {};\n\n        this._locale = getLocale();\n\n        this._bubble();\n    }\n\n    function isDuration(obj) {\n        return obj instanceof Duration;\n    }\n\n    function absRound(number) {\n        if (number < 0) {\n            return Math.round(-1 * number) * -1;\n        } else {\n            return Math.round(number);\n        }\n    }\n\n    // compare two arrays, return the number of differences\n    function compareArrays(array1, array2, dontConvert) {\n        var len = Math.min(array1.length, array2.length),\n            lengthDiff = Math.abs(array1.length - array2.length),\n            diffs = 0,\n            i;\n        for (i = 0; i < len; i++) {\n            if (\n                (dontConvert && array1[i] !== array2[i]) ||\n                (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))\n            ) {\n                diffs++;\n            }\n        }\n        return diffs + lengthDiff;\n    }\n\n    // FORMATTING\n\n    function offset(token, separator) {\n        addFormatToken(token, 0, 0, function () {\n            var offset = this.utcOffset(),\n                sign = '+';\n            if (offset < 0) {\n                offset = -offset;\n                sign = '-';\n            }\n            return (\n                sign +\n                zeroFill(~~(offset / 60), 2) +\n                separator +\n                zeroFill(~~offset % 60, 2)\n            );\n        });\n    }\n\n    offset('Z', ':');\n    offset('ZZ', '');\n\n    // PARSING\n\n    addRegexToken('Z', matchShortOffset);\n    addRegexToken('ZZ', matchShortOffset);\n    addParseToken(['Z', 'ZZ'], function (input, array, config) {\n        config._useUTC = true;\n        config._tzm = offsetFromString(matchShortOffset, input);\n    });\n\n    // HELPERS\n\n    // timezone chunker\n    // '+10:00' > ['10',  '00']\n    // '-1530'  > ['-15', '30']\n    var chunkOffset = /([\\+\\-]|\\d\\d)/gi;\n\n    function offsetFromString(matcher, string) {\n        var matches = (string || '').match(matcher),\n            chunk,\n            parts,\n            minutes;\n\n        if (matches === null) {\n            return null;\n        }\n\n        chunk = matches[matches.length - 1] || [];\n        parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];\n        minutes = +(parts[1] * 60) + toInt(parts[2]);\n\n        return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes;\n    }\n\n    // Return a moment from input, that is local/utc/zone equivalent to model.\n    function cloneWithOffset(input, model) {\n        var res, diff;\n        if (model._isUTC) {\n            res = model.clone();\n            diff =\n                (isMoment(input) || isDate(input)\n                    ? input.valueOf()\n                    : createLocal(input).valueOf()) - res.valueOf();\n            // Use low-level api, because this fn is low-level api.\n            res._d.setTime(res._d.valueOf() + diff);\n            hooks.updateOffset(res, false);\n            return res;\n        } else {\n            return createLocal(input).local();\n        }\n    }\n\n    function getDateOffset(m) {\n        // On Firefox.24 Date#getTimezoneOffset returns a floating point.\n        // https://github.com/moment/moment/pull/1871\n        return -Math.round(m._d.getTimezoneOffset());\n    }\n\n    // HOOKS\n\n    // This function will be called whenever a moment is mutated.\n    // It is intended to keep the offset in sync with the timezone.\n    hooks.updateOffset = function () {};\n\n    // MOMENTS\n\n    // keepLocalTime = true means only change the timezone, without\n    // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->\n    // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset\n    // +0200, so we adjust the time as needed, to be valid.\n    //\n    // Keeping the time actually adds/subtracts (one hour)\n    // from the actual represented time. That is why we call updateOffset\n    // a second time. In case it wants us to change the offset again\n    // _changeInProgress == true case, then we have to adjust, because\n    // there is no such time in the given timezone.\n    function getSetOffset(input, keepLocalTime, keepMinutes) {\n        var offset = this._offset || 0,\n            localAdjust;\n        if (!this.isValid()) {\n            return input != null ? this : NaN;\n        }\n        if (input != null) {\n            if (typeof input === 'string') {\n                input = offsetFromString(matchShortOffset, input);\n                if (input === null) {\n                    return this;\n                }\n            } else if (Math.abs(input) < 16 && !keepMinutes) {\n                input = input * 60;\n            }\n            if (!this._isUTC && keepLocalTime) {\n                localAdjust = getDateOffset(this);\n            }\n            this._offset = input;\n            this._isUTC = true;\n            if (localAdjust != null) {\n                this.add(localAdjust, 'm');\n            }\n            if (offset !== input) {\n                if (!keepLocalTime || this._changeInProgress) {\n                    addSubtract(\n                        this,\n                        createDuration(input - offset, 'm'),\n                        1,\n                        false\n                    );\n                } else if (!this._changeInProgress) {\n                    this._changeInProgress = true;\n                    hooks.updateOffset(this, true);\n                    this._changeInProgress = null;\n                }\n            }\n            return this;\n        } else {\n            return this._isUTC ? offset : getDateOffset(this);\n        }\n    }\n\n    function getSetZone(input, keepLocalTime) {\n        if (input != null) {\n            if (typeof input !== 'string') {\n                input = -input;\n            }\n\n            this.utcOffset(input, keepLocalTime);\n\n            return this;\n        } else {\n            return -this.utcOffset();\n        }\n    }\n\n    function setOffsetToUTC(keepLocalTime) {\n        return this.utcOffset(0, keepLocalTime);\n    }\n\n    function setOffsetToLocal(keepLocalTime) {\n        if (this._isUTC) {\n            this.utcOffset(0, keepLocalTime);\n            this._isUTC = false;\n\n            if (keepLocalTime) {\n                this.subtract(getDateOffset(this), 'm');\n            }\n        }\n        return this;\n    }\n\n    function setOffsetToParsedOffset() {\n        if (this._tzm != null) {\n            this.utcOffset(this._tzm, false, true);\n        } else if (typeof this._i === 'string') {\n            var tZone = offsetFromString(matchOffset, this._i);\n            if (tZone != null) {\n                this.utcOffset(tZone);\n            } else {\n                this.utcOffset(0, true);\n            }\n        }\n        return this;\n    }\n\n    function hasAlignedHourOffset(input) {\n        if (!this.isValid()) {\n            return false;\n        }\n        input = input ? createLocal(input).utcOffset() : 0;\n\n        return (this.utcOffset() - input) % 60 === 0;\n    }\n\n    function isDaylightSavingTime() {\n        return (\n            this.utcOffset() > this.clone().month(0).utcOffset() ||\n            this.utcOffset() > this.clone().month(5).utcOffset()\n        );\n    }\n\n    function isDaylightSavingTimeShifted() {\n        if (!isUndefined(this._isDSTShifted)) {\n            return this._isDSTShifted;\n        }\n\n        var c = {},\n            other;\n\n        copyConfig(c, this);\n        c = prepareConfig(c);\n\n        if (c._a) {\n            other = c._isUTC ? createUTC(c._a) : createLocal(c._a);\n            this._isDSTShifted =\n                this.isValid() && compareArrays(c._a, other.toArray()) > 0;\n        } else {\n            this._isDSTShifted = false;\n        }\n\n        return this._isDSTShifted;\n    }\n\n    function isLocal() {\n        return this.isValid() ? !this._isUTC : false;\n    }\n\n    function isUtcOffset() {\n        return this.isValid() ? this._isUTC : false;\n    }\n\n    function isUtc() {\n        return this.isValid() ? this._isUTC && this._offset === 0 : false;\n    }\n\n    // ASP.NET json date format regex\n    var aspNetRegex = /^(-|\\+)?(?:(\\d*)[. ])?(\\d+):(\\d+)(?::(\\d+)(\\.\\d*)?)?$/,\n        // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html\n        // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere\n        // and further modified to allow for strings containing both week and day\n        isoRegex = /^(-|\\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;\n\n    function createDuration(input, key) {\n        var duration = input,\n            // matching against regexp is expensive, do it on demand\n            match = null,\n            sign,\n            ret,\n            diffRes;\n\n        if (isDuration(input)) {\n            duration = {\n                ms: input._milliseconds,\n                d: input._days,\n                M: input._months,\n            };\n        } else if (isNumber(input) || !isNaN(+input)) {\n            duration = {};\n            if (key) {\n                duration[key] = +input;\n            } else {\n                duration.milliseconds = +input;\n            }\n        } else if ((match = aspNetRegex.exec(input))) {\n            sign = match[1] === '-' ? -1 : 1;\n            duration = {\n                y: 0,\n                d: toInt(match[DATE]) * sign,\n                h: toInt(match[HOUR]) * sign,\n                m: toInt(match[MINUTE]) * sign,\n                s: toInt(match[SECOND]) * sign,\n                ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match\n            };\n        } else if ((match = isoRegex.exec(input))) {\n            sign = match[1] === '-' ? -1 : 1;\n            duration = {\n                y: parseIso(match[2], sign),\n                M: parseIso(match[3], sign),\n                w: parseIso(match[4], sign),\n                d: parseIso(match[5], sign),\n                h: parseIso(match[6], sign),\n                m: parseIso(match[7], sign),\n                s: parseIso(match[8], sign),\n            };\n        } else if (duration == null) {\n            // checks for null or undefined\n            duration = {};\n        } else if (\n            typeof duration === 'object' &&\n            ('from' in duration || 'to' in duration)\n        ) {\n            diffRes = momentsDifference(\n                createLocal(duration.from),\n                createLocal(duration.to)\n            );\n\n            duration = {};\n            duration.ms = diffRes.milliseconds;\n            duration.M = diffRes.months;\n        }\n\n        ret = new Duration(duration);\n\n        if (isDuration(input) && hasOwnProp(input, '_locale')) {\n            ret._locale = input._locale;\n        }\n\n        if (isDuration(input) && hasOwnProp(input, '_isValid')) {\n            ret._isValid = input._isValid;\n        }\n\n        return ret;\n    }\n\n    createDuration.fn = Duration.prototype;\n    createDuration.invalid = createInvalid$1;\n\n    function parseIso(inp, sign) {\n        // We'd normally use ~~inp for this, but unfortunately it also\n        // converts floats to ints.\n        // inp may be undefined, so careful calling replace on it.\n        var res = inp && parseFloat(inp.replace(',', '.'));\n        // apply sign while we're at it\n        return (isNaN(res) ? 0 : res) * sign;\n    }\n\n    function positiveMomentsDifference(base, other) {\n        var res = {};\n\n        res.months =\n            other.month() - base.month() + (other.year() - base.year()) * 12;\n        if (base.clone().add(res.months, 'M').isAfter(other)) {\n            --res.months;\n        }\n\n        res.milliseconds = +other - +base.clone().add(res.months, 'M');\n\n        return res;\n    }\n\n    function momentsDifference(base, other) {\n        var res;\n        if (!(base.isValid() && other.isValid())) {\n            return { milliseconds: 0, months: 0 };\n        }\n\n        other = cloneWithOffset(other, base);\n        if (base.isBefore(other)) {\n            res = positiveMomentsDifference(base, other);\n        } else {\n            res = positiveMomentsDifference(other, base);\n            res.milliseconds = -res.milliseconds;\n            res.months = -res.months;\n        }\n\n        return res;\n    }\n\n    // TODO: remove 'name' arg after deprecation is removed\n    function createAdder(direction, name) {\n        return function (val, period) {\n            var dur, tmp;\n            //invert the arguments, but complain about it\n            if (period !== null && !isNaN(+period)) {\n                deprecateSimple(\n                    name,\n                    'moment().' +\n                        name +\n                        '(period, number) is deprecated. Please use moment().' +\n                        name +\n                        '(number, period). ' +\n                        'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'\n                );\n                tmp = val;\n                val = period;\n                period = tmp;\n            }\n\n            dur = createDuration(val, period);\n            addSubtract(this, dur, direction);\n            return this;\n        };\n    }\n\n    function addSubtract(mom, duration, isAdding, updateOffset) {\n        var milliseconds = duration._milliseconds,\n            days = absRound(duration._days),\n            months = absRound(duration._months);\n\n        if (!mom.isValid()) {\n            // No op\n            return;\n        }\n\n        updateOffset = updateOffset == null ? true : updateOffset;\n\n        if (months) {\n            setMonth(mom, get(mom, 'Month') + months * isAdding);\n        }\n        if (days) {\n            set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);\n        }\n        if (milliseconds) {\n            mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);\n        }\n        if (updateOffset) {\n            hooks.updateOffset(mom, days || months);\n        }\n    }\n\n    var add = createAdder(1, 'add'),\n        subtract = createAdder(-1, 'subtract');\n\n    function isString(input) {\n        return typeof input === 'string' || input instanceof String;\n    }\n\n    // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined\n    function isMomentInput(input) {\n        return (\n            isMoment(input) ||\n            isDate(input) ||\n            isString(input) ||\n            isNumber(input) ||\n            isNumberOrStringArray(input) ||\n            isMomentInputObject(input) ||\n            input === null ||\n            input === undefined\n        );\n    }\n\n    function isMomentInputObject(input) {\n        var objectTest = isObject(input) && !isObjectEmpty(input),\n            propertyTest = false,\n            properties = [\n                'years',\n                'year',\n                'y',\n                'months',\n                'month',\n                'M',\n                'days',\n                'day',\n                'd',\n                'dates',\n                'date',\n                'D',\n                'hours',\n                'hour',\n                'h',\n                'minutes',\n                'minute',\n                'm',\n                'seconds',\n                'second',\n                's',\n                'milliseconds',\n                'millisecond',\n                'ms',\n            ],\n            i,\n            property;\n\n        for (i = 0; i < properties.length; i += 1) {\n            property = properties[i];\n            propertyTest = propertyTest || hasOwnProp(input, property);\n        }\n\n        return objectTest && propertyTest;\n    }\n\n    function isNumberOrStringArray(input) {\n        var arrayTest = isArray(input),\n            dataTypeTest = false;\n        if (arrayTest) {\n            dataTypeTest =\n                input.filter(function (item) {\n                    return !isNumber(item) && isString(input);\n                }).length === 0;\n        }\n        return arrayTest && dataTypeTest;\n    }\n\n    function isCalendarSpec(input) {\n        var objectTest = isObject(input) && !isObjectEmpty(input),\n            propertyTest = false,\n            properties = [\n                'sameDay',\n                'nextDay',\n                'lastDay',\n                'nextWeek',\n                'lastWeek',\n                'sameElse',\n            ],\n            i,\n            property;\n\n        for (i = 0; i < properties.length; i += 1) {\n            property = properties[i];\n            propertyTest = propertyTest || hasOwnProp(input, property);\n        }\n\n        return objectTest && propertyTest;\n    }\n\n    function getCalendarFormat(myMoment, now) {\n        var diff = myMoment.diff(now, 'days', true);\n        return diff < -6\n            ? 'sameElse'\n            : diff < -1\n            ? 'lastWeek'\n            : diff < 0\n            ? 'lastDay'\n            : diff < 1\n            ? 'sameDay'\n            : diff < 2\n            ? 'nextDay'\n            : diff < 7\n            ? 'nextWeek'\n            : 'sameElse';\n    }\n\n    function calendar$1(time, formats) {\n        // Support for single parameter, formats only overload to the calendar function\n        if (arguments.length === 1) {\n            if (!arguments[0]) {\n                time = undefined;\n                formats = undefined;\n            } else if (isMomentInput(arguments[0])) {\n                time = arguments[0];\n                formats = undefined;\n            } else if (isCalendarSpec(arguments[0])) {\n                formats = arguments[0];\n                time = undefined;\n            }\n        }\n        // We want to compare the start of today, vs this.\n        // Getting start-of-today depends on whether we're local/utc/offset or not.\n        var now = time || createLocal(),\n            sod = cloneWithOffset(now, this).startOf('day'),\n            format = hooks.calendarFormat(this, sod) || 'sameElse',\n            output =\n                formats &&\n                (isFunction(formats[format])\n                    ? formats[format].call(this, now)\n                    : formats[format]);\n\n        return this.format(\n            output || this.localeData().calendar(format, this, createLocal(now))\n        );\n    }\n\n    function clone() {\n        return new Moment(this);\n    }\n\n    function isAfter(input, units) {\n        var localInput = isMoment(input) ? input : createLocal(input);\n        if (!(this.isValid() && localInput.isValid())) {\n            return false;\n        }\n        units = normalizeUnits(units) || 'millisecond';\n        if (units === 'millisecond') {\n            return this.valueOf() > localInput.valueOf();\n        } else {\n            return localInput.valueOf() < this.clone().startOf(units).valueOf();\n        }\n    }\n\n    function isBefore(input, units) {\n        var localInput = isMoment(input) ? input : createLocal(input);\n        if (!(this.isValid() && localInput.isValid())) {\n            return false;\n        }\n        units = normalizeUnits(units) || 'millisecond';\n        if (units === 'millisecond') {\n            return this.valueOf() < localInput.valueOf();\n        } else {\n            return this.clone().endOf(units).valueOf() < localInput.valueOf();\n        }\n    }\n\n    function isBetween(from, to, units, inclusivity) {\n        var localFrom = isMoment(from) ? from : createLocal(from),\n            localTo = isMoment(to) ? to : createLocal(to);\n        if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) {\n            return false;\n        }\n        inclusivity = inclusivity || '()';\n        return (\n            (inclusivity[0] === '('\n                ? this.isAfter(localFrom, units)\n                : !this.isBefore(localFrom, units)) &&\n            (inclusivity[1] === ')'\n                ? this.isBefore(localTo, units)\n                : !this.isAfter(localTo, units))\n        );\n    }\n\n    function isSame(input, units) {\n        var localInput = isMoment(input) ? input : createLocal(input),\n            inputMs;\n        if (!(this.isValid() && localInput.isValid())) {\n            return false;\n        }\n        units = normalizeUnits(units) || 'millisecond';\n        if (units === 'millisecond') {\n            return this.valueOf() === localInput.valueOf();\n        } else {\n            inputMs = localInput.valueOf();\n            return (\n                this.clone().startOf(units).valueOf() <= inputMs &&\n                inputMs <= this.clone().endOf(units).valueOf()\n            );\n        }\n    }\n\n    function isSameOrAfter(input, units) {\n        return this.isSame(input, units) || this.isAfter(input, units);\n    }\n\n    function isSameOrBefore(input, units) {\n        return this.isSame(input, units) || this.isBefore(input, units);\n    }\n\n    function diff(input, units, asFloat) {\n        var that, zoneDelta, output;\n\n        if (!this.isValid()) {\n            return NaN;\n        }\n\n        that = cloneWithOffset(input, this);\n\n        if (!that.isValid()) {\n            return NaN;\n        }\n\n        zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;\n\n        units = normalizeUnits(units);\n\n        switch (units) {\n            case 'year':\n                output = monthDiff(this, that) / 12;\n                break;\n            case 'month':\n                output = monthDiff(this, that);\n                break;\n            case 'quarter':\n                output = monthDiff(this, that) / 3;\n                break;\n            case 'second':\n                output = (this - that) / 1e3;\n                break; // 1000\n            case 'minute':\n                output = (this - that) / 6e4;\n                break; // 1000 * 60\n            case 'hour':\n                output = (this - that) / 36e5;\n                break; // 1000 * 60 * 60\n            case 'day':\n                output = (this - that - zoneDelta) / 864e5;\n                break; // 1000 * 60 * 60 * 24, negate dst\n            case 'week':\n                output = (this - that - zoneDelta) / 6048e5;\n                break; // 1000 * 60 * 60 * 24 * 7, negate dst\n            default:\n                output = this - that;\n        }\n\n        return asFloat ? output : absFloor(output);\n    }\n\n    function monthDiff(a, b) {\n        if (a.date() < b.date()) {\n            // end-of-month calculations work correct when the start month has more\n            // days than the end month.\n            return -monthDiff(b, a);\n        }\n        // difference in months\n        var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()),\n            // b is in (anchor - 1 month, anchor + 1 month)\n            anchor = a.clone().add(wholeMonthDiff, 'months'),\n            anchor2,\n            adjust;\n\n        if (b - anchor < 0) {\n            anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');\n            // linear across the month\n            adjust = (b - anchor) / (anchor - anchor2);\n        } else {\n            anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');\n            // linear across the month\n            adjust = (b - anchor) / (anchor2 - anchor);\n        }\n\n        //check for negative zero, return zero if negative zero\n        return -(wholeMonthDiff + adjust) || 0;\n    }\n\n    hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';\n    hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';\n\n    function toString() {\n        return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');\n    }\n\n    function toISOString(keepOffset) {\n        if (!this.isValid()) {\n            return null;\n        }\n        var utc = keepOffset !== true,\n            m = utc ? this.clone().utc() : this;\n        if (m.year() < 0 || m.year() > 9999) {\n            return formatMoment(\n                m,\n                utc\n                    ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'\n                    : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'\n            );\n        }\n        if (isFunction(Date.prototype.toISOString)) {\n            // native implementation is ~50x faster, use it when we can\n            if (utc) {\n                return this.toDate().toISOString();\n            } else {\n                return new Date(this.valueOf() + this.utcOffset() * 60 * 1000)\n                    .toISOString()\n                    .replace('Z', formatMoment(m, 'Z'));\n            }\n        }\n        return formatMoment(\n            m,\n            utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'\n        );\n    }\n\n    /**\n     * Return a human readable representation of a moment that can\n     * also be evaluated to get a new moment which is the same\n     *\n     * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects\n     */\n    function inspect() {\n        if (!this.isValid()) {\n            return 'moment.invalid(/* ' + this._i + ' */)';\n        }\n        var func = 'moment',\n            zone = '',\n            prefix,\n            year,\n            datetime,\n            suffix;\n        if (!this.isLocal()) {\n            func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';\n            zone = 'Z';\n        }\n        prefix = '[' + func + '(\"]';\n        year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY';\n        datetime = '-MM-DD[T]HH:mm:ss.SSS';\n        suffix = zone + '[\")]';\n\n        return this.format(prefix + year + datetime + suffix);\n    }\n\n    function format(inputString) {\n        if (!inputString) {\n            inputString = this.isUtc()\n                ? hooks.defaultFormatUtc\n                : hooks.defaultFormat;\n        }\n        var output = formatMoment(this, inputString);\n        return this.localeData().postformat(output);\n    }\n\n    function from(time, withoutSuffix) {\n        if (\n            this.isValid() &&\n            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())\n        ) {\n            return createDuration({ to: this, from: time })\n                .locale(this.locale())\n                .humanize(!withoutSuffix);\n        } else {\n            return this.localeData().invalidDate();\n        }\n    }\n\n    function fromNow(withoutSuffix) {\n        return this.from(createLocal(), withoutSuffix);\n    }\n\n    function to(time, withoutSuffix) {\n        if (\n            this.isValid() &&\n            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())\n        ) {\n            return createDuration({ from: this, to: time })\n                .locale(this.locale())\n                .humanize(!withoutSuffix);\n        } else {\n            return this.localeData().invalidDate();\n        }\n    }\n\n    function toNow(withoutSuffix) {\n        return this.to(createLocal(), withoutSuffix);\n    }\n\n    // If passed a locale key, it will set the locale for this\n    // instance.  Otherwise, it will return the locale configuration\n    // variables for this instance.\n    function locale(key) {\n        var newLocaleData;\n\n        if (key === undefined) {\n            return this._locale._abbr;\n        } else {\n            newLocaleData = getLocale(key);\n            if (newLocaleData != null) {\n                this._locale = newLocaleData;\n            }\n            return this;\n        }\n    }\n\n    var lang = deprecate(\n        'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',\n        function (key) {\n            if (key === undefined) {\n                return this.localeData();\n            } else {\n                return this.locale(key);\n            }\n        }\n    );\n\n    function localeData() {\n        return this._locale;\n    }\n\n    var MS_PER_SECOND = 1000,\n        MS_PER_MINUTE = 60 * MS_PER_SECOND,\n        MS_PER_HOUR = 60 * MS_PER_MINUTE,\n        MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR;\n\n    // actual modulo - handles negative numbers (for dates before 1970):\n    function mod$1(dividend, divisor) {\n        return ((dividend % divisor) + divisor) % divisor;\n    }\n\n    function localStartOfDate(y, m, d) {\n        // the date constructor remaps years 0-99 to 1900-1999\n        if (y < 100 && y >= 0) {\n            // preserve leap years using a full 400 year cycle, then reset\n            return new Date(y + 400, m, d) - MS_PER_400_YEARS;\n        } else {\n            return new Date(y, m, d).valueOf();\n        }\n    }\n\n    function utcStartOfDate(y, m, d) {\n        // Date.UTC remaps years 0-99 to 1900-1999\n        if (y < 100 && y >= 0) {\n            // preserve leap years using a full 400 year cycle, then reset\n            return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS;\n        } else {\n            return Date.UTC(y, m, d);\n        }\n    }\n\n    function startOf(units) {\n        var time, startOfDate;\n        units = normalizeUnits(units);\n        if (units === undefined || units === 'millisecond' || !this.isValid()) {\n            return this;\n        }\n\n        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;\n\n        switch (units) {\n            case 'year':\n                time = startOfDate(this.year(), 0, 1);\n                break;\n            case 'quarter':\n                time = startOfDate(\n                    this.year(),\n                    this.month() - (this.month() % 3),\n                    1\n                );\n                break;\n            case 'month':\n                time = startOfDate(this.year(), this.month(), 1);\n                break;\n            case 'week':\n                time = startOfDate(\n                    this.year(),\n                    this.month(),\n                    this.date() - this.weekday()\n                );\n                break;\n            case 'isoWeek':\n                time = startOfDate(\n                    this.year(),\n                    this.month(),\n                    this.date() - (this.isoWeekday() - 1)\n                );\n                break;\n            case 'day':\n            case 'date':\n                time = startOfDate(this.year(), this.month(), this.date());\n                break;\n            case 'hour':\n                time = this._d.valueOf();\n                time -= mod$1(\n                    time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),\n                    MS_PER_HOUR\n                );\n                break;\n            case 'minute':\n                time = this._d.valueOf();\n                time -= mod$1(time, MS_PER_MINUTE);\n                break;\n            case 'second':\n                time = this._d.valueOf();\n                time -= mod$1(time, MS_PER_SECOND);\n                break;\n        }\n\n        this._d.setTime(time);\n        hooks.updateOffset(this, true);\n        return this;\n    }\n\n    function endOf(units) {\n        var time, startOfDate;\n        units = normalizeUnits(units);\n        if (units === undefined || units === 'millisecond' || !this.isValid()) {\n            return this;\n        }\n\n        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;\n\n        switch (units) {\n            case 'year':\n                time = startOfDate(this.year() + 1, 0, 1) - 1;\n                break;\n            case 'quarter':\n                time =\n                    startOfDate(\n                        this.year(),\n                        this.month() - (this.month() % 3) + 3,\n                        1\n                    ) - 1;\n                break;\n            case 'month':\n                time = startOfDate(this.year(), this.month() + 1, 1) - 1;\n                break;\n            case 'week':\n                time =\n                    startOfDate(\n                        this.year(),\n                        this.month(),\n                        this.date() - this.weekday() + 7\n                    ) - 1;\n                break;\n            case 'isoWeek':\n                time =\n                    startOfDate(\n                        this.year(),\n                        this.month(),\n                        this.date() - (this.isoWeekday() - 1) + 7\n                    ) - 1;\n                break;\n            case 'day':\n            case 'date':\n                time = startOfDate(this.year(), this.month(), this.date() + 1) - 1;\n                break;\n            case 'hour':\n                time = this._d.valueOf();\n                time +=\n                    MS_PER_HOUR -\n                    mod$1(\n                        time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),\n                        MS_PER_HOUR\n                    ) -\n                    1;\n                break;\n            case 'minute':\n                time = this._d.valueOf();\n                time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1;\n                break;\n            case 'second':\n                time = this._d.valueOf();\n                time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1;\n                break;\n        }\n\n        this._d.setTime(time);\n        hooks.updateOffset(this, true);\n        return this;\n    }\n\n    function valueOf() {\n        return this._d.valueOf() - (this._offset || 0) * 60000;\n    }\n\n    function unix() {\n        return Math.floor(this.valueOf() / 1000);\n    }\n\n    function toDate() {\n        return new Date(this.valueOf());\n    }\n\n    function toArray() {\n        var m = this;\n        return [\n            m.year(),\n            m.month(),\n            m.date(),\n            m.hour(),\n            m.minute(),\n            m.second(),\n            m.millisecond(),\n        ];\n    }\n\n    function toObject() {\n        var m = this;\n        return {\n            years: m.year(),\n            months: m.month(),\n            date: m.date(),\n            hours: m.hours(),\n            minutes: m.minutes(),\n            seconds: m.seconds(),\n            milliseconds: m.milliseconds(),\n        };\n    }\n\n    function toJSON() {\n        // new Date(NaN).toJSON() === null\n        return this.isValid() ? this.toISOString() : null;\n    }\n\n    function isValid$2() {\n        return isValid(this);\n    }\n\n    function parsingFlags() {\n        return extend({}, getParsingFlags(this));\n    }\n\n    function invalidAt() {\n        return getParsingFlags(this).overflow;\n    }\n\n    function creationData() {\n        return {\n            input: this._i,\n            format: this._f,\n            locale: this._locale,\n            isUTC: this._isUTC,\n            strict: this._strict,\n        };\n    }\n\n    addFormatToken('N', 0, 0, 'eraAbbr');\n    addFormatToken('NN', 0, 0, 'eraAbbr');\n    addFormatToken('NNN', 0, 0, 'eraAbbr');\n    addFormatToken('NNNN', 0, 0, 'eraName');\n    addFormatToken('NNNNN', 0, 0, 'eraNarrow');\n\n    addFormatToken('y', ['y', 1], 'yo', 'eraYear');\n    addFormatToken('y', ['yy', 2], 0, 'eraYear');\n    addFormatToken('y', ['yyy', 3], 0, 'eraYear');\n    addFormatToken('y', ['yyyy', 4], 0, 'eraYear');\n\n    addRegexToken('N', matchEraAbbr);\n    addRegexToken('NN', matchEraAbbr);\n    addRegexToken('NNN', matchEraAbbr);\n    addRegexToken('NNNN', matchEraName);\n    addRegexToken('NNNNN', matchEraNarrow);\n\n    addParseToken(['N', 'NN', 'NNN', 'NNNN', 'NNNNN'], function (\n        input,\n        array,\n        config,\n        token\n    ) {\n        var era = config._locale.erasParse(input, token, config._strict);\n        if (era) {\n            getParsingFlags(config).era = era;\n        } else {\n            getParsingFlags(config).invalidEra = input;\n        }\n    });\n\n    addRegexToken('y', matchUnsigned);\n    addRegexToken('yy', matchUnsigned);\n    addRegexToken('yyy', matchUnsigned);\n    addRegexToken('yyyy', matchUnsigned);\n    addRegexToken('yo', matchEraYearOrdinal);\n\n    addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR);\n    addParseToken(['yo'], function (input, array, config, token) {\n        var match;\n        if (config._locale._eraYearOrdinalRegex) {\n            match = input.match(config._locale._eraYearOrdinalRegex);\n        }\n\n        if (config._locale.eraYearOrdinalParse) {\n            array[YEAR] = config._locale.eraYearOrdinalParse(input, match);\n        } else {\n            array[YEAR] = parseInt(input, 10);\n        }\n    });\n\n    function localeEras(m, format) {\n        var i,\n            l,\n            date,\n            eras = this._eras || getLocale('en')._eras;\n        for (i = 0, l = eras.length; i < l; ++i) {\n            switch (typeof eras[i].since) {\n                case 'string':\n                    // truncate time\n                    date = hooks(eras[i].since).startOf('day');\n                    eras[i].since = date.valueOf();\n                    break;\n            }\n\n            switch (typeof eras[i].until) {\n                case 'undefined':\n                    eras[i].until = +Infinity;\n                    break;\n                case 'string':\n                    // truncate time\n                    date = hooks(eras[i].until).startOf('day').valueOf();\n                    eras[i].until = date.valueOf();\n                    break;\n            }\n        }\n        return eras;\n    }\n\n    function localeErasParse(eraName, format, strict) {\n        var i,\n            l,\n            eras = this.eras(),\n            name,\n            abbr,\n            narrow;\n        eraName = eraName.toUpperCase();\n\n        for (i = 0, l = eras.length; i < l; ++i) {\n            name = eras[i].name.toUpperCase();\n            abbr = eras[i].abbr.toUpperCase();\n            narrow = eras[i].narrow.toUpperCase();\n\n            if (strict) {\n                switch (format) {\n                    case 'N':\n                    case 'NN':\n                    case 'NNN':\n                        if (abbr === eraName) {\n                            return eras[i];\n                        }\n                        break;\n\n                    case 'NNNN':\n                        if (name === eraName) {\n                            return eras[i];\n                        }\n                        break;\n\n                    case 'NNNNN':\n                        if (narrow === eraName) {\n                            return eras[i];\n                        }\n                        break;\n                }\n            } else if ([name, abbr, narrow].indexOf(eraName) >= 0) {\n                return eras[i];\n            }\n        }\n    }\n\n    function localeErasConvertYear(era, year) {\n        var dir = era.since <= era.until ? +1 : -1;\n        if (year === undefined) {\n            return hooks(era.since).year();\n        } else {\n            return hooks(era.since).year() + (year - era.offset) * dir;\n        }\n    }\n\n    function getEraName() {\n        var i,\n            l,\n            val,\n            eras = this.localeData().eras();\n        for (i = 0, l = eras.length; i < l; ++i) {\n            // truncate time\n            val = this.clone().startOf('day').valueOf();\n\n            if (eras[i].since <= val && val <= eras[i].until) {\n                return eras[i].name;\n            }\n            if (eras[i].until <= val && val <= eras[i].since) {\n                return eras[i].name;\n            }\n        }\n\n        return '';\n    }\n\n    function getEraNarrow() {\n        var i,\n            l,\n            val,\n            eras = this.localeData().eras();\n        for (i = 0, l = eras.length; i < l; ++i) {\n            // truncate time\n            val = this.clone().startOf('day').valueOf();\n\n            if (eras[i].since <= val && val <= eras[i].until) {\n                return eras[i].narrow;\n            }\n            if (eras[i].until <= val && val <= eras[i].since) {\n                return eras[i].narrow;\n            }\n        }\n\n        return '';\n    }\n\n    function getEraAbbr() {\n        var i,\n            l,\n            val,\n            eras = this.localeData().eras();\n        for (i = 0, l = eras.length; i < l; ++i) {\n            // truncate time\n            val = this.clone().startOf('day').valueOf();\n\n            if (eras[i].since <= val && val <= eras[i].until) {\n                return eras[i].abbr;\n            }\n            if (eras[i].until <= val && val <= eras[i].since) {\n                return eras[i].abbr;\n            }\n        }\n\n        return '';\n    }\n\n    function getEraYear() {\n        var i,\n            l,\n            dir,\n            val,\n            eras = this.localeData().eras();\n        for (i = 0, l = eras.length; i < l; ++i) {\n            dir = eras[i].since <= eras[i].until ? +1 : -1;\n\n            // truncate time\n            val = this.clone().startOf('day').valueOf();\n\n            if (\n                (eras[i].since <= val && val <= eras[i].until) ||\n                (eras[i].until <= val && val <= eras[i].since)\n            ) {\n                return (\n                    (this.year() - hooks(eras[i].since).year()) * dir +\n                    eras[i].offset\n                );\n            }\n        }\n\n        return this.year();\n    }\n\n    function erasNameRegex(isStrict) {\n        if (!hasOwnProp(this, '_erasNameRegex')) {\n            computeErasParse.call(this);\n        }\n        return isStrict ? this._erasNameRegex : this._erasRegex;\n    }\n\n    function erasAbbrRegex(isStrict) {\n        if (!hasOwnProp(this, '_erasAbbrRegex')) {\n            computeErasParse.call(this);\n        }\n        return isStrict ? this._erasAbbrRegex : this._erasRegex;\n    }\n\n    function erasNarrowRegex(isStrict) {\n        if (!hasOwnProp(this, '_erasNarrowRegex')) {\n            computeErasParse.call(this);\n        }\n        return isStrict ? this._erasNarrowRegex : this._erasRegex;\n    }\n\n    function matchEraAbbr(isStrict, locale) {\n        return locale.erasAbbrRegex(isStrict);\n    }\n\n    function matchEraName(isStrict, locale) {\n        return locale.erasNameRegex(isStrict);\n    }\n\n    function matchEraNarrow(isStrict, locale) {\n        return locale.erasNarrowRegex(isStrict);\n    }\n\n    function matchEraYearOrdinal(isStrict, locale) {\n        return locale._eraYearOrdinalRegex || matchUnsigned;\n    }\n\n    function computeErasParse() {\n        var abbrPieces = [],\n            namePieces = [],\n            narrowPieces = [],\n            mixedPieces = [],\n            i,\n            l,\n            eras = this.eras();\n\n        for (i = 0, l = eras.length; i < l; ++i) {\n            namePieces.push(regexEscape(eras[i].name));\n            abbrPieces.push(regexEscape(eras[i].abbr));\n            narrowPieces.push(regexEscape(eras[i].narrow));\n\n            mixedPieces.push(regexEscape(eras[i].name));\n            mixedPieces.push(regexEscape(eras[i].abbr));\n            mixedPieces.push(regexEscape(eras[i].narrow));\n        }\n\n        this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');\n        this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i');\n        this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i');\n        this._erasNarrowRegex = new RegExp(\n            '^(' + narrowPieces.join('|') + ')',\n            'i'\n        );\n    }\n\n    // FORMATTING\n\n    addFormatToken(0, ['gg', 2], 0, function () {\n        return this.weekYear() % 100;\n    });\n\n    addFormatToken(0, ['GG', 2], 0, function () {\n        return this.isoWeekYear() % 100;\n    });\n\n    function addWeekYearFormatToken(token, getter) {\n        addFormatToken(0, [token, token.length], 0, getter);\n    }\n\n    addWeekYearFormatToken('gggg', 'weekYear');\n    addWeekYearFormatToken('ggggg', 'weekYear');\n    addWeekYearFormatToken('GGGG', 'isoWeekYear');\n    addWeekYearFormatToken('GGGGG', 'isoWeekYear');\n\n    // ALIASES\n\n    addUnitAlias('weekYear', 'gg');\n    addUnitAlias('isoWeekYear', 'GG');\n\n    // PRIORITY\n\n    addUnitPriority('weekYear', 1);\n    addUnitPriority('isoWeekYear', 1);\n\n    // PARSING\n\n    addRegexToken('G', matchSigned);\n    addRegexToken('g', matchSigned);\n    addRegexToken('GG', match1to2, match2);\n    addRegexToken('gg', match1to2, match2);\n    addRegexToken('GGGG', match1to4, match4);\n    addRegexToken('gggg', match1to4, match4);\n    addRegexToken('GGGGG', match1to6, match6);\n    addRegexToken('ggggg', match1to6, match6);\n\n    addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (\n        input,\n        week,\n        config,\n        token\n    ) {\n        week[token.substr(0, 2)] = toInt(input);\n    });\n\n    addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {\n        week[token] = hooks.parseTwoDigitYear(input);\n    });\n\n    // MOMENTS\n\n    function getSetWeekYear(input) {\n        return getSetWeekYearHelper.call(\n            this,\n            input,\n            this.week(),\n            this.weekday(),\n            this.localeData()._week.dow,\n            this.localeData()._week.doy\n        );\n    }\n\n    function getSetISOWeekYear(input) {\n        return getSetWeekYearHelper.call(\n            this,\n            input,\n            this.isoWeek(),\n            this.isoWeekday(),\n            1,\n            4\n        );\n    }\n\n    function getISOWeeksInYear() {\n        return weeksInYear(this.year(), 1, 4);\n    }\n\n    function getISOWeeksInISOWeekYear() {\n        return weeksInYear(this.isoWeekYear(), 1, 4);\n    }\n\n    function getWeeksInYear() {\n        var weekInfo = this.localeData()._week;\n        return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);\n    }\n\n    function getWeeksInWeekYear() {\n        var weekInfo = this.localeData()._week;\n        return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy);\n    }\n\n    function getSetWeekYearHelper(input, week, weekday, dow, doy) {\n        var weeksTarget;\n        if (input == null) {\n            return weekOfYear(this, dow, doy).year;\n        } else {\n            weeksTarget = weeksInYear(input, dow, doy);\n            if (week > weeksTarget) {\n                week = weeksTarget;\n            }\n            return setWeekAll.call(this, input, week, weekday, dow, doy);\n        }\n    }\n\n    function setWeekAll(weekYear, week, weekday, dow, doy) {\n        var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),\n            date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);\n\n        this.year(date.getUTCFullYear());\n        this.month(date.getUTCMonth());\n        this.date(date.getUTCDate());\n        return this;\n    }\n\n    // FORMATTING\n\n    addFormatToken('Q', 0, 'Qo', 'quarter');\n\n    // ALIASES\n\n    addUnitAlias('quarter', 'Q');\n\n    // PRIORITY\n\n    addUnitPriority('quarter', 7);\n\n    // PARSING\n\n    addRegexToken('Q', match1);\n    addParseToken('Q', function (input, array) {\n        array[MONTH] = (toInt(input) - 1) * 3;\n    });\n\n    // MOMENTS\n\n    function getSetQuarter(input) {\n        return input == null\n            ? Math.ceil((this.month() + 1) / 3)\n            : this.month((input - 1) * 3 + (this.month() % 3));\n    }\n\n    // FORMATTING\n\n    addFormatToken('D', ['DD', 2], 'Do', 'date');\n\n    // ALIASES\n\n    addUnitAlias('date', 'D');\n\n    // PRIORITY\n    addUnitPriority('date', 9);\n\n    // PARSING\n\n    addRegexToken('D', match1to2);\n    addRegexToken('DD', match1to2, match2);\n    addRegexToken('Do', function (isStrict, locale) {\n        // TODO: Remove \"ordinalParse\" fallback in next major release.\n        return isStrict\n            ? locale._dayOfMonthOrdinalParse || locale._ordinalParse\n            : locale._dayOfMonthOrdinalParseLenient;\n    });\n\n    addParseToken(['D', 'DD'], DATE);\n    addParseToken('Do', function (input, array) {\n        array[DATE] = toInt(input.match(match1to2)[0]);\n    });\n\n    // MOMENTS\n\n    var getSetDayOfMonth = makeGetSet('Date', true);\n\n    // FORMATTING\n\n    addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');\n\n    // ALIASES\n\n    addUnitAlias('dayOfYear', 'DDD');\n\n    // PRIORITY\n    addUnitPriority('dayOfYear', 4);\n\n    // PARSING\n\n    addRegexToken('DDD', match1to3);\n    addRegexToken('DDDD', match3);\n    addParseToken(['DDD', 'DDDD'], function (input, array, config) {\n        config._dayOfYear = toInt(input);\n    });\n\n    // HELPERS\n\n    // MOMENTS\n\n    function getSetDayOfYear(input) {\n        var dayOfYear =\n            Math.round(\n                (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5\n            ) + 1;\n        return input == null ? dayOfYear : this.add(input - dayOfYear, 'd');\n    }\n\n    // FORMATTING\n\n    addFormatToken('m', ['mm', 2], 0, 'minute');\n\n    // ALIASES\n\n    addUnitAlias('minute', 'm');\n\n    // PRIORITY\n\n    addUnitPriority('minute', 14);\n\n    // PARSING\n\n    addRegexToken('m', match1to2);\n    addRegexToken('mm', match1to2, match2);\n    addParseToken(['m', 'mm'], MINUTE);\n\n    // MOMENTS\n\n    var getSetMinute = makeGetSet('Minutes', false);\n\n    // FORMATTING\n\n    addFormatToken('s', ['ss', 2], 0, 'second');\n\n    // ALIASES\n\n    addUnitAlias('second', 's');\n\n    // PRIORITY\n\n    addUnitPriority('second', 15);\n\n    // PARSING\n\n    addRegexToken('s', match1to2);\n    addRegexToken('ss', match1to2, match2);\n    addParseToken(['s', 'ss'], SECOND);\n\n    // MOMENTS\n\n    var getSetSecond = makeGetSet('Seconds', false);\n\n    // FORMATTING\n\n    addFormatToken('S', 0, 0, function () {\n        return ~~(this.millisecond() / 100);\n    });\n\n    addFormatToken(0, ['SS', 2], 0, function () {\n        return ~~(this.millisecond() / 10);\n    });\n\n    addFormatToken(0, ['SSS', 3], 0, 'millisecond');\n    addFormatToken(0, ['SSSS', 4], 0, function () {\n        return this.millisecond() * 10;\n    });\n    addFormatToken(0, ['SSSSS', 5], 0, function () {\n        return this.millisecond() * 100;\n    });\n    addFormatToken(0, ['SSSSSS', 6], 0, function () {\n        return this.millisecond() * 1000;\n    });\n    addFormatToken(0, ['SSSSSSS', 7], 0, function () {\n        return this.millisecond() * 10000;\n    });\n    addFormatToken(0, ['SSSSSSSS', 8], 0, function () {\n        return this.millisecond() * 100000;\n    });\n    addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {\n        return this.millisecond() * 1000000;\n    });\n\n    // ALIASES\n\n    addUnitAlias('millisecond', 'ms');\n\n    // PRIORITY\n\n    addUnitPriority('millisecond', 16);\n\n    // PARSING\n\n    addRegexToken('S', match1to3, match1);\n    addRegexToken('SS', match1to3, match2);\n    addRegexToken('SSS', match1to3, match3);\n\n    var token, getSetMillisecond;\n    for (token = 'SSSS'; token.length <= 9; token += 'S') {\n        addRegexToken(token, matchUnsigned);\n    }\n\n    function parseMs(input, array) {\n        array[MILLISECOND] = toInt(('0.' + input) * 1000);\n    }\n\n    for (token = 'S'; token.length <= 9; token += 'S') {\n        addParseToken(token, parseMs);\n    }\n\n    getSetMillisecond = makeGetSet('Milliseconds', false);\n\n    // FORMATTING\n\n    addFormatToken('z', 0, 0, 'zoneAbbr');\n    addFormatToken('zz', 0, 0, 'zoneName');\n\n    // MOMENTS\n\n    function getZoneAbbr() {\n        return this._isUTC ? 'UTC' : '';\n    }\n\n    function getZoneName() {\n        return this._isUTC ? 'Coordinated Universal Time' : '';\n    }\n\n    var proto = Moment.prototype;\n\n    proto.add = add;\n    proto.calendar = calendar$1;\n    proto.clone = clone;\n    proto.diff = diff;\n    proto.endOf = endOf;\n    proto.format = format;\n    proto.from = from;\n    proto.fromNow = fromNow;\n    proto.to = to;\n    proto.toNow = toNow;\n    proto.get = stringGet;\n    proto.invalidAt = invalidAt;\n    proto.isAfter = isAfter;\n    proto.isBefore = isBefore;\n    proto.isBetween = isBetween;\n    proto.isSame = isSame;\n    proto.isSameOrAfter = isSameOrAfter;\n    proto.isSameOrBefore = isSameOrBefore;\n    proto.isValid = isValid$2;\n    proto.lang = lang;\n    proto.locale = locale;\n    proto.localeData = localeData;\n    proto.max = prototypeMax;\n    proto.min = prototypeMin;\n    proto.parsingFlags = parsingFlags;\n    proto.set = stringSet;\n    proto.startOf = startOf;\n    proto.subtract = subtract;\n    proto.toArray = toArray;\n    proto.toObject = toObject;\n    proto.toDate = toDate;\n    proto.toISOString = toISOString;\n    proto.inspect = inspect;\n    if (typeof Symbol !== 'undefined' && Symbol.for != null) {\n        proto[Symbol.for('nodejs.util.inspect.custom')] = function () {\n            return 'Moment<' + this.format() + '>';\n        };\n    }\n    proto.toJSON = toJSON;\n    proto.toString = toString;\n    proto.unix = unix;\n    proto.valueOf = valueOf;\n    proto.creationData = creationData;\n    proto.eraName = getEraName;\n    proto.eraNarrow = getEraNarrow;\n    proto.eraAbbr = getEraAbbr;\n    proto.eraYear = getEraYear;\n    proto.year = getSetYear;\n    proto.isLeapYear = getIsLeapYear;\n    proto.weekYear = getSetWeekYear;\n    proto.isoWeekYear = getSetISOWeekYear;\n    proto.quarter = proto.quarters = getSetQuarter;\n    proto.month = getSetMonth;\n    proto.daysInMonth = getDaysInMonth;\n    proto.week = proto.weeks = getSetWeek;\n    proto.isoWeek = proto.isoWeeks = getSetISOWeek;\n    proto.weeksInYear = getWeeksInYear;\n    proto.weeksInWeekYear = getWeeksInWeekYear;\n    proto.isoWeeksInYear = getISOWeeksInYear;\n    proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear;\n    proto.date = getSetDayOfMonth;\n    proto.day = proto.days = getSetDayOfWeek;\n    proto.weekday = getSetLocaleDayOfWeek;\n    proto.isoWeekday = getSetISODayOfWeek;\n    proto.dayOfYear = getSetDayOfYear;\n    proto.hour = proto.hours = getSetHour;\n    proto.minute = proto.minutes = getSetMinute;\n    proto.second = proto.seconds = getSetSecond;\n    proto.millisecond = proto.milliseconds = getSetMillisecond;\n    proto.utcOffset = getSetOffset;\n    proto.utc = setOffsetToUTC;\n    proto.local = setOffsetToLocal;\n    proto.parseZone = setOffsetToParsedOffset;\n    proto.hasAlignedHourOffset = hasAlignedHourOffset;\n    proto.isDST = isDaylightSavingTime;\n    proto.isLocal = isLocal;\n    proto.isUtcOffset = isUtcOffset;\n    proto.isUtc = isUtc;\n    proto.isUTC = isUtc;\n    proto.zoneAbbr = getZoneAbbr;\n    proto.zoneName = getZoneName;\n    proto.dates = deprecate(\n        'dates accessor is deprecated. Use date instead.',\n        getSetDayOfMonth\n    );\n    proto.months = deprecate(\n        'months accessor is deprecated. Use month instead',\n        getSetMonth\n    );\n    proto.years = deprecate(\n        'years accessor is deprecated. Use year instead',\n        getSetYear\n    );\n    proto.zone = deprecate(\n        'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/',\n        getSetZone\n    );\n    proto.isDSTShifted = deprecate(\n        'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information',\n        isDaylightSavingTimeShifted\n    );\n\n    function createUnix(input) {\n        return createLocal(input * 1000);\n    }\n\n    function createInZone() {\n        return createLocal.apply(null, arguments).parseZone();\n    }\n\n    function preParsePostFormat(string) {\n        return string;\n    }\n\n    var proto$1 = Locale.prototype;\n\n    proto$1.calendar = calendar;\n    proto$1.longDateFormat = longDateFormat;\n    proto$1.invalidDate = invalidDate;\n    proto$1.ordinal = ordinal;\n    proto$1.preparse = preParsePostFormat;\n    proto$1.postformat = preParsePostFormat;\n    proto$1.relativeTime = relativeTime;\n    proto$1.pastFuture = pastFuture;\n    proto$1.set = set;\n    proto$1.eras = localeEras;\n    proto$1.erasParse = localeErasParse;\n    proto$1.erasConvertYear = localeErasConvertYear;\n    proto$1.erasAbbrRegex = erasAbbrRegex;\n    proto$1.erasNameRegex = erasNameRegex;\n    proto$1.erasNarrowRegex = erasNarrowRegex;\n\n    proto$1.months = localeMonths;\n    proto$1.monthsShort = localeMonthsShort;\n    proto$1.monthsParse = localeMonthsParse;\n    proto$1.monthsRegex = monthsRegex;\n    proto$1.monthsShortRegex = monthsShortRegex;\n    proto$1.week = localeWeek;\n    proto$1.firstDayOfYear = localeFirstDayOfYear;\n    proto$1.firstDayOfWeek = localeFirstDayOfWeek;\n\n    proto$1.weekdays = localeWeekdays;\n    proto$1.weekdaysMin = localeWeekdaysMin;\n    proto$1.weekdaysShort = localeWeekdaysShort;\n    proto$1.weekdaysParse = localeWeekdaysParse;\n\n    proto$1.weekdaysRegex = weekdaysRegex;\n    proto$1.weekdaysShortRegex = weekdaysShortRegex;\n    proto$1.weekdaysMinRegex = weekdaysMinRegex;\n\n    proto$1.isPM = localeIsPM;\n    proto$1.meridiem = localeMeridiem;\n\n    function get$1(format, index, field, setter) {\n        var locale = getLocale(),\n            utc = createUTC().set(setter, index);\n        return locale[field](utc, format);\n    }\n\n    function listMonthsImpl(format, index, field) {\n        if (isNumber(format)) {\n            index = format;\n            format = undefined;\n        }\n\n        format = format || '';\n\n        if (index != null) {\n            return get$1(format, index, field, 'month');\n        }\n\n        var i,\n            out = [];\n        for (i = 0; i < 12; i++) {\n            out[i] = get$1(format, i, field, 'month');\n        }\n        return out;\n    }\n\n    // ()\n    // (5)\n    // (fmt, 5)\n    // (fmt)\n    // (true)\n    // (true, 5)\n    // (true, fmt, 5)\n    // (true, fmt)\n    function listWeekdaysImpl(localeSorted, format, index, field) {\n        if (typeof localeSorted === 'boolean') {\n            if (isNumber(format)) {\n                index = format;\n                format = undefined;\n            }\n\n            format = format || '';\n        } else {\n            format = localeSorted;\n            index = format;\n            localeSorted = false;\n\n            if (isNumber(format)) {\n                index = format;\n                format = undefined;\n            }\n\n            format = format || '';\n        }\n\n        var locale = getLocale(),\n            shift = localeSorted ? locale._week.dow : 0,\n            i,\n            out = [];\n\n        if (index != null) {\n            return get$1(format, (index + shift) % 7, field, 'day');\n        }\n\n        for (i = 0; i < 7; i++) {\n            out[i] = get$1(format, (i + shift) % 7, field, 'day');\n        }\n        return out;\n    }\n\n    function listMonths(format, index) {\n        return listMonthsImpl(format, index, 'months');\n    }\n\n    function listMonthsShort(format, index) {\n        return listMonthsImpl(format, index, 'monthsShort');\n    }\n\n    function listWeekdays(localeSorted, format, index) {\n        return listWeekdaysImpl(localeSorted, format, index, 'weekdays');\n    }\n\n    function listWeekdaysShort(localeSorted, format, index) {\n        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');\n    }\n\n    function listWeekdaysMin(localeSorted, format, index) {\n        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');\n    }\n\n    getSetGlobalLocale('en', {\n        eras: [\n            {\n                since: '0001-01-01',\n                until: +Infinity,\n                offset: 1,\n                name: 'Anno Domini',\n                narrow: 'AD',\n                abbr: 'AD',\n            },\n            {\n                since: '0000-12-31',\n                until: -Infinity,\n                offset: 1,\n                name: 'Before Christ',\n                narrow: 'BC',\n                abbr: 'BC',\n            },\n        ],\n        dayOfMonthOrdinalParse: /\\d{1,2}(th|st|nd|rd)/,\n        ordinal: function (number) {\n            var b = number % 10,\n                output =\n                    toInt((number % 100) / 10) === 1\n                        ? 'th'\n                        : b === 1\n                        ? 'st'\n                        : b === 2\n                        ? 'nd'\n                        : b === 3\n                        ? 'rd'\n                        : 'th';\n            return number + output;\n        },\n    });\n\n    // Side effect imports\n\n    hooks.lang = deprecate(\n        'moment.lang is deprecated. Use moment.locale instead.',\n        getSetGlobalLocale\n    );\n    hooks.langData = deprecate(\n        'moment.langData is deprecated. Use moment.localeData instead.',\n        getLocale\n    );\n\n    var mathAbs = Math.abs;\n\n    function abs() {\n        var data = this._data;\n\n        this._milliseconds = mathAbs(this._milliseconds);\n        this._days = mathAbs(this._days);\n        this._months = mathAbs(this._months);\n\n        data.milliseconds = mathAbs(data.milliseconds);\n        data.seconds = mathAbs(data.seconds);\n        data.minutes = mathAbs(data.minutes);\n        data.hours = mathAbs(data.hours);\n        data.months = mathAbs(data.months);\n        data.years = mathAbs(data.years);\n\n        return this;\n    }\n\n    function addSubtract$1(duration, input, value, direction) {\n        var other = createDuration(input, value);\n\n        duration._milliseconds += direction * other._milliseconds;\n        duration._days += direction * other._days;\n        duration._months += direction * other._months;\n\n        return duration._bubble();\n    }\n\n    // supports only 2.0-style add(1, 's') or add(duration)\n    function add$1(input, value) {\n        return addSubtract$1(this, input, value, 1);\n    }\n\n    // supports only 2.0-style subtract(1, 's') or subtract(duration)\n    function subtract$1(input, value) {\n        return addSubtract$1(this, input, value, -1);\n    }\n\n    function absCeil(number) {\n        if (number < 0) {\n            return Math.floor(number);\n        } else {\n            return Math.ceil(number);\n        }\n    }\n\n    function bubble() {\n        var milliseconds = this._milliseconds,\n            days = this._days,\n            months = this._months,\n            data = this._data,\n            seconds,\n            minutes,\n            hours,\n            years,\n            monthsFromDays;\n\n        // if we have a mix of positive and negative values, bubble down first\n        // check: https://github.com/moment/moment/issues/2166\n        if (\n            !(\n                (milliseconds >= 0 && days >= 0 && months >= 0) ||\n                (milliseconds <= 0 && days <= 0 && months <= 0)\n            )\n        ) {\n            milliseconds += absCeil(monthsToDays(months) + days) * 864e5;\n            days = 0;\n            months = 0;\n        }\n\n        // The following code bubbles up values, see the tests for\n        // examples of what that means.\n        data.milliseconds = milliseconds % 1000;\n\n        seconds = absFloor(milliseconds / 1000);\n        data.seconds = seconds % 60;\n\n        minutes = absFloor(seconds / 60);\n        data.minutes = minutes % 60;\n\n        hours = absFloor(minutes / 60);\n        data.hours = hours % 24;\n\n        days += absFloor(hours / 24);\n\n        // convert days to months\n        monthsFromDays = absFloor(daysToMonths(days));\n        months += monthsFromDays;\n        days -= absCeil(monthsToDays(monthsFromDays));\n\n        // 12 months -> 1 year\n        years = absFloor(months / 12);\n        months %= 12;\n\n        data.days = days;\n        data.months = months;\n        data.years = years;\n\n        return this;\n    }\n\n    function daysToMonths(days) {\n        // 400 years have 146097 days (taking into account leap year rules)\n        // 400 years have 12 months === 4800\n        return (days * 4800) / 146097;\n    }\n\n    function monthsToDays(months) {\n        // the reverse of daysToMonths\n        return (months * 146097) / 4800;\n    }\n\n    function as(units) {\n        if (!this.isValid()) {\n            return NaN;\n        }\n        var days,\n            months,\n            milliseconds = this._milliseconds;\n\n        units = normalizeUnits(units);\n\n        if (units === 'month' || units === 'quarter' || units === 'year') {\n            days = this._days + milliseconds / 864e5;\n            months = this._months + daysToMonths(days);\n            switch (units) {\n                case 'month':\n                    return months;\n                case 'quarter':\n                    return months / 3;\n                case 'year':\n                    return months / 12;\n            }\n        } else {\n            // handle milliseconds separately because of floating point math errors (issue #1867)\n            days = this._days + Math.round(monthsToDays(this._months));\n            switch (units) {\n                case 'week':\n                    return days / 7 + milliseconds / 6048e5;\n                case 'day':\n                    return days + milliseconds / 864e5;\n                case 'hour':\n                    return days * 24 + milliseconds / 36e5;\n                case 'minute':\n                    return days * 1440 + milliseconds / 6e4;\n                case 'second':\n                    return days * 86400 + milliseconds / 1000;\n                // Math.floor prevents floating point math errors here\n                case 'millisecond':\n                    return Math.floor(days * 864e5) + milliseconds;\n                default:\n                    throw new Error('Unknown unit ' + units);\n            }\n        }\n    }\n\n    // TODO: Use this.as('ms')?\n    function valueOf$1() {\n        if (!this.isValid()) {\n            return NaN;\n        }\n        return (\n            this._milliseconds +\n            this._days * 864e5 +\n            (this._months % 12) * 2592e6 +\n            toInt(this._months / 12) * 31536e6\n        );\n    }\n\n    function makeAs(alias) {\n        return function () {\n            return this.as(alias);\n        };\n    }\n\n    var asMilliseconds = makeAs('ms'),\n        asSeconds = makeAs('s'),\n        asMinutes = makeAs('m'),\n        asHours = makeAs('h'),\n        asDays = makeAs('d'),\n        asWeeks = makeAs('w'),\n        asMonths = makeAs('M'),\n        asQuarters = makeAs('Q'),\n        asYears = makeAs('y');\n\n    function clone$1() {\n        return createDuration(this);\n    }\n\n    function get$2(units) {\n        units = normalizeUnits(units);\n        return this.isValid() ? this[units + 's']() : NaN;\n    }\n\n    function makeGetter(name) {\n        return function () {\n            return this.isValid() ? this._data[name] : NaN;\n        };\n    }\n\n    var milliseconds = makeGetter('milliseconds'),\n        seconds = makeGetter('seconds'),\n        minutes = makeGetter('minutes'),\n        hours = makeGetter('hours'),\n        days = makeGetter('days'),\n        months = makeGetter('months'),\n        years = makeGetter('years');\n\n    function weeks() {\n        return absFloor(this.days() / 7);\n    }\n\n    var round = Math.round,\n        thresholds = {\n            ss: 44, // a few seconds to seconds\n            s: 45, // seconds to minute\n            m: 45, // minutes to hour\n            h: 22, // hours to day\n            d: 26, // days to month/week\n            w: null, // weeks to month\n            M: 11, // months to year\n        };\n\n    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize\n    function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {\n        return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);\n    }\n\n    function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) {\n        var duration = createDuration(posNegDuration).abs(),\n            seconds = round(duration.as('s')),\n            minutes = round(duration.as('m')),\n            hours = round(duration.as('h')),\n            days = round(duration.as('d')),\n            months = round(duration.as('M')),\n            weeks = round(duration.as('w')),\n            years = round(duration.as('y')),\n            a =\n                (seconds <= thresholds.ss && ['s', seconds]) ||\n                (seconds < thresholds.s && ['ss', seconds]) ||\n                (minutes <= 1 && ['m']) ||\n                (minutes < thresholds.m && ['mm', minutes]) ||\n                (hours <= 1 && ['h']) ||\n                (hours < thresholds.h && ['hh', hours]) ||\n                (days <= 1 && ['d']) ||\n                (days < thresholds.d && ['dd', days]);\n\n        if (thresholds.w != null) {\n            a =\n                a ||\n                (weeks <= 1 && ['w']) ||\n                (weeks < thresholds.w && ['ww', weeks]);\n        }\n        a = a ||\n            (months <= 1 && ['M']) ||\n            (months < thresholds.M && ['MM', months]) ||\n            (years <= 1 && ['y']) || ['yy', years];\n\n        a[2] = withoutSuffix;\n        a[3] = +posNegDuration > 0;\n        a[4] = locale;\n        return substituteTimeAgo.apply(null, a);\n    }\n\n    // This function allows you to set the rounding function for relative time strings\n    function getSetRelativeTimeRounding(roundingFunction) {\n        if (roundingFunction === undefined) {\n            return round;\n        }\n        if (typeof roundingFunction === 'function') {\n            round = roundingFunction;\n            return true;\n        }\n        return false;\n    }\n\n    // This function allows you to set a threshold for relative time strings\n    function getSetRelativeTimeThreshold(threshold, limit) {\n        if (thresholds[threshold] === undefined) {\n            return false;\n        }\n        if (limit === undefined) {\n            return thresholds[threshold];\n        }\n        thresholds[threshold] = limit;\n        if (threshold === 's') {\n            thresholds.ss = limit - 1;\n        }\n        return true;\n    }\n\n    function humanize(argWithSuffix, argThresholds) {\n        if (!this.isValid()) {\n            return this.localeData().invalidDate();\n        }\n\n        var withSuffix = false,\n            th = thresholds,\n            locale,\n            output;\n\n        if (typeof argWithSuffix === 'object') {\n            argThresholds = argWithSuffix;\n            argWithSuffix = false;\n        }\n        if (typeof argWithSuffix === 'boolean') {\n            withSuffix = argWithSuffix;\n        }\n        if (typeof argThresholds === 'object') {\n            th = Object.assign({}, thresholds, argThresholds);\n            if (argThresholds.s != null && argThresholds.ss == null) {\n                th.ss = argThresholds.s - 1;\n            }\n        }\n\n        locale = this.localeData();\n        output = relativeTime$1(this, !withSuffix, th, locale);\n\n        if (withSuffix) {\n            output = locale.pastFuture(+this, output);\n        }\n\n        return locale.postformat(output);\n    }\n\n    var abs$1 = Math.abs;\n\n    function sign(x) {\n        return (x > 0) - (x < 0) || +x;\n    }\n\n    function toISOString$1() {\n        // for ISO strings we do not use the normal bubbling rules:\n        //  * milliseconds bubble up until they become hours\n        //  * days do not bubble at all\n        //  * months bubble up until they become years\n        // This is because there is no context-free conversion between hours and days\n        // (think of clock changes)\n        // and also not between days and months (28-31 days per month)\n        if (!this.isValid()) {\n            return this.localeData().invalidDate();\n        }\n\n        var seconds = abs$1(this._milliseconds) / 1000,\n            days = abs$1(this._days),\n            months = abs$1(this._months),\n            minutes,\n            hours,\n            years,\n            s,\n            total = this.asSeconds(),\n            totalSign,\n            ymSign,\n            daysSign,\n            hmsSign;\n\n        if (!total) {\n            // this is the same as C#'s (Noda) and python (isodate)...\n            // but not other JS (goog.date)\n            return 'P0D';\n        }\n\n        // 3600 seconds -> 60 minutes -> 1 hour\n        minutes = absFloor(seconds / 60);\n        hours = absFloor(minutes / 60);\n        seconds %= 60;\n        minutes %= 60;\n\n        // 12 months -> 1 year\n        years = absFloor(months / 12);\n        months %= 12;\n\n        // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js\n        s = seconds ? seconds.toFixed(3).replace(/\\.?0+$/, '') : '';\n\n        totalSign = total < 0 ? '-' : '';\n        ymSign = sign(this._months) !== sign(total) ? '-' : '';\n        daysSign = sign(this._days) !== sign(total) ? '-' : '';\n        hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';\n\n        return (\n            totalSign +\n            'P' +\n            (years ? ymSign + years + 'Y' : '') +\n            (months ? ymSign + months + 'M' : '') +\n            (days ? daysSign + days + 'D' : '') +\n            (hours || minutes || seconds ? 'T' : '') +\n            (hours ? hmsSign + hours + 'H' : '') +\n            (minutes ? hmsSign + minutes + 'M' : '') +\n            (seconds ? hmsSign + s + 'S' : '')\n        );\n    }\n\n    var proto$2 = Duration.prototype;\n\n    proto$2.isValid = isValid$1;\n    proto$2.abs = abs;\n    proto$2.add = add$1;\n    proto$2.subtract = subtract$1;\n    proto$2.as = as;\n    proto$2.asMilliseconds = asMilliseconds;\n    proto$2.asSeconds = asSeconds;\n    proto$2.asMinutes = asMinutes;\n    proto$2.asHours = asHours;\n    proto$2.asDays = asDays;\n    proto$2.asWeeks = asWeeks;\n    proto$2.asMonths = asMonths;\n    proto$2.asQuarters = asQuarters;\n    proto$2.asYears = asYears;\n    proto$2.valueOf = valueOf$1;\n    proto$2._bubble = bubble;\n    proto$2.clone = clone$1;\n    proto$2.get = get$2;\n    proto$2.milliseconds = milliseconds;\n    proto$2.seconds = seconds;\n    proto$2.minutes = minutes;\n    proto$2.hours = hours;\n    proto$2.days = days;\n    proto$2.weeks = weeks;\n    proto$2.months = months;\n    proto$2.years = years;\n    proto$2.humanize = humanize;\n    proto$2.toISOString = toISOString$1;\n    proto$2.toString = toISOString$1;\n    proto$2.toJSON = toISOString$1;\n    proto$2.locale = locale;\n    proto$2.localeData = localeData;\n\n    proto$2.toIsoString = deprecate(\n        'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)',\n        toISOString$1\n    );\n    proto$2.lang = lang;\n\n    // FORMATTING\n\n    addFormatToken('X', 0, 0, 'unix');\n    addFormatToken('x', 0, 0, 'valueOf');\n\n    // PARSING\n\n    addRegexToken('x', matchSigned);\n    addRegexToken('X', matchTimestamp);\n    addParseToken('X', function (input, array, config) {\n        config._d = new Date(parseFloat(input) * 1000);\n    });\n    addParseToken('x', function (input, array, config) {\n        config._d = new Date(toInt(input));\n    });\n\n    //! moment.js\n\n    hooks.version = '2.29.1';\n\n    setHookCallback(createLocal);\n\n    hooks.fn = proto;\n    hooks.min = min;\n    hooks.max = max;\n    hooks.now = now;\n    hooks.utc = createUTC;\n    hooks.unix = createUnix;\n    hooks.months = listMonths;\n    hooks.isDate = isDate;\n    hooks.locale = getSetGlobalLocale;\n    hooks.invalid = createInvalid;\n    hooks.duration = createDuration;\n    hooks.isMoment = isMoment;\n    hooks.weekdays = listWeekdays;\n    hooks.parseZone = createInZone;\n    hooks.localeData = getLocale;\n    hooks.isDuration = isDuration;\n    hooks.monthsShort = listMonthsShort;\n    hooks.weekdaysMin = listWeekdaysMin;\n    hooks.defineLocale = defineLocale;\n    hooks.updateLocale = updateLocale;\n    hooks.locales = listLocales;\n    hooks.weekdaysShort = listWeekdaysShort;\n    hooks.normalizeUnits = normalizeUnits;\n    hooks.relativeTimeRounding = getSetRelativeTimeRounding;\n    hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;\n    hooks.calendarFormat = getCalendarFormat;\n    hooks.prototype = proto;\n\n    // currently HTML5 input type only supports 24-hour formats\n    hooks.HTML5_FMT = {\n        DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type=\"datetime-local\" />\n        DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type=\"datetime-local\" step=\"1\" />\n        DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type=\"datetime-local\" step=\"0.001\" />\n        DATE: 'YYYY-MM-DD', // <input type=\"date\" />\n        TIME: 'HH:mm', // <input type=\"time\" />\n        TIME_SECONDS: 'HH:mm:ss', // <input type=\"time\" step=\"1\" />\n        TIME_MS: 'HH:mm:ss.SSS', // <input type=\"time\" step=\"0.001\" />\n        WEEK: 'GGGG-[W]WW', // <input type=\"week\" />\n        MONTH: 'YYYY-MM', // <input type=\"month\" />\n    };\n\n    return hooks;\n\n})));\n"
  },
  {
    "path": "tests/test_code/js/scoping/scoping.js",
    "content": "function scope_confusion() {\n    console.log('scope_confusion');\n}\n\nclass MyClass {\n    scope_confusion() {\n        console.log('scope_confusion');\n    }\n\n    a() {\n        this.scope_confusion();\n    }\n\n    b() {\n        scope_confusion();\n    }\n}\n\n\nlet obj = new myClass();\nobj.a();\nobj.b();\n"
  },
  {
    "path": "tests/test_code/js/simple_a_js/simple_a.js",
    "content": "function func_b() {}\n\nfunction func_a() {\n    func_b();\n}\n"
  },
  {
    "path": "tests/test_code/js/simple_b_js/simple_b.js",
    "content": "\n\nfunction a() {\n    b();\n}\n\n\nfunction b() {\n    a(\"STRC #\");\n}\n\n\nclass C {\n    d(param) {\n        a(\"AnotherSTR\");\n    }\n}\n\nconst c = new C()\nc.d()\n"
  },
  {
    "path": "tests/test_code/js/ternary_new/ternary_new.js",
    "content": "class Abra {\n    init() {\n        return this.init()\n    }\n}\n\nclass Cadabra {\n    init() {\n        return this.init()\n    }\n}\n\nfunction abra_fact() {\n    return Abra;\n}\n\nfunction cadabra_fact() {\n    return Cadabra;\n}\n\n\nclass ClassMap {\n    fact(which) {\n        return which == \"abra\" ? abra_fact : cadabra_fact;\n    }\n}\n\nobj = true ? new ClassMap.fact(\"abra\").init(n) : new ClassMap.fact(\"cadabra\").init(n)\nobj = true ? new ClassMap.fact(\"abra\").fact().init(n) : new ClassMap.fact(\"cadabra\").fact().init(n)\n"
  },
  {
    "path": "tests/test_code/js/two_file_imports/imported.js",
    "content": "class myClass {\n    constructor() {\n        this.doit()\n    }\n\n    get doit() {\n        this.doit2()\n    }\n\n    doit2() {\n        console.log('at the end')\n    }\n}\n\nfunction inner() {\n    console.log(\"inner\")\n}\n\nfunction bye() {\n\n}\n\nmodule.exports = {myClass, inner, 'hi': bye};\n"
  },
  {
    "path": "tests/test_code/js/two_file_imports/importer.js",
    "content": "const {myClass, inner, hi} = require(\"imported\");\n\nfunction outer() {\n    let cls = new myClass();\n    inner();\n    hi();\n}\n\nouter();\n"
  },
  {
    "path": "tests/test_code/js/two_file_simple/file_a.js",
    "content": "function a() {\n    b()\n}\n\n\na();\n"
  },
  {
    "path": "tests/test_code/js/two_file_simple/file_b.js",
    "content": "function b() {\n    console.log(\"here\")\n}\n\n\nfunction c() {}\n"
  },
  {
    "path": "tests/test_code/js/two_file_simple/shouldntberead",
    "content": "function d() {\n    e();\n}\n"
  },
  {
    "path": "tests/test_code/js/weird_assignments/weird_assignments.js",
    "content": "function get_ab() {\n    return {a: 1, b: 2};\n}\n\nlet {a, b} = get_ab()\n"
  },
  {
    "path": "tests/test_code/mjs/two_file_imports_es6/imported_es6.mjs",
    "content": "class myClass {\n    constructor() {\n        this.doit();\n    }\n\n    doit() {\n        this.doit2();\n    }\n\n    doit2() {\n        console.log('at the end')\n    }\n}\n\nfunction inner() {\n    console.log(\"inner\")\n}\n\nexport {myClass, inner};\n"
  },
  {
    "path": "tests/test_code/mjs/two_file_imports_es6/importer_es6.mjs",
    "content": "import {myClass, inner} from \"./imported_es6.mjs\";\n\nfunction outer() {\n    let cls = new myClass();\n    inner();\n}\n\nouter();\n"
  },
  {
    "path": "tests/test_code/php/ambiguous_resolution/ambiguous_resolution.php",
    "content": "<?php\n\nclass Abra {\n    function magic() {\n        echo \"Abra.magic() cant resolve\";\n    }\n    function abra_it() {\n        echo \"Abra.abra_it() will resolve\";\n    }\n}\n\n\nclass Cadabra {\n    function magic() {\n        echo \"Cadabra.magic() cant resolve\";\n\n    }\n    function cadabra_it($a) {\n        echo \"Cadabra.cadabra_it() will resolve\";\n        $a->abra_it();\n        $a->magic();\n    }\n}\n\n\nfunction main($cls) {\n    echo \"main\";\n    $obj = new $cls;\n    $obj->magic();\n    $obj->cadabra_it(new Abra());\n}\n\nmain(Cadabra::class);\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/anon/anonymous_function.php",
    "content": "\n<?php\n\n\n$greet = (static function($name): void\n{\n    printf(\"Hello %s\\r\\n\", $name);\n})('World');\n\n$saybye = (static function($name): void\n{\n    printf(\"Bye %s\\r\\n\", $name);\n});\n\n$saybye('World');\n\nfunction a() {}\na();\n?>\n\n"
  },
  {
    "path": "tests/test_code/php/anon2/anonymous_function2.php",
    "content": "\n<?php\n\nfunction func_a($name) {\n    echo \"Hello \" + $name;\n}\n\n$greet = (static function($name): void\n{\n    func_a($name);\n});\n\n$greet('World');\n\n?>\n\n"
  },
  {
    "path": "tests/test_code/php/bad_php/bad_php_a.php",
    "content": "<?php\na()\ndsl\n?>\n"
  },
  {
    "path": "tests/test_code/php/bad_php/bad_php_b.php",
    "content": "<html>\n<body>Hello World</body>\n</html>\n"
  },
  {
    "path": "tests/test_code/php/branch/branch.php",
    "content": "<?php\nfunction a() {\n\n}\n\nif (true) {\n    a();\n}\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/chains/chains.php",
    "content": "<?php\nfunction a() {\n    echo \"A\";\n    b();\n}\n\n\nfunction b() {\n    echo \"B\";\n    a();\n}\n\n\nclass Cls {\n    function __construct() {\n        echo \"init\";\n        return $this;\n    }\n\n\n    function a() {\n        echo \"a\";\n        return $this;\n    }\n\n    function b() {\n        echo \"b\";\n        return $this;\n    }\n}\n\n\nfunction c() {\n    $obj = new Cls();\n    $obj->a()->b()->a();\n}\n\na();\nb();\nc();\n?>\n"
  },
  {
    "path": "tests/test_code/php/factory/currency.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\n/**\n * Currency Value Object.\n *\n * Holds Currency specific data.\n *\n * @psalm-immutable\n */\nfinal class Currency implements JsonSerializable\n{\n    /**\n     * Currency code.\n     *\n     * @psalm-var non-empty-string\n     */\n    private string $code;\n\n    /** @psalm-param non-empty-string $code */\n    public function __construct(string $code)\n    {\n        /** @psalm-var non-empty-string $this->code */\n        $this->code = strtoupper($code);\n    }\n\n    /**\n     * Returns the currency code.\n     *\n     * @psalm-return non-empty-string\n     */\n    public function getCode(): string\n    {\n        return $this->code;\n    }\n\n    /**\n     * Checks whether this currency is the same as an other.\n     */\n    public function equals(Currency $other): bool\n    {\n        return $this->code === $other->code;\n    }\n\n    public function __toString(): string\n    {\n        return $this->code;\n    }\n\n    /**\n     * {@inheritdoc}\n     *\n     * @return string\n     */\n    public function jsonSerialize()\n    {\n        return $this->code;\n    }\n}\n\n\n/**\n * Implement this to provide a list of currencies.\n */\ninterface Currencies extends IteratorAggregate\n{\n    /**\n     * Checks whether a currency is available in the current context.\n     */\n    public function contains(Currency $currency): bool;\n\n    /**\n     * Returns the subunit for a currency.\n     *\n     * @throws UnknownCurrencyException If currency is not available in the current context.\n     */\n    public function subunitFor(Currency $currency): int;\n\n    /**\n     * @psalm-return Traversable<int|string, Currency>\n     */\n    public function getIterator(): Traversable;\n}\n"
  },
  {
    "path": "tests/test_code/php/factory/factory.php",
    "content": "<?php\n\nrequire 'currency.php';\n\nuse Money\\Currencies;\nuse Money\\Currency;\n\n(static function (): void {\n    $buffer = <<<'PHP'\n<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\nuse InvalidArgumentException;\n\n/**\n * This is a generated file. Do not edit it manually!\n */\ntrait MoneyFactory\n{\n    public static function __callStatic(string $method, array $arguments): Money\n    {\n        return new Money($arguments[0], new Currency($method));\n    }\n}\n\nPHP;\n\n    $methodBuffer = '';\n\n    $currencies = iterator_to_array(new Currencies\\AggregateCurrencies([\n        new Currencies\\ISOCurrencies(),\n        new Currencies\\BitcoinCurrencies(),\n    ]));\n\n    $bitcoin = new Currency();\n\n    usort($currencies, static fn (Currency $a, Currency $b): int => strcmp($a->getCode(), $b->getCode()));\n\n    /** @var Currency[] $currencies */\n    foreach ($currencies as $currency) {\n        $methodBuffer .= sprintf(\" * @method static Money %s(numeric-string|int \\$amount)\\n\", $currency->getCode());\n        echo $currency.contains($bitcoin);\n    }\n\n    $buffer = str_replace('PHPDOC', rtrim($methodBuffer), $buffer);\n\n    file_put_contents('content.php', $buffer);\n})();\n"
  },
  {
    "path": "tests/test_code/php/inheritance/inheritance.php",
    "content": " <?php\n// from https://www.w3schools.com/php/php_oop_inheritance.asp\nclass FakeFruit {\n  public $name;\n  public $color;\n  public function __construct($name, $color) {\n    $this->name = $name;\n    $this->color = $color;\n  }\n  public function __construct() {\n    echo \"__construct\";\n  }\n\n  public function intro() {\n    echo \"The fruit is {$this->name} and the color is {$this->color}.\";\n  }\n}\n\n\nclass Fruit {\n  public $name;\n  public $color;\n  public function __construct($name, $color) {\n    $this->name = $name;\n    $this->color = $color;\n  }\n  public function intro() {\n    echo \"The fruit is {$this->name} and the color is {$this->color}.\";\n  }\n  public function getColor() {\n    return $this->color;\n  }\n}\n\nclass PhantomFruit {\n  public $name;\n  public $color;\n  public function __construct($name, $color) {\n    $this->name = $name;\n    $this->color = $color;\n  }\n  public function __construct() {\n    echo \"__construct\";\n  }\n\n  public function intro() {\n    echo \"The fruit is {$this->name} and the color is {$this->color}.\";\n  }\n}\n\n// Strawberry is inherited from Fruit\nclass Strawberry extends Fruit {\n  public function message() {\n    echo \"Am I a fruit or a berry? \";\n    $this.getColor();\n  }\n}\n$strawberry = new Strawberry(\"Strawberry\", \"red\");\n$strawberry->message();\n$strawberry->intro();\n?>\n"
  },
  {
    "path": "tests/test_code/php/inheritance2/inheritance2.php",
    "content": "<?php\n// from https://www.w3schools.com/php/php_oop_classes_abstract.asp\n\n// Parent class\nabstract class Car {\n  public $name;\n  public function __construct($name) {\n    $this->name = $name;\n  }\n  abstract public function intro() : string;\n}\n\n// Interface\ninterface Noisy {\n  public function makeSound();\n}\n\n// Child classes\nclass Audi extends Car implements Noisy {\n  public function intro() : string {\n    return \"Choose German quality! I'm an $this->name!\";\n  }\n\n  public function makeSound(): string {\n    return \"HONK!\";\n  }\n}\n\nclass Volvo extends Car implements Noisy {\n  public function intro() : string {\n    return \"Proud to be Swedish! I'm a $this->name!\";\n  }\n  public function makeSound(): string {\n    return \"HONK!\";\n  }\n}\n\nclass Citroen extends Car implements Noisy {\n  public function intro() : string {\n    return \"French extravagance! I'm a $this->name!\";\n  }\n}\n\n// Create objects from the child classes\n$audi = new Audi(\"Audi\");\necho $audi->intro();\necho $audi->makeSound();\necho \"<br>\";\n\n$volvo = new Volvo(\"Volvo\");\necho $volvo->intro();\necho \"<br>\";\n\n$citroen = new Citroen(\"Citroen\");\necho $citroen->intro();\n?>\n"
  },
  {
    "path": "tests/test_code/php/instance_methods/instance_methods.php",
    "content": "<?php\n\nfunction main() {\n    echo \"main; This is called from abra.main\";\n}\n\nclass Abra {\n    function main() {\n        echo \"Abra.main\";\n        function nested() {\n            echo \"Abra.nested; This gets placed on Abra\";\n            main(); # calls global main\n        }\n        $this->nested();\n    }\n\n    function main2() {\n        echo \"Abra.main2\";\n        function nested2() {\n            echo \"Abra.nested2\";\n            $this.main();\n        }\n    }\n}\n\necho \"A\";\n$a = new Abra();\necho \"B\";\n$a.main();\necho \"C\";\n$a.nested();\n$a.main2();\n$a.nested2();\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/money/Calculator/BcMathCalculator.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Calculator;\n\nuse InvalidArgumentException as CoreInvalidArgumentException;\nuse Money\\Calculator;\nuse Money\\Exception\\InvalidArgumentException;\nuse Money\\Money;\nuse Money\\Number;\n\nuse function bcadd;\nuse function bccomp;\nuse function bcdiv;\nuse function bcmod;\nuse function bcmul;\nuse function bcsub;\nuse function ltrim;\n\nfinal class BcMathCalculator implements Calculator\n{\n    private const SCALE = 14;\n\n    /** @psalm-pure */\n    public static function compare(string $a, string $b): int\n    {\n        return bccomp($a, $b, self::SCALE);\n    }\n\n    /** @psalm-pure */\n    public static function add(string $amount, string $addend): string\n    {\n        return bcadd($amount, $addend, self::SCALE);\n    }\n\n    /** @psalm-pure */\n    public static function subtract(string $amount, string $subtrahend): string\n    {\n        return bcsub($amount, $subtrahend, self::SCALE);\n    }\n\n    /** @psalm-pure */\n    public static function multiply(string $amount, string $multiplier): string\n    {\n        return bcmul($amount, $multiplier, self::SCALE);\n    }\n\n    /** @psalm-pure */\n    public static function divide(string $amount, string $divisor): string\n    {\n        if (bccomp($divisor, '0', self::SCALE) === 0) {\n            throw InvalidArgumentException::divisionByZero();\n        }\n\n        return bcdiv($amount, $divisor, self::SCALE);\n    }\n\n    /** @psalm-pure */\n    public static function ceil(string $number): string\n    {\n        $number = Number::fromString($number);\n\n        if ($number->isInteger()) {\n            return $number->__toString();\n        }\n\n        if ($number->isNegative()) {\n            return bcadd($number->__toString(), '0', 0);\n        }\n\n        return bcadd($number->__toString(), '1', 0);\n    }\n\n    /** @psalm-pure */\n    public static function floor(string $number): string\n    {\n        $number = Number::fromString($number);\n\n        if ($number->isInteger()) {\n            return $number->__toString();\n        }\n\n        if ($number->isNegative()) {\n            return bcadd($number->__toString(), '-1', 0);\n        }\n\n        return bcadd($number->__toString(), '0', 0);\n    }\n\n    /**\n     * @psalm-suppress MoreSpecificReturnType we know that trimming `-` produces a positive numeric-string here\n     * @psalm-suppress LessSpecificReturnStatement we know that trimming `-` produces a positive numeric-string here\n     * @psalm-pure\n     */\n    public static function absolute(string $number): string\n    {\n        return ltrim($number, '-');\n    }\n\n    /**\n     * @psalm-param Money::ROUND_* $roundingMode\n     *\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    public static function round(string $number, int $roundingMode): string\n    {\n        $number = Number::fromString($number);\n\n        if ($number->isInteger()) {\n            return $number->__toString();\n        }\n\n        if ($number->isHalf() === false) {\n            return self::roundDigit($number);\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_UP) {\n            return bcadd(\n                $number->__toString(),\n                $number->getIntegerRoundingMultiplier(),\n                0\n            );\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_DOWN) {\n            return bcadd($number->__toString(), '0', 0);\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_EVEN) {\n            if ($number->isCurrentEven()) {\n                return bcadd($number->__toString(), '0', 0);\n            }\n\n            return bcadd(\n                $number->__toString(),\n                $number->getIntegerRoundingMultiplier(),\n                0\n            );\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_ODD) {\n            if ($number->isCurrentEven()) {\n                return bcadd(\n                    $number->__toString(),\n                    $number->getIntegerRoundingMultiplier(),\n                    0\n                );\n            }\n\n            return bcadd($number->__toString(), '0', 0);\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_POSITIVE_INFINITY) {\n            if ($number->isNegative()) {\n                return bcadd($number->__toString(), '0', 0);\n            }\n\n            return bcadd(\n                $number->__toString(),\n                $number->getIntegerRoundingMultiplier(),\n                0\n            );\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_NEGATIVE_INFINITY) {\n            if ($number->isNegative()) {\n                return bcadd(\n                    $number->__toString(),\n                    $number->getIntegerRoundingMultiplier(),\n                    0\n                );\n            }\n\n            return bcadd(\n                $number->__toString(),\n                '0',\n                0\n            );\n        }\n\n        throw new CoreInvalidArgumentException('Unknown rounding mode');\n    }\n\n    /**\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    private static function roundDigit(Number $number): string\n    {\n        if ($number->isCloserToNext()) {\n            return bcadd(\n                $number->__toString(),\n                $number->getIntegerRoundingMultiplier(),\n                0\n            );\n        }\n\n        return bcadd($number->__toString(), '0', 0);\n    }\n\n    /** @psalm-pure */\n    public static function share(string $amount, string $ratio, string $total): string\n    {\n        return self::floor(bcdiv(bcmul($amount, $ratio, self::SCALE), $total, self::SCALE));\n    }\n\n    /** @psalm-pure */\n    public static function mod(string $amount, string $divisor): string\n    {\n        if (bccomp($divisor, '0') === 0) {\n            throw InvalidArgumentException::moduloByZero();\n        }\n\n        return bcmod($amount, $divisor) ?? '0';\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Calculator/GmpCalculator.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Calculator;\n\nuse InvalidArgumentException as CoreInvalidArgumentException;\nuse Money\\Calculator;\nuse Money\\Exception\\InvalidArgumentException;\nuse Money\\Money;\nuse Money\\Number;\n\nuse function gmp_add;\nuse function gmp_cmp;\nuse function gmp_div_q;\nuse function gmp_div_qr;\nuse function gmp_init;\nuse function gmp_mod;\nuse function gmp_mul;\nuse function gmp_neg;\nuse function gmp_strval;\nuse function gmp_sub;\nuse function ltrim;\nuse function str_pad;\nuse function str_replace;\nuse function strlen;\nuse function substr;\n\nuse const GMP_ROUND_MINUSINF;\nuse const STR_PAD_LEFT;\n\n/**\n * @psalm-immutable\n *\n * Important: the {@see GmpCalculator} is not optimized for decimal operations, as GMP\n *            is designed to operate on large integers. Consider using this only if your\n *            system does not have `ext-bcmath` installed.\n */\nfinal class GmpCalculator implements Calculator\n{\n    private const SCALE = 14;\n\n    /** @psalm-pure */\n    public static function compare(string $a, string $b): int\n    {\n        $aNum = Number::fromString($a);\n        $bNum = Number::fromString($b);\n\n        if ($aNum->isDecimal() || $bNum->isDecimal()) {\n            $integersCompared = gmp_cmp($aNum->getIntegerPart(), $bNum->getIntegerPart());\n            if ($integersCompared !== 0) {\n                return $integersCompared;\n            }\n\n            $aNumFractional = $aNum->getFractionalPart() === '' ? '0' : $aNum->getFractionalPart();\n            $bNumFractional = $bNum->getFractionalPart() === '' ? '0' : $bNum->getFractionalPart();\n\n            return gmp_cmp($aNumFractional, $bNumFractional);\n        }\n\n        return gmp_cmp($a, $b);\n    }\n\n    /** @psalm-pure */\n    public static function add(string $amount, string $addend): string\n    {\n        return gmp_strval(gmp_add($amount, $addend));\n    }\n\n    /** @psalm-pure */\n    public static function subtract(string $amount, string $subtrahend): string\n    {\n        return gmp_strval(gmp_sub($amount, $subtrahend));\n    }\n\n    /** @psalm-pure */\n    public static function multiply(string $amount, string $multiplier): string\n    {\n        $multiplier = Number::fromString($multiplier);\n\n        if ($multiplier->isDecimal()) {\n            $decimalPlaces  = strlen($multiplier->getFractionalPart());\n            $multiplierBase = $multiplier->getIntegerPart();\n            $negativeZero   = $multiplierBase === '-0';\n\n            if ($negativeZero) {\n                $multiplierBase = '-';\n            }\n\n            if ($multiplierBase) {\n                $multiplierBase .= $multiplier->getFractionalPart();\n            } else {\n                $multiplierBase = ltrim($multiplier->getFractionalPart(), '0');\n            }\n\n            $resultBase = gmp_strval(gmp_mul(gmp_init($amount), gmp_init($multiplierBase)));\n\n            if ($resultBase === '0') {\n                return '0';\n            }\n\n            $result       = substr($resultBase, $decimalPlaces * -1);\n            $resultLength = strlen($result);\n            if ($decimalPlaces > $resultLength) {\n                /** @psalm-var numeric-string $finalResult */\n                $finalResult = '0.' . str_pad('', $decimalPlaces - $resultLength, '0') . $result;\n\n                return $finalResult;\n            }\n\n            /** @psalm-var numeric-string $finalResult */\n            $finalResult = substr($resultBase, 0, $decimalPlaces * -1) . '.' . $result;\n\n            if ($negativeZero) {\n                /** @psalm-var numeric-string $finalResult */\n                $finalResult = str_replace('-.', '-0.', $finalResult);\n            }\n\n            return $finalResult;\n        }\n\n        return gmp_strval(gmp_mul(gmp_init($amount), gmp_init((string) $multiplier)));\n    }\n\n    /** @psalm-pure */\n    public static function divide(string $amount, string $divisor): string\n    {\n        if (self::compare($divisor, '0') === 0) {\n            throw InvalidArgumentException::divisionByZero();\n        }\n\n        $divisor = Number::fromString($divisor);\n\n        if ($divisor->isDecimal()) {\n            $decimalPlaces = strlen($divisor->getFractionalPart());\n            $divisorBase   = $divisor->getIntegerPart();\n            $negativeZero  = $divisorBase === '-0';\n\n            if ($negativeZero) {\n                $divisorBase = '-';\n            }\n\n            if ($divisor->getIntegerPart()) {\n                $divisor = new Number($divisorBase . $divisor->getFractionalPart());\n            } else {\n                $divisor = new Number(ltrim($divisor->getFractionalPart(), '0'));\n            }\n\n            $amount = gmp_strval(gmp_mul(gmp_init($amount), gmp_init('1' . str_pad('', $decimalPlaces, '0'))));\n        }\n\n        [$integer, $remainder] = gmp_div_qr(gmp_init($amount), gmp_init((string) $divisor));\n\n        if (gmp_cmp($remainder, '0') === 0) {\n            return gmp_strval($integer);\n        }\n\n        $divisionOfRemainder = gmp_strval(\n            gmp_div_q(\n                gmp_mul($remainder, gmp_init('1' . str_pad('', self::SCALE, '0'))),\n                gmp_init((string) $divisor),\n                GMP_ROUND_MINUSINF\n            )\n        );\n\n        if ($divisionOfRemainder[0] === '-') {\n            $divisionOfRemainder = substr($divisionOfRemainder, 1);\n        }\n\n        /** @psalm-var numeric-string $finalResult */\n        $finalResult = gmp_strval($integer) . '.' . str_pad($divisionOfRemainder, self::SCALE, '0', STR_PAD_LEFT);\n\n        return $finalResult;\n    }\n\n    /** @psalm-pure */\n    public static function ceil(string $number): string\n    {\n        $number = Number::fromString($number);\n\n        if ($number->isInteger()) {\n            return $number->__toString();\n        }\n\n        if ($number->isNegative()) {\n            return self::add($number->getIntegerPart(), '0');\n        }\n\n        return self::add($number->getIntegerPart(), '1');\n    }\n\n    /** @psalm-pure */\n    public static function floor(string $number): string\n    {\n        $number = Number::fromString($number);\n\n        if ($number->isInteger()) {\n            return $number->__toString();\n        }\n\n        if ($number->isNegative()) {\n            return self::add($number->getIntegerPart(), '-1');\n        }\n\n        return self::add($number->getIntegerPart(), '0');\n    }\n\n    /**\n     * @psalm-suppress MoreSpecificReturnType we know that trimming `-` produces a positive numeric-string here\n     * @psalm-suppress LessSpecificReturnStatement we know that trimming `-` produces a positive numeric-string here\n     * @psalm-pure\n     */\n    public static function absolute(string $number): string\n    {\n        return ltrim($number, '-');\n    }\n\n    /**\n     * @psalm-param Money::ROUND_* $roundingMode\n     *\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    public static function round(string $number, int $roundingMode): string\n    {\n        $number = Number::fromString($number);\n\n        if ($number->isInteger()) {\n            return $number->__toString();\n        }\n\n        if ($number->isHalf() === false) {\n            return self::roundDigit($number);\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_UP) {\n            return self::add(\n                $number->getIntegerPart(),\n                $number->getIntegerRoundingMultiplier()\n            );\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_DOWN) {\n            return self::add($number->getIntegerPart(), '0');\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_EVEN) {\n            if ($number->isCurrentEven()) {\n                return self::add($number->getIntegerPart(), '0');\n            }\n\n            return self::add(\n                $number->getIntegerPart(),\n                $number->getIntegerRoundingMultiplier()\n            );\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_ODD) {\n            if ($number->isCurrentEven()) {\n                return self::add(\n                    $number->getIntegerPart(),\n                    $number->getIntegerRoundingMultiplier()\n                );\n            }\n\n            return self::add($number->getIntegerPart(), '0');\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_POSITIVE_INFINITY) {\n            if ($number->isNegative()) {\n                return self::add(\n                    $number->getIntegerPart(),\n                    '0'\n                );\n            }\n\n            return self::add(\n                $number->getIntegerPart(),\n                $number->getIntegerRoundingMultiplier()\n            );\n        }\n\n        if ($roundingMode === Money::ROUND_HALF_NEGATIVE_INFINITY) {\n            if ($number->isNegative()) {\n                return self::add(\n                    $number->getIntegerPart(),\n                    $number->getIntegerRoundingMultiplier()\n                );\n            }\n\n            return self::add(\n                $number->getIntegerPart(),\n                '0'\n            );\n        }\n\n        throw new CoreInvalidArgumentException('Unknown rounding mode');\n    }\n\n    /**\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    private static function roundDigit(Number $number): string\n    {\n        if ($number->isCloserToNext()) {\n            return self::add(\n                $number->getIntegerPart(),\n                $number->getIntegerRoundingMultiplier()\n            );\n        }\n\n        return self::add($number->getIntegerPart(), '0');\n    }\n\n    /** @psalm-pure */\n    public static function share(string $amount, string $ratio, string $total): string\n    {\n        return self::floor(self::divide(self::multiply($amount, $ratio), $total));\n    }\n\n    /** @psalm-pure */\n    public static function mod(string $amount, string $divisor): string\n    {\n        if (self::compare($divisor, '0') === 0) {\n            throw InvalidArgumentException::moduloByZero();\n        }\n\n        // gmp_mod() only calculates non-negative integers, so we use absolutes\n        $remainder = gmp_mod(self::absolute($amount), self::absolute($divisor));\n\n        // If the amount was negative, we negate the result of the modulus operation\n        $amount = Number::fromString($amount);\n\n        if ($amount->isNegative()) {\n            $remainder = gmp_neg($remainder);\n        }\n\n        return gmp_strval($remainder);\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Calculator.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\nuse Money\\Exception\\InvalidArgumentException;\n\n/**\n * Money calculations abstracted away from the Money value object.\n *\n * @internal the calculator component is an internal detail of this library: it is only supposed to be replaced if\n *           your system requires a custom architecture for operating on large numbers.\n */\ninterface Calculator\n{\n    /**\n     * Compare a to b.\n     *\n     * Retrieves a negative value if $a < $b.\n     * Retrieves a positive value if $a > $b.\n     * Retrieves zero if $a == $b\n     *\n     * @psalm-param numeric-string $a\n     * @psalm-param numeric-string $b\n     *\n     * @psalm-pure\n     */\n    public static function compare(string $a, string $b): int;\n\n    /**\n     * Add added to amount.\n     *\n     * @psalm-param numeric-string $amount\n     * @psalm-param numeric-string $addend\n     *\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    public static function add(string $amount, string $addend): string;\n\n    /**\n     * Subtract subtrahend from amount.\n     *\n     * @psalm-param numeric-string $amount\n     * @psalm-param numeric-string $subtrahend\n     *\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    public static function subtract(string $amount, string $subtrahend): string;\n\n    /**\n     * Multiply amount with multiplier.\n     *\n     * @psalm-param numeric-string $amount\n     * @psalm-param numeric-string $multiplier\n     *\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    public static function multiply(string $amount, string $multiplier): string;\n\n    /**\n     * Divide amount with divisor.\n     *\n     * @psalm-param numeric-string $amount\n     * @psalm-param numeric-string $divisor\n     *\n     * @psalm-return numeric-string\n     *\n     * @throws InvalidArgumentException when $divisor is zero.\n     *\n     * @psalm-pure\n     */\n    public static function divide(string $amount, string $divisor): string;\n\n    /**\n     * Round number to following integer.\n     *\n     * @psalm-param numeric-string $number\n     *\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    public static function ceil(string $number): string;\n\n    /**\n     * Round number to preceding integer.\n     *\n     * @psalm-param numeric-string $number\n     *\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    public static function floor(string $number): string;\n\n    /**\n     * Returns the absolute value of the number.\n     *\n     * @psalm-param numeric-string $number\n     *\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    public static function absolute(string $number): string;\n\n    /**\n     * Round number, use rounding mode for tie-breaker.\n     *\n     * @psalm-param numeric-string $number\n     * @psalm-param Money::ROUND_* $roundingMode\n     *\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    public static function round(string $number, int $roundingMode): string;\n\n    /**\n     * Share amount among ratio / total portions.\n     *\n     * @psalm-param numeric-string $amount\n     * @psalm-param numeric-string $ratio\n     * @psalm-param numeric-string $total\n     *\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     */\n    public static function share(string $amount, string $ratio, string $total): string;\n\n    /**\n     * Get the modulus of an amount.\n     *\n     * @psalm-param numeric-string $amount\n     * @psalm-param numeric-string $divisor\n     *\n     * @psalm-return numeric-string\n     *\n     * @throws InvalidArgumentException when $divisor is zero.\n     *\n     * @psalm-pure\n     */\n    public static function mod(string $amount, string $divisor): string;\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Converter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\nuse InvalidArgumentException;\n\nuse function sprintf;\n\n/**\n * Provides a way to convert Money to Money in another Currency using an exchange rate.\n */\nfinal class Converter\n{\n    private Currencies $currencies;\n\n    private Exchange $exchange;\n\n    public function __construct(Currencies $currencies, Exchange $exchange)\n    {\n        $this->currencies = $currencies;\n        $this->exchange   = $exchange;\n    }\n\n    public function convert(Money $money, Currency $counterCurrency, int $roundingMode = Money::ROUND_HALF_UP): Money\n    {\n        return $this->convertAgainstCurrencyPair(\n            $money,\n            $this->exchange->quote(\n                $money->getCurrency(),\n                $counterCurrency\n            ),\n            $roundingMode\n        );\n    }\n\n    /** @return array{0: Money, 1: CurrencyPair} */\n    public function convertAndReturnWithCurrencyPair(Money $money, Currency $counterCurrency, int $roundingMode = Money::ROUND_HALF_UP): array\n    {\n        $pair = $this->exchange->quote(\n            $money->getCurrency(),\n            $counterCurrency\n        );\n\n        return [$this->convertAgainstCurrencyPair($money, $pair, $roundingMode), $pair];\n    }\n\n    public function convertAgainstCurrencyPair(Money $money, CurrencyPair $currencyPair, int $roundingMode = Money::ROUND_HALF_UP): Money\n    {\n        if (! $money->getCurrency()->equals($currencyPair->getBaseCurrency())) {\n            throw new InvalidArgumentException(\n                sprintf(\n                    'Expecting to convert against base currency %s, but got %s instead',\n                    $money->getCurrency()->getCode(),\n                    $currencyPair->getBaseCurrency()->getCode()\n                )\n            );\n        }\n\n        $ratio                  = $currencyPair->getConversionRatio();\n        $baseCurrencySubunit    = $this->currencies->subunitFor($currencyPair->getBaseCurrency());\n        $counterCurrencySubunit = $this->currencies->subunitFor($currencyPair->getCounterCurrency());\n        $subunitDifference      = $baseCurrencySubunit - $counterCurrencySubunit;\n\n        $ratio = Number::fromString($ratio)\n            ->base10($subunitDifference)\n            ->__toString();\n\n        $counterValue = $money->multiply($ratio, $roundingMode);\n\n        return new Money($counterValue->getAmount(), $currencyPair->getCounterCurrency());\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Currencies/AggregateCurrencies.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Currencies;\n\nuse AppendIterator;\nuse Money\\Currencies;\nuse Money\\Currency;\nuse Money\\Exception\\UnknownCurrencyException;\nuse Traversable;\n\n/**\n * Aggregates several currency repositories.\n */\nfinal class AggregateCurrencies implements Currencies\n{\n    /** @var Currencies[] */\n    private array $currencies;\n\n    /**\n     * @param Currencies[] $currencies\n     */\n    public function __construct(array $currencies)\n    {\n        $this->currencies = $currencies;\n    }\n\n    public function contains(Currency $currency): bool\n    {\n        foreach ($this->currencies as $currencies) {\n            if ($currencies->contains($currency)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    public function subunitFor(Currency $currency): int\n    {\n        foreach ($this->currencies as $currencies) {\n            if ($currencies->contains($currency)) {\n                return $currencies->subunitFor($currency);\n            }\n        }\n\n        throw new UnknownCurrencyException('Cannot find currency ' . $currency->getCode());\n    }\n\n    /** {@inheritDoc} */\n    public function getIterator(): Traversable\n    {\n        /** @psalm-var AppendIterator&Traversable<int|string, Currency> $iterator */\n        $iterator = new AppendIterator();\n\n        foreach ($this->currencies as $currencies) {\n            $iterator->append($currencies->getIterator());\n        }\n\n        return $iterator;\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Currencies/BitcoinCurrencies.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Currencies;\n\nuse ArrayIterator;\nuse Money\\Currencies;\nuse Money\\Currency;\nuse Money\\Exception\\UnknownCurrencyException;\nuse Traversable;\n\nfinal class BitcoinCurrencies implements Currencies\n{\n    public const CODE = 'XBT';\n\n    public const SYMBOL = \"\\xC9\\x83\";\n\n    public function contains(Currency $currency): bool\n    {\n        return $currency->getCode() === self::CODE;\n    }\n\n    public function subunitFor(Currency $currency): int\n    {\n        if ($currency->getCode() !== self::CODE) {\n            throw new UnknownCurrencyException($currency->getCode() . ' is not bitcoin and is not supported by this currency repository');\n        }\n\n        return 8;\n    }\n\n    /** {@inheritDoc} */\n    public function getIterator(): Traversable\n    {\n        return new ArrayIterator([new Currency(self::CODE)]);\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Currencies/CachedCurrencies.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Currencies;\n\nuse ArrayIterator;\nuse Cache\\TagInterop\\TaggableCacheItemInterface;\nuse CallbackFilterIterator;\nuse Money\\Currencies;\nuse Money\\Currency;\nuse Psr\\Cache\\CacheItemPoolInterface;\nuse Traversable;\n\nuse function iterator_to_array;\n\n/**\n * Cache the result of currency checking.\n */\nfinal class CachedCurrencies implements Currencies\n{\n    private Currencies $currencies;\n\n    private CacheItemPoolInterface $pool;\n\n    public function __construct(Currencies $currencies, CacheItemPoolInterface $pool)\n    {\n        $this->currencies = $currencies;\n        $this->pool       = $pool;\n    }\n\n    public function contains(Currency $currency): bool\n    {\n        $item = $this->pool->getItem('currency|availability|' . $currency->getCode());\n\n        if ($item->isHit() === false) {\n            $item->set($this->currencies->contains($currency));\n\n            if ($item instanceof TaggableCacheItemInterface) {\n                $item->setTags(['currency.availability']);\n            }\n\n            $this->pool->save($item);\n        }\n\n        return (bool) $item->get();\n    }\n\n    public function subunitFor(Currency $currency): int\n    {\n        $item = $this->pool->getItem('currency|subunit|' . $currency->getCode());\n\n        if ($item->isHit() === false) {\n            $item->set($this->currencies->subunitFor($currency));\n\n            if ($item instanceof TaggableCacheItemInterface) {\n                $item->setTags(['currency.subunit']);\n            }\n\n            $this->pool->save($item);\n        }\n\n        return (int) $item->get();\n    }\n\n    /** {@inheritDoc} */\n    public function getIterator(): Traversable\n    {\n        return new CallbackFilterIterator(\n            new ArrayIterator(iterator_to_array($this->currencies->getIterator())),\n            function (Currency $currency): bool {\n                $item = $this->pool->getItem('currency|availability|' . $currency->getCode());\n                $item->set(true);\n\n                if ($item instanceof TaggableCacheItemInterface) {\n                    $item->setTags(['currency.availability']);\n                }\n\n                $this->pool->save($item);\n\n                return true;\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Currencies/CurrencyList.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Currencies;\n\nuse ArrayIterator;\nuse Money\\Currencies;\nuse Money\\Currency;\nuse Money\\Exception\\UnknownCurrencyException;\nuse Traversable;\n\nuse function array_keys;\nuse function array_map;\n\n/**\n * A list of custom currencies.\n */\nfinal class CurrencyList implements Currencies\n{\n    /**\n     * Map of currencies and their sub-units indexed by code.\n     *\n     * @psalm-var array<non-empty-string, int>\n     */\n    private array $currencies;\n\n    /** @psalm-param array<non-empty-string, positive-int|0> $currencies */\n    public function __construct(array $currencies)\n    {\n        $this->currencies = $currencies;\n    }\n\n    public function contains(Currency $currency): bool\n    {\n        return isset($this->currencies[$currency->getCode()]);\n    }\n\n    public function subunitFor(Currency $currency): int\n    {\n        if (! $this->contains($currency)) {\n            throw new UnknownCurrencyException('Cannot find currency ' . $currency->getCode());\n        }\n\n        return $this->currencies[$currency->getCode()];\n    }\n\n    /** {@inheritDoc} */\n    public function getIterator(): Traversable\n    {\n        return new ArrayIterator(\n            array_map(\n                static function ($code) {\n                    return new Currency($code);\n                },\n                array_keys($this->currencies)\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Currencies/ISOCurrencies.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Currencies;\n\nuse ArrayIterator;\nuse Money\\Currencies;\nuse Money\\Currency;\nuse Money\\Exception\\UnknownCurrencyException;\nuse RuntimeException;\nuse Traversable;\n\nuse function array_keys;\nuse function array_map;\nuse function is_file;\n\n/**\n * List of supported ISO 4217 currency codes and names.\n */\nfinal class ISOCurrencies implements Currencies\n{\n    /**\n     * Map of known currencies indexed by code.\n     *\n     * @psalm-var non-empty-array<non-empty-string, array{\n     *     alphabeticCode: non-empty-string,\n     *     currency: non-empty-string,\n     *     minorUnit: positive-int|0,\n     *     numericCode: positive-int\n     * }>|null\n     */\n    private static ?array $currencies = null;\n\n    public function contains(Currency $currency): bool\n    {\n        return isset($this->getCurrencies()[$currency->getCode()]);\n    }\n\n    public function subunitFor(Currency $currency): int\n    {\n        if (! $this->contains($currency)) {\n            throw new UnknownCurrencyException('Cannot find ISO currency ' . $currency->getCode());\n        }\n\n        return $this->getCurrencies()[$currency->getCode()]['minorUnit'];\n    }\n\n    /**\n     * Returns the numeric code for a currency.\n     *\n     * @throws UnknownCurrencyException If currency is not available in the current context.\n     */\n    public function numericCodeFor(Currency $currency): int\n    {\n        if (! $this->contains($currency)) {\n            throw new UnknownCurrencyException('Cannot find ISO currency ' . $currency->getCode());\n        }\n\n        return $this->getCurrencies()[$currency->getCode()]['numericCode'];\n    }\n\n    /**\n     * @psalm-return Traversable<int, Currency>\n     */\n    public function getIterator(): Traversable\n    {\n        return new ArrayIterator(\n            array_map(\n                static function ($code) {\n                    return new Currency($code);\n                },\n                array_keys($this->getCurrencies())\n            )\n        );\n    }\n\n    /**\n     * Returns a map of known currencies indexed by code.\n     *\n     * @psalm-return non-empty-array<non-empty-string, array{\n     *     alphabeticCode: non-empty-string,\n     *     currency: non-empty-string,\n     *     minorUnit: positive-int|0,\n     *     numericCode: positive-int\n     * }>\n     */\n    private function getCurrencies(): array\n    {\n        if (self::$currencies === null) {\n            self::$currencies = $this->loadCurrencies();\n        }\n\n        return self::$currencies;\n    }\n\n    /**\n     * @psalm-return non-empty-array<non-empty-string, array{\n     *     alphabeticCode: non-empty-string,\n     *     currency: non-empty-string,\n     *     minorUnit: positive-int|0,\n     *     numericCode: positive-int\n     * }>\n     *\n     * @psalm-suppress MixedInferredReturnType `include` cannot be inferred here\n     * @psalm-suppress MixedReturnStatement `include` cannot be inferred here\n     */\n    private function loadCurrencies(): array\n    {\n        $file = __DIR__ . '/../../resources/currency.php';\n\n        if (is_file($file)) {\n            return require $file;\n        }\n\n        throw new RuntimeException('Failed to load currency ISO codes.');\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Currencies.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\nuse IteratorAggregate;\nuse Money\\Exception\\UnknownCurrencyException;\nuse Traversable;\n\n/**\n * Implement this to provide a list of currencies.\n */\ninterface Currencies extends IteratorAggregate\n{\n    /**\n     * Checks whether a currency is available in the current context.\n     */\n    public function contains(Currency $currency): bool;\n\n    /**\n     * Returns the subunit for a currency.\n     *\n     * @throws UnknownCurrencyException If currency is not available in the current context.\n     */\n    public function subunitFor(Currency $currency): int;\n\n    /**\n     * @psalm-return Traversable<int|string, Currency>\n     */\n    public function getIterator(): Traversable;\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Currency.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\nuse JsonSerializable;\n\nuse function strtoupper;\n\n/**\n * Currency Value Object.\n *\n * Holds Currency specific data.\n *\n * @psalm-immutable\n */\nfinal class Currency implements JsonSerializable\n{\n    /**\n     * Currency code.\n     *\n     * @psalm-var non-empty-string\n     */\n    private string $code;\n\n    /** @psalm-param non-empty-string $code */\n    public function __construct(string $code)\n    {\n        /** @psalm-var non-empty-string $this->code */\n        $this->code = strtoupper($code);\n    }\n\n    /**\n     * Returns the currency code.\n     *\n     * @psalm-return non-empty-string\n     */\n    public function getCode(): string\n    {\n        return $this->code;\n    }\n\n    /**\n     * Checks whether this currency is the same as an other.\n     */\n    public function equals(Currency $other): bool\n    {\n        return $this->code === $other->code;\n    }\n\n    public function __toString(): string\n    {\n        return $this->code;\n    }\n\n    /**\n     * {@inheritdoc}\n     *\n     * @return string\n     */\n    public function jsonSerialize()\n    {\n        return $this->code;\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/CurrencyPair.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\nuse InvalidArgumentException;\nuse JsonSerializable;\n\nuse function assert;\nuse function is_numeric;\nuse function preg_match;\nuse function sprintf;\n\n/**\n * Currency Pair holding a base, a counter currency and a conversion ratio.\n *\n * @see http://en.wikipedia.org/wiki/Currency_pair\n */\nfinal class CurrencyPair implements JsonSerializable\n{\n    /**\n     * Currency to convert from.\n     */\n    private Currency $baseCurrency;\n\n    /**\n     * Currency to convert to.\n     */\n    private Currency $counterCurrency;\n\n    /** @psalm-var numeric-string */\n    private string $conversionRatio;\n\n    /**\n     * @psalm-param numeric-string $conversionRatio\n     */\n    public function __construct(Currency $baseCurrency, Currency $counterCurrency, string $conversionRatio)\n    {\n        $this->counterCurrency = $counterCurrency;\n        $this->baseCurrency    = $baseCurrency;\n        $this->conversionRatio = $conversionRatio;\n    }\n\n    /**\n     * Creates a new Currency Pair based on \"EUR/USD 1.2500\" form representation.\n     *\n     * @param string $iso String representation of the form \"EUR/USD 1.2500\"\n     *\n     * @throws InvalidArgumentException Format of $iso is invalid.\n     */\n    public static function createFromIso(string $iso): CurrencyPair\n    {\n        $currency = '([A-Z]{2,3})';\n        $ratio    = '([0-9]*\\.?[0-9]+)'; // @see http://www.regular-expressions.info/floatingpoint.html\n        $pattern  = '#' . $currency . '/' . $currency . ' ' . $ratio . '#';\n\n        $matches = [];\n\n        if (! preg_match($pattern, $iso, $matches)) {\n            throw new InvalidArgumentException(sprintf('Cannot create currency pair from ISO string \"%s\", format of string is invalid', $iso));\n        }\n\n        assert(! empty($matches[1]));\n        assert(is_numeric($matches[2]));\n        assert(is_numeric($matches[3]));\n\n        return new self(new Currency($matches[1]), new Currency($matches[2]), $matches[3]);\n    }\n\n    /**\n     * Returns the counter currency.\n     */\n    public function getCounterCurrency(): Currency\n    {\n        return $this->counterCurrency;\n    }\n\n    /**\n     * Returns the base currency.\n     */\n    public function getBaseCurrency(): Currency\n    {\n        return $this->baseCurrency;\n    }\n\n    /**\n     * Returns the conversion ratio.\n     *\n     * @psalm-return numeric-string\n     */\n    public function getConversionRatio(): string\n    {\n        return $this->conversionRatio;\n    }\n\n    /**\n     * Checks if an other CurrencyPair has the same parameters as this.\n     */\n    public function equals(CurrencyPair $other): bool\n    {\n        return $this->baseCurrency->equals($other->baseCurrency)\n            && $this->counterCurrency->equals($other->counterCurrency)\n            && $this->conversionRatio === $other->conversionRatio;\n    }\n\n    /**\n     * {@inheritdoc}\n     *\n     * @return array\n     */\n    public function jsonSerialize()\n    {\n        return [\n            'baseCurrency' => $this->baseCurrency,\n            'counterCurrency' => $this->counterCurrency,\n            'ratio' => $this->conversionRatio,\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exception/FormatterException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Exception;\n\nuse Money\\Exception;\nuse RuntimeException;\n\n/**\n * Thrown when a Money object cannot be formatted into a string.\n */\nfinal class FormatterException extends RuntimeException implements Exception\n{\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exception/InvalidArgumentException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Exception;\n\nuse InvalidArgumentException as CoreInvalidArgumentException;\nuse Money\\Exception;\n\nfinal class InvalidArgumentException extends CoreInvalidArgumentException implements Exception\n{\n    /** @psalm-pure */\n    public static function divisionByZero(): self\n    {\n        return new self('Cannot compute division with a zero divisor');\n    }\n\n    /** @psalm-pure */\n    public static function moduloByZero(): self\n    {\n        return new self('Cannot compute modulo with a zero divisor');\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exception/ParserException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Exception;\n\nuse Money\\Exception;\nuse RuntimeException;\n\n/**\n * Thrown when a string cannot be parsed to a Money object.\n */\nfinal class ParserException extends RuntimeException implements Exception\n{\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exception/UnknownCurrencyException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Exception;\n\nuse DomainException;\nuse Money\\Exception;\n\n/**\n * Thrown when trying to get ISO currency that does not exists.\n */\nfinal class UnknownCurrencyException extends DomainException implements Exception\n{\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exception/UnresolvableCurrencyPairException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Exception;\n\nuse InvalidArgumentException;\nuse Money\\Currency;\nuse Money\\Exception;\n\nuse function sprintf;\n\n/**\n * Thrown when there is no currency pair (rate) available for the given currencies.\n */\nfinal class UnresolvableCurrencyPairException extends InvalidArgumentException implements Exception\n{\n    /**\n     * Creates an exception from Currency objects.\n     */\n    public static function createFromCurrencies(Currency $baseCurrency, Currency $counterCurrency): UnresolvableCurrencyPairException\n    {\n        $message = sprintf(\n            'Cannot resolve a currency pair for currencies: %s/%s',\n            $baseCurrency->getCode(),\n            $counterCurrency->getCode()\n        );\n\n        return new self($message);\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exception.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\n/**\n * Common interface for all exceptions thrown by this library.\n */\ninterface Exception\n{\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exchange/ExchangerExchange.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Exchange;\n\nuse Exchanger\\Contract\\ExchangeRateProvider;\nuse Exchanger\\CurrencyPair as ExchangerCurrencyPair;\nuse Exchanger\\Exception\\Exception as ExchangerException;\nuse Exchanger\\ExchangeRateQuery;\nuse Money\\Currency;\nuse Money\\CurrencyPair;\nuse Money\\Exception\\UnresolvableCurrencyPairException;\nuse Money\\Exchange;\n\nuse function assert;\nuse function is_numeric;\nuse function sprintf;\n\n/**\n * Provides a way to get exchange rate from a third-party source and return a currency pair.\n */\nfinal class ExchangerExchange implements Exchange\n{\n    private ExchangeRateProvider $exchanger;\n\n    public function __construct(ExchangeRateProvider $exchanger)\n    {\n        $this->exchanger = $exchanger;\n    }\n\n    public function quote(Currency $baseCurrency, Currency $counterCurrency): CurrencyPair\n    {\n        try {\n            $query = new ExchangeRateQuery(\n                new ExchangerCurrencyPair($baseCurrency->getCode(), $counterCurrency->getCode())\n            );\n            $rate  = $this->exchanger->getExchangeRate($query);\n        } catch (ExchangerException) {\n            throw UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency);\n        }\n\n        $rateValue = sprintf('%.14F', $rate->getValue());\n\n        assert(is_numeric($rateValue));\n\n        return new CurrencyPair($baseCurrency, $counterCurrency, $rateValue);\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exchange/FixedExchange.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Exchange;\n\nuse Money\\Currency;\nuse Money\\CurrencyPair;\nuse Money\\Exception\\UnresolvableCurrencyPairException;\nuse Money\\Exchange;\n\n/**\n * Provides a way to get exchange rate from a static list (array).\n */\nfinal class FixedExchange implements Exchange\n{\n    /** @psalm-var array<non-empty-string, array<non-empty-string, numeric-string>> */\n    private array $list;\n\n    /** @psalm-param array<non-empty-string, array<non-empty-string, numeric-string>> $list */\n    public function __construct(array $list)\n    {\n        $this->list = $list;\n    }\n\n    public function quote(Currency $baseCurrency, Currency $counterCurrency): CurrencyPair\n    {\n        if (isset($this->list[$baseCurrency->getCode()][$counterCurrency->getCode()])) {\n            return new CurrencyPair(\n                $baseCurrency,\n                $counterCurrency,\n                $this->list[$baseCurrency->getCode()][$counterCurrency->getCode()]\n            );\n        }\n\n        throw UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency);\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exchange/IndirectExchange.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Exchange;\n\nuse Money\\Currencies;\nuse Money\\Currency;\nuse Money\\CurrencyPair;\nuse Money\\Exception\\UnresolvableCurrencyPairException;\nuse Money\\Exchange;\nuse Money\\Money;\nuse SplQueue;\n\nuse function array_reduce;\nuse function array_reverse;\n\n/**\n * Provides a way to get an exchange rate through a minimal set of intermediate conversions.\n */\nfinal class IndirectExchange implements Exchange\n{\n    private Currencies $currencies;\n\n    private Exchange $exchange;\n\n    public function __construct(Exchange $exchange, Currencies $currencies)\n    {\n        $this->exchange   = $exchange;\n        $this->currencies = $currencies;\n    }\n\n    public function quote(Currency $baseCurrency, Currency $counterCurrency): CurrencyPair\n    {\n        try {\n            return $this->exchange->quote($baseCurrency, $counterCurrency);\n        } catch (UnresolvableCurrencyPairException) {\n            $rate = array_reduce(\n                $this->getConversions($baseCurrency, $counterCurrency),\n                /**\n                 * @psalm-param numeric-string $carry\n                 *\n                 * @psalm-return numeric-string\n                 */\n                static function (string $carry, CurrencyPair $pair) {\n                    $calculator = Money::getCalculator();\n\n                    return $calculator::multiply($carry, $pair->getConversionRatio());\n                },\n                '1.0'\n            );\n\n            return new CurrencyPair($baseCurrency, $counterCurrency, $rate);\n        }\n    }\n\n    /**\n     * @return CurrencyPair[]\n     *\n     * @throws UnresolvableCurrencyPairException\n     */\n    private function getConversions(Currency $baseCurrency, Currency $counterCurrency): array\n    {\n        $startNode             = new IndirectExchangeQueuedItem($baseCurrency);\n        $startNode->discovered = true;\n\n        /** @psalm-var array<non-empty-string, IndirectExchangeQueuedItem> $nodes */\n        $nodes = [$baseCurrency->getCode() => $startNode];\n\n        /** @psam-var SplQueue<IndirectExchangeQueuedItem> $frontier */\n        $frontier = new SplQueue();\n        $frontier->enqueue($startNode);\n\n        while ($frontier->count()) {\n            /** @psalm-var IndirectExchangeQueuedItem $currentNode */\n            $currentNode     = $frontier->dequeue();\n            $currentCurrency = $currentNode->currency;\n\n            if ($currentCurrency->equals($counterCurrency)) {\n                return $this->reconstructConversionChain($nodes, $currentNode);\n            }\n\n            foreach ($this->currencies as $candidateCurrency) {\n                if (! isset($nodes[$candidateCurrency->getCode()])) {\n                    $nodes[$candidateCurrency->getCode()] = new IndirectExchangeQueuedItem($candidateCurrency);\n                }\n\n                $node = $nodes[$candidateCurrency->getCode()];\n\n                if ($node->discovered) {\n                    continue;\n                }\n\n                try {\n                    // Check if the candidate is a neighbor. This will throw an exception if it isn't.\n                    $this->exchange->quote($currentCurrency, $candidateCurrency);\n\n                    $node->discovered = true;\n                    $node->parent     = $currentNode;\n\n                    $frontier->enqueue($node);\n                } catch (UnresolvableCurrencyPairException $exception) {\n                    // Not a neighbor. Move on.\n                }\n            }\n        }\n\n        throw UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency);\n    }\n\n    /**\n     * @psalm-param array<non-empty-string, IndirectExchangeQueuedItem> $currencies\n     *\n     * @return CurrencyPair[]\n     * @psalm-return list<CurrencyPair>\n     */\n    private function reconstructConversionChain(array $currencies, IndirectExchangeQueuedItem $goalNode): array\n    {\n        $current     = $goalNode;\n        $conversions = [];\n\n        while ($current->parent) {\n            $previous      = $currencies[$current->parent->currency->getCode()];\n            $conversions[] = $this->exchange->quote($previous->currency, $current->currency);\n            $current       = $previous;\n        }\n\n        return array_reverse($conversions);\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exchange/IndirectExchangeQueuedItem.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Exchange;\n\nuse Money\\Currency;\n\n/** @internal for sole consumption by {@see IndirectExchange} */\nfinal class IndirectExchangeQueuedItem\n{\n    public Currency $currency;\n    public bool $discovered = false;\n    public ?self $parent    = null;\n\n    public function __construct(Currency $currency)\n    {\n        $this->currency = $currency;\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exchange/ReversedCurrenciesExchange.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Exchange;\n\nuse Money\\Currency;\nuse Money\\CurrencyPair;\nuse Money\\Exception\\UnresolvableCurrencyPairException;\nuse Money\\Exchange;\nuse Money\\Money;\n\n/**\n * Tries the reverse of the currency pair if one is not available.\n *\n * Note: adding nested ReversedCurrenciesExchange could cause a huge performance hit.\n */\nfinal class ReversedCurrenciesExchange implements Exchange\n{\n    private Exchange $exchange;\n\n    public function __construct(Exchange $exchange)\n    {\n        $this->exchange = $exchange;\n    }\n\n    public function quote(Currency $baseCurrency, Currency $counterCurrency): CurrencyPair\n    {\n        try {\n            return $this->exchange->quote($baseCurrency, $counterCurrency);\n        } catch (UnresolvableCurrencyPairException $exception) {\n            $calculator = Money::getCalculator();\n\n            try {\n                $currencyPair = $this->exchange->quote($counterCurrency, $baseCurrency);\n\n                return new CurrencyPair(\n                    $baseCurrency,\n                    $counterCurrency,\n                    $calculator::divide('1', $currencyPair->getConversionRatio())\n                );\n            } catch (UnresolvableCurrencyPairException) {\n                throw $exception;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exchange/SwapExchange.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Exchange;\n\nuse Exchanger\\Exception\\Exception as ExchangerException;\nuse Money\\Currency;\nuse Money\\CurrencyPair;\nuse Money\\Exception\\UnresolvableCurrencyPairException;\nuse Money\\Exchange;\nuse Swap\\Swap;\n\n/**\n * Provides a way to get exchange rate from a third-party source and return a currency pair.\n */\nfinal class SwapExchange implements Exchange\n{\n    private Swap $swap;\n\n    public function __construct(Swap $swap)\n    {\n        $this->swap = $swap;\n    }\n\n    public function quote(Currency $baseCurrency, Currency $counterCurrency): CurrencyPair\n    {\n        try {\n            $rate = $this->swap->latest($baseCurrency->getCode() . '/' . $counterCurrency->getCode());\n        } catch (ExchangerException) {\n            throw UnresolvableCurrencyPairException::createFromCurrencies($baseCurrency, $counterCurrency);\n        }\n\n        return new CurrencyPair($baseCurrency, $counterCurrency, (string) $rate->getValue());\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Exchange.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\nuse Money\\Exception\\UnresolvableCurrencyPairException;\n\n/**\n * Provides a way to get exchange rate from a third-party source and return a currency pair.\n */\ninterface Exchange\n{\n    /**\n     * Returns a currency pair for the passed currencies with the rate coming from a third-party source.\n     *\n     * @throws UnresolvableCurrencyPairException When there is no currency pair (rate) available for the given currencies.\n     */\n    public function quote(Currency $baseCurrency, Currency $counterCurrency): CurrencyPair;\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Formatter/AggregateMoneyFormatter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Formatter;\n\nuse Money\\Exception\\FormatterException;\nuse Money\\Money;\nuse Money\\MoneyFormatter;\n\n/**\n * Formats a Money object using other Money formatters.\n */\nfinal class AggregateMoneyFormatter implements MoneyFormatter\n{\n    /**\n     * @var MoneyFormatter[] indexed by currency code\n     * @psalm-var non-empty-array<non-empty-string, MoneyFormatter> indexed by currency code\n     */\n    private array $formatters;\n\n    /**\n     * @param MoneyFormatter[] $formatters indexed by currency code\n     * @psalm-param non-empty-array<non-empty-string, MoneyFormatter> $formatters indexed by currency code\n     */\n    public function __construct(array $formatters)\n    {\n        $this->formatters = $formatters;\n    }\n\n    public function format(Money $money): string\n    {\n        $currencyCode = $money->getCurrency()->getCode();\n\n        if (isset($this->formatters[$currencyCode])) {\n            return $this->formatters[$currencyCode]->format($money);\n        }\n\n        if (isset($this->formatters['*'])) {\n            return $this->formatters['*']->format($money);\n        }\n\n        throw new FormatterException('No formatter found for currency ' . $currencyCode);\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Formatter/BitcoinMoneyFormatter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Formatter;\n\nuse Money\\Currencies;\nuse Money\\Currencies\\BitcoinCurrencies;\nuse Money\\Exception\\FormatterException;\nuse Money\\Money;\nuse Money\\MoneyFormatter;\nuse Money\\Number;\n\nuse function str_pad;\nuse function strlen;\nuse function strpos;\nuse function substr;\n\n/**\n * Formats Money to Bitcoin currency.\n */\nfinal class BitcoinMoneyFormatter implements MoneyFormatter\n{\n    private int $fractionDigits;\n\n    private Currencies $currencies;\n\n    public function __construct(int $fractionDigits, Currencies $currencies)\n    {\n        $this->fractionDigits = $fractionDigits;\n        $this->currencies     = $currencies;\n    }\n\n    public function format(Money $money): string\n    {\n        if ($money->getCurrency()->getCode() !== BitcoinCurrencies::CODE) {\n            throw new FormatterException('Bitcoin Formatter can only format Bitcoin currency');\n        }\n\n        $valueBase = $money->getAmount();\n        $negative  = false;\n\n        if ($valueBase[0] === '-') {\n            $negative  = true;\n            $valueBase = substr($valueBase, 1);\n        }\n\n        $subunit     = $this->currencies->subunitFor($money->getCurrency());\n        $valueBase   = Number::roundMoneyValue($valueBase, $this->fractionDigits, $subunit);\n        $valueLength = strlen($valueBase);\n\n        if ($valueLength > $subunit) {\n            $formatted = substr($valueBase, 0, $valueLength - $subunit);\n\n            if ($subunit) {\n                $formatted .= '.';\n                $formatted .= substr($valueBase, $valueLength - $subunit);\n            }\n        } else {\n            $formatted = '0.' . str_pad('', $subunit - $valueLength, '0') . $valueBase;\n        }\n\n        if ($this->fractionDigits === 0) {\n            $formatted = substr($formatted, 0, (int) strpos($formatted, '.'));\n        } elseif ($this->fractionDigits > $subunit) {\n            $formatted .= str_pad('', $this->fractionDigits - $subunit, '0');\n        } elseif ($this->fractionDigits < $subunit) {\n            $lastDigit = (int) strpos($formatted, '.') + $this->fractionDigits + 1;\n            $formatted = substr($formatted, 0, $lastDigit);\n        }\n\n        $formatted = BitcoinCurrencies::SYMBOL . $formatted;\n\n        if ($negative) {\n            $formatted = '-' . $formatted;\n        }\n\n        return $formatted;\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Formatter/DecimalMoneyFormatter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Formatter;\n\nuse Money\\Currencies;\nuse Money\\Money;\nuse Money\\MoneyFormatter;\n\nuse function assert;\nuse function str_pad;\nuse function strlen;\nuse function substr;\n\n/**\n * Formats a Money object as a decimal string.\n */\nfinal class DecimalMoneyFormatter implements MoneyFormatter\n{\n    private Currencies $currencies;\n\n    public function __construct(Currencies $currencies)\n    {\n        $this->currencies = $currencies;\n    }\n\n    public function format(Money $money): string\n    {\n        $valueBase = $money->getAmount();\n        $negative  = $valueBase[0] === '-';\n\n        if ($negative) {\n            $valueBase = substr($valueBase, 1);\n        }\n\n        $subunit     = $this->currencies->subunitFor($money->getCurrency());\n        $valueLength = strlen($valueBase);\n\n        if ($valueLength > $subunit) {\n            $formatted     = substr($valueBase, 0, $valueLength - $subunit);\n            $decimalDigits = substr($valueBase, $valueLength - $subunit);\n\n            if (strlen($decimalDigits) > 0) {\n                $formatted .= '.' . $decimalDigits;\n            }\n        } else {\n            $formatted = '0.' . str_pad('', $subunit - $valueLength, '0') . $valueBase;\n        }\n\n        if ($negative) {\n            return '-' . $formatted;\n        }\n\n        assert(! empty($formatted));\n\n        return $formatted;\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Formatter/IntlLocalizedDecimalFormatter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Formatter;\n\nuse Money\\Currencies;\nuse Money\\Money;\nuse Money\\MoneyFormatter;\nuse NumberFormatter;\n\nuse function assert;\nuse function is_string;\nuse function str_pad;\nuse function strlen;\nuse function substr;\n\n/**\n * Formats a Money object using intl extension.\n */\nfinal class IntlLocalizedDecimalFormatter implements MoneyFormatter\n{\n    private NumberFormatter $formatter;\n\n    private Currencies $currencies;\n\n    public function __construct(NumberFormatter $formatter, Currencies $currencies)\n    {\n        $this->formatter  = $formatter;\n        $this->currencies = $currencies;\n    }\n\n    public function format(Money $money): string\n    {\n        $valueBase = $money->getAmount();\n        $negative  = $valueBase[0] === '-';\n\n        if ($negative) {\n            $valueBase = substr($valueBase, 1);\n        }\n\n        $subunit     = $this->currencies->subunitFor($money->getCurrency());\n        $valueLength = strlen($valueBase);\n\n        if ($valueLength > $subunit) {\n            $formatted     = substr($valueBase, 0, $valueLength - $subunit);\n            $decimalDigits = substr($valueBase, $valueLength - $subunit);\n\n            if (strlen($decimalDigits) > 0) {\n                $formatted .= '.' . $decimalDigits;\n            }\n        } else {\n            $formatted = '0.' . str_pad('', $subunit - $valueLength, '0') . $valueBase;\n        }\n\n        if ($negative) {\n            $formatted = '-' . $formatted;\n        }\n\n        $formatted = $this->formatter->format((float) $formatted);\n\n        assert(is_string($formatted) && ! empty($formatted));\n\n        return $formatted;\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Formatter/IntlMoneyFormatter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Formatter;\n\nuse Money\\Currencies;\nuse Money\\Money;\nuse Money\\MoneyFormatter;\nuse NumberFormatter;\n\nuse function assert;\nuse function str_pad;\nuse function strlen;\nuse function substr;\n\n/**\n * Formats a Money object using intl extension.\n */\nfinal class IntlMoneyFormatter implements MoneyFormatter\n{\n    private NumberFormatter $formatter;\n\n    private Currencies $currencies;\n\n    public function __construct(NumberFormatter $formatter, Currencies $currencies)\n    {\n        $this->formatter  = $formatter;\n        $this->currencies = $currencies;\n    }\n\n    public function format(Money $money): string\n    {\n        $valueBase = $money->getAmount();\n        $negative  = $valueBase[0] === '-';\n\n        if ($negative) {\n            $valueBase = substr($valueBase, 1);\n        }\n\n        $subunit     = $this->currencies->subunitFor($money->getCurrency());\n        $valueLength = strlen($valueBase);\n\n        if ($valueLength > $subunit) {\n            $formatted     = substr($valueBase, 0, $valueLength - $subunit);\n            $decimalDigits = substr($valueBase, $valueLength - $subunit);\n\n            if (strlen($decimalDigits) > 0) {\n                $formatted .= '.' . $decimalDigits;\n            }\n        } else {\n            $formatted = '0.' . str_pad('', $subunit - $valueLength, '0') . $valueBase;\n        }\n\n        if ($negative) {\n            $formatted = '-' . $formatted;\n        }\n\n        $formatted = $this->formatter->formatCurrency((float) $formatted, $money->getCurrency()->getCode());\n\n        assert(! empty($formatted));\n\n        return $formatted;\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Money.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\nuse InvalidArgumentException;\nuse JsonSerializable;\nuse Money\\Calculator\\BcMathCalculator;\n\nuse function array_fill;\nuse function array_keys;\nuse function array_map;\nuse function array_sum;\nuse function count;\nuse function filter_var;\nuse function floor;\nuse function is_int;\nuse function max;\nuse function str_pad;\nuse function strlen;\nuse function substr;\n\nuse const FILTER_VALIDATE_INT;\nuse const PHP_ROUND_HALF_DOWN;\nuse const PHP_ROUND_HALF_EVEN;\nuse const PHP_ROUND_HALF_ODD;\nuse const PHP_ROUND_HALF_UP;\n\n/**\n * Money Value Object.\n *\n * @psalm-immutable\n */\nfinal class Money implements JsonSerializable\n{\n    use MoneyFactory;\n\n    public const ROUND_HALF_UP = PHP_ROUND_HALF_UP;\n\n    public const ROUND_HALF_DOWN = PHP_ROUND_HALF_DOWN;\n\n    public const ROUND_HALF_EVEN = PHP_ROUND_HALF_EVEN;\n\n    public const ROUND_HALF_ODD = PHP_ROUND_HALF_ODD;\n\n    public const ROUND_UP = 5;\n\n    public const ROUND_DOWN = 6;\n\n    public const ROUND_HALF_POSITIVE_INFINITY = 7;\n\n    public const ROUND_HALF_NEGATIVE_INFINITY = 8;\n\n    /**\n     * Internal value.\n     *\n     * @psalm-var numeric-string\n     */\n    private string $amount;\n\n    private Currency $currency;\n\n    /**\n     * @var Calculator\n     * @psalm-var class-string<Calculator>\n     */\n    private static string $calculator = BcMathCalculator::class;\n\n    /**\n     * @param int|string $amount Amount, expressed in the smallest units of $currency (eg cents)\n     * @psalm-param int|numeric-string $amount\n     *\n     * @throws InvalidArgumentException If amount is not integer.\n     */\n    public function __construct(int|string $amount, Currency $currency)\n    {\n        $this->currency = $currency;\n\n        if (filter_var($amount, FILTER_VALIDATE_INT) === false) {\n            $numberFromString = Number::fromString((string) $amount);\n            if (! $numberFromString->isInteger()) {\n                throw new InvalidArgumentException('Amount must be an integer(ish) value');\n            }\n\n            $this->amount = $numberFromString->getIntegerPart();\n\n            return;\n        }\n\n        $this->amount = (string) $amount;\n    }\n\n    /**\n     * Checks whether a Money has the same Currency as this.\n     */\n    public function isSameCurrency(Money ...$others): bool\n    {\n        foreach ($others as $other) {\n            // Note: non-strict equality is intentional here, since `Currency` is `final` and reliable.\n            if ($this->currency != $other->currency) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Checks whether the value represented by this object equals to the other.\n     */\n    public function equals(Money $other): bool\n    {\n        // Note: non-strict equality is intentional here, since `Currency` is `final` and reliable.\n        if ($this->currency != $other->currency) {\n            return false;\n        }\n\n        if ($this->amount === $other->amount) {\n            return true;\n        }\n\n        // @TODO do we want Money instance to be byte-equivalent when trailing zeroes exist? Very expensive!\n        // Assumption: Money#equals() is called **less** than other number-based comparisons, and probably\n        // only within test suites. Therefore, using complex normalization here is acceptable waste of performance.\n        return $this->compare($other) === 0;\n    }\n\n    /**\n     * Returns an integer less than, equal to, or greater than zero\n     * if the value of this object is considered to be respectively\n     * less than, equal to, or greater than the other.\n     */\n    public function compare(Money $other): int\n    {\n        // Note: non-strict equality is intentional here, since `Currency` is `final` and reliable.\n        if ($this->currency != $other->currency) {\n            throw new InvalidArgumentException('Currencies must be identical');\n        }\n\n        return self::$calculator::compare($this->amount, $other->amount);\n    }\n\n    /**\n     * Checks whether the value represented by this object is greater than the other.\n     */\n    public function greaterThan(Money $other): bool\n    {\n        return $this->compare($other) > 0;\n    }\n\n    public function greaterThanOrEqual(Money $other): bool\n    {\n        return $this->compare($other) >= 0;\n    }\n\n    /**\n     * Checks whether the value represented by this object is less than the other.\n     */\n    public function lessThan(Money $other): bool\n    {\n        return $this->compare($other) < 0;\n    }\n\n    public function lessThanOrEqual(Money $other): bool\n    {\n        return $this->compare($other) <= 0;\n    }\n\n    /**\n     * Returns the value represented by this object.\n     *\n     * @psalm-return numeric-string\n     */\n    public function getAmount(): string\n    {\n        return $this->amount;\n    }\n\n    /**\n     * Returns the currency of this object.\n     */\n    public function getCurrency(): Currency\n    {\n        return $this->currency;\n    }\n\n    /**\n     * Returns a new Money object that represents\n     * the sum of this and an other Money object.\n     *\n     * @param Money[] $addends\n     */\n    public function add(Money ...$addends): Money\n    {\n        $amount = $this->amount;\n\n        foreach ($addends as $addend) {\n            // Note: non-strict equality is intentional here, since `Currency` is `final` and reliable.\n            if ($this->currency != $addend->currency) {\n                throw new InvalidArgumentException('Currencies must be identical');\n            }\n\n            $amount = self::$calculator::add($amount, $addend->amount);\n        }\n\n        return new self($amount, $this->currency);\n    }\n\n    /**\n     * Returns a new Money object that represents\n     * the difference of this and an other Money object.\n     *\n     * @param Money[] $subtrahends\n     *\n     * @psalm-pure\n     */\n    public function subtract(Money ...$subtrahends): Money\n    {\n        $amount = $this->amount;\n\n        foreach ($subtrahends as $subtrahend) {\n            // Note: non-strict equality is intentional here, since `Currency` is `final` and reliable.\n            if ($this->currency != $subtrahend->currency) {\n                throw new InvalidArgumentException('Currencies must be identical');\n            }\n\n            $amount = self::$calculator::subtract($amount, $subtrahend->amount);\n        }\n\n        return new self($amount, $this->currency);\n    }\n\n    /**\n     * Returns a new Money object that represents\n     * the multiplied value by the given factor.\n     *\n     * @psalm-param int|numeric-string $multiplier\n     * @psalm-param self::ROUND_*  $roundingMode\n     */\n    public function multiply(int|string $multiplier, int $roundingMode = self::ROUND_HALF_UP): Money\n    {\n        if (is_int($multiplier)) {\n            $multiplier = (string) $multiplier;\n        }\n\n        $product = $this->round(self::$calculator::multiply($this->amount, $multiplier), $roundingMode);\n\n        return new self($product, $this->currency);\n    }\n\n    /**\n     * Returns a new Money object that represents\n     * the divided value by the given factor.\n     *\n     * @psalm-param int|numeric-string $divisor\n     * @psalm-param self::ROUND_*  $roundingMode\n     */\n    public function divide(int|string $divisor, int $roundingMode = self::ROUND_HALF_UP): Money\n    {\n        if (is_int($divisor)) {\n            $divisor = (string) $divisor;\n        }\n\n        $quotient = $this->round(self::$calculator::divide($this->amount, $divisor), $roundingMode);\n\n        return new self($quotient, $this->currency);\n    }\n\n    /**\n     * Returns a new Money object that represents\n     * the remainder after dividing the value by\n     * the given factor.\n     */\n    public function mod(Money $divisor): Money\n    {\n        // Note: non-strict equality is intentional here, since `Currency` is `final` and reliable.\n        if ($this->currency != $divisor->currency) {\n            throw new InvalidArgumentException('Currencies must be identical');\n        }\n\n        return new self(self::$calculator::mod($this->amount, $divisor->amount), $this->currency);\n    }\n\n    /**\n     * Allocate the money according to a list of ratios.\n     *\n     * @psalm-param TRatios $ratios\n     *\n     * @return Money[]\n     * @psalm-return (\n     *     TRatios is list\n     *         ? non-empty-list<Money>\n     *         : non-empty-array<Money>\n     * )\n     *\n     * @template TRatios as non-empty-array<float|int>\n     */\n    public function allocate(array $ratios): array\n    {\n        $remainder = $this->amount;\n        $results   = [];\n        $total     = array_sum($ratios);\n\n        if ($total <= 0) {\n            throw new InvalidArgumentException('Cannot allocate to none, sum of ratios must be greater than zero');\n        }\n\n        foreach ($ratios as $key => $ratio) {\n            if ($ratio < 0) {\n                throw new InvalidArgumentException('Cannot allocate to none, ratio must be zero or positive');\n            }\n\n            $share         = self::$calculator::share($this->amount, (string) $ratio, (string) $total);\n            $results[$key] = new self($share, $this->currency);\n            $remainder     = self::$calculator::subtract($remainder, $share);\n        }\n\n        if (self::$calculator::compare($remainder, '0') === 0) {\n            return $results;\n        }\n\n        $amount    = $this->amount;\n        $fractions = array_map(static function (float|int $ratio) use ($total, $amount) {\n            $share = (float) $ratio / $total * (float) $amount;\n\n            return $share - floor($share);\n        }, $ratios);\n\n        while (self::$calculator::compare($remainder, '0') > 0) {\n            $index           = ! empty($fractions) ? array_keys($fractions, max($fractions))[0] : 0;\n            $results[$index] = new self(self::$calculator::add($results[$index]->amount, '1'), $results[$index]->currency);\n            $remainder       = self::$calculator::subtract($remainder, '1');\n            unset($fractions[$index]);\n        }\n\n        return $results;\n    }\n\n    /**\n     * Allocate the money among N targets.\n     *\n     * @psalm-param positive-int $n\n     *\n     * @return Money[]\n     * @psalm-return non-empty-list<Money>\n     *\n     * @throws InvalidArgumentException If number of targets is not an integer.\n     */\n    public function allocateTo(int $n): array\n    {\n        return $this->allocate(array_fill(0, $n, 1));\n    }\n\n    /**\n     * @throws InvalidArgumentException if the given $money is zero.\n     */\n    public function ratioOf(Money $money): string\n    {\n        if ($money->isZero()) {\n            throw new InvalidArgumentException('Cannot calculate a ratio of zero');\n        }\n\n        return self::$calculator::divide($this->amount, $money->amount);\n    }\n\n    /**\n     * @psalm-param numeric-string $amount\n     * @psalm-param self::ROUND_*  $roundingMode\n     *\n     * @psalm-return numeric-string\n     */\n    private function round(string $amount, int $roundingMode): string\n    {\n        if ($roundingMode === self::ROUND_UP) {\n            return self::$calculator::ceil($amount);\n        }\n\n        if ($roundingMode === self::ROUND_DOWN) {\n            return self::$calculator::floor($amount);\n        }\n\n        return self::$calculator::round($amount, $roundingMode);\n    }\n\n    /**\n     * Round to a specific unit.\n     *\n     * @psalm-param positive-int|0  $unit\n     * @psalm-param self::ROUND_* $roundingMode\n     */\n    public function roundToUnit(int $unit, int $roundingMode = self::ROUND_HALF_UP): self\n    {\n        if ($unit === 0) {\n            return $this;\n        }\n\n        $abs = self::$calculator::absolute($this->amount);\n        if (strlen($abs) < $unit) {\n            return new self('0', $this->currency);\n        }\n\n        /** @psalm-var numeric-string $toBeRounded */\n        $toBeRounded = substr($this->amount, 0, strlen($this->amount) - $unit) . '.' . substr($this->amount, $unit * -1);\n        /** @psalm-var numeric-string $result */\n        $result = self::$calculator::round($toBeRounded, $roundingMode) . str_pad('', $unit, '0');\n\n        return new self($result, $this->currency);\n    }\n\n    public function absolute(): Money\n    {\n        return new self(\n            self::$calculator::absolute($this->amount),\n            $this->currency\n        );\n    }\n\n    public function negative(): Money\n    {\n        return (new self(0, $this->currency))\n            ->subtract($this);\n    }\n\n    /**\n     * Checks if the value represented by this object is zero.\n     */\n    public function isZero(): bool\n    {\n        return self::$calculator::compare($this->amount, '0') === 0;\n    }\n\n    /**\n     * Checks if the value represented by this object is positive.\n     */\n    public function isPositive(): bool\n    {\n        return self::$calculator::compare($this->amount, '0') > 0;\n    }\n\n    /**\n     * Checks if the value represented by this object is negative.\n     */\n    public function isNegative(): bool\n    {\n        return self::$calculator::compare($this->amount, '0') < 0;\n    }\n\n    /**\n     * {@inheritdoc}\n     *\n     * @return array\n     */\n    public function jsonSerialize()\n    {\n        return [\n            'amount' => $this->amount,\n            'currency' => $this->currency->jsonSerialize(),\n        ];\n    }\n\n    /**\n     * @param Money $first\n     * @param Money ...$collection\n     *\n     * @psalm-pure\n     */\n    public static function min(self $first, self ...$collection): Money\n    {\n        $min = $first;\n\n        foreach ($collection as $money) {\n            if (! $money->lessThan($min)) {\n                continue;\n            }\n\n            $min = $money;\n        }\n\n        return $min;\n    }\n\n    /**\n     * @param Money $first\n     * @param Money ...$collection\n     *\n     * @psalm-pure\n     */\n    public static function max(self $first, self ...$collection): Money\n    {\n        $max = $first;\n\n        foreach ($collection as $money) {\n            if (! $money->greaterThan($max)) {\n                continue;\n            }\n\n            $max = $money;\n        }\n\n        return $max;\n    }\n\n    /** @psalm-pure */\n    public static function sum(self $first, self ...$collection): Money\n    {\n        return $first->add(...$collection);\n    }\n\n    /** @psalm-pure */\n    public static function avg(self $first, self ...$collection): Money\n    {\n        return $first->add(...$collection)->divide((string) (count($collection) + 1));\n    }\n\n    /** @psalm-param class-string<Calculator> $calculator */\n    public static function registerCalculator(string $calculator): void\n    {\n        self::$calculator = $calculator;\n    }\n\n    /** @psalm-return class-string<Calculator> */\n    public static function getCalculator(): string\n    {\n        return self::$calculator;\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/MoneyFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\nuse InvalidArgumentException;\n\n/**\n * This is a generated file. Do not edit it manually!\n *\n * @method static Money AED(numeric-string|int $amount)\n * @method static Money ALL(numeric-string|int $amount)\n * @method static Money AMD(numeric-string|int $amount)\n * @method static Money ANG(numeric-string|int $amount)\n * @method static Money AOA(numeric-string|int $amount)\n * @method static Money ARS(numeric-string|int $amount)\n * @method static Money AUD(numeric-string|int $amount)\n * @method static Money AWG(numeric-string|int $amount)\n * @method static Money AZN(numeric-string|int $amount)\n * @method static Money BAM(numeric-string|int $amount)\n * @method static Money BBD(numeric-string|int $amount)\n * @method static Money BDT(numeric-string|int $amount)\n * @method static Money BGN(numeric-string|int $amount)\n * @method static Money BHD(numeric-string|int $amount)\n * @method static Money BIF(numeric-string|int $amount)\n * @method static Money BMD(numeric-string|int $amount)\n * @method static Money BND(numeric-string|int $amount)\n * @method static Money BOB(numeric-string|int $amount)\n * @method static Money BOV(numeric-string|int $amount)\n * @method static Money BRL(numeric-string|int $amount)\n * @method static Money BSD(numeric-string|int $amount)\n * @method static Money BTN(numeric-string|int $amount)\n * @method static Money BWP(numeric-string|int $amount)\n * @method static Money BYN(numeric-string|int $amount)\n * @method static Money BZD(numeric-string|int $amount)\n * @method static Money CAD(numeric-string|int $amount)\n * @method static Money CDF(numeric-string|int $amount)\n * @method static Money CHE(numeric-string|int $amount)\n * @method static Money CHF(numeric-string|int $amount)\n * @method static Money CHW(numeric-string|int $amount)\n * @method static Money CLF(numeric-string|int $amount)\n * @method static Money CLP(numeric-string|int $amount)\n * @method static Money CNY(numeric-string|int $amount)\n * @method static Money COP(numeric-string|int $amount)\n * @method static Money COU(numeric-string|int $amount)\n * @method static Money CRC(numeric-string|int $amount)\n * @method static Money CUC(numeric-string|int $amount)\n * @method static Money CUP(numeric-string|int $amount)\n * @method static Money CVE(numeric-string|int $amount)\n * @method static Money CZK(numeric-string|int $amount)\n * @method static Money DJF(numeric-string|int $amount)\n * @method static Money DKK(numeric-string|int $amount)\n * @method static Money DOP(numeric-string|int $amount)\n * @method static Money DZD(numeric-string|int $amount)\n * @method static Money EGP(numeric-string|int $amount)\n * @method static Money ERN(numeric-string|int $amount)\n * @method static Money ETB(numeric-string|int $amount)\n * @method static Money EUR(numeric-string|int $amount)\n * @method static Money FJD(numeric-string|int $amount)\n * @method static Money FKP(numeric-string|int $amount)\n * @method static Money GBP(numeric-string|int $amount)\n * @method static Money GEL(numeric-string|int $amount)\n * @method static Money GHS(numeric-string|int $amount)\n * @method static Money GIP(numeric-string|int $amount)\n * @method static Money GMD(numeric-string|int $amount)\n * @method static Money GNF(numeric-string|int $amount)\n * @method static Money GTQ(numeric-string|int $amount)\n * @method static Money GYD(numeric-string|int $amount)\n * @method static Money HKD(numeric-string|int $amount)\n * @method static Money HNL(numeric-string|int $amount)\n * @method static Money HRK(numeric-string|int $amount)\n * @method static Money HTG(numeric-string|int $amount)\n * @method static Money HUF(numeric-string|int $amount)\n * @method static Money IDR(numeric-string|int $amount)\n * @method static Money ILS(numeric-string|int $amount)\n * @method static Money INR(numeric-string|int $amount)\n * @method static Money IQD(numeric-string|int $amount)\n * @method static Money IRR(numeric-string|int $amount)\n * @method static Money ISK(numeric-string|int $amount)\n * @method static Money JMD(numeric-string|int $amount)\n * @method static Money JOD(numeric-string|int $amount)\n * @method static Money JPY(numeric-string|int $amount)\n * @method static Money KES(numeric-string|int $amount)\n * @method static Money KGS(numeric-string|int $amount)\n * @method static Money KHR(numeric-string|int $amount)\n * @method static Money KMF(numeric-string|int $amount)\n * @method static Money KPW(numeric-string|int $amount)\n * @method static Money KRW(numeric-string|int $amount)\n * @method static Money KWD(numeric-string|int $amount)\n * @method static Money KYD(numeric-string|int $amount)\n * @method static Money KZT(numeric-string|int $amount)\n * @method static Money LAK(numeric-string|int $amount)\n * @method static Money LBP(numeric-string|int $amount)\n * @method static Money LKR(numeric-string|int $amount)\n * @method static Money LRD(numeric-string|int $amount)\n * @method static Money LSL(numeric-string|int $amount)\n * @method static Money LYD(numeric-string|int $amount)\n * @method static Money MAD(numeric-string|int $amount)\n * @method static Money MDL(numeric-string|int $amount)\n * @method static Money MGA(numeric-string|int $amount)\n * @method static Money MKD(numeric-string|int $amount)\n * @method static Money MMK(numeric-string|int $amount)\n * @method static Money MNT(numeric-string|int $amount)\n * @method static Money MOP(numeric-string|int $amount)\n * @method static Money MRU(numeric-string|int $amount)\n * @method static Money MUR(numeric-string|int $amount)\n * @method static Money MVR(numeric-string|int $amount)\n * @method static Money MWK(numeric-string|int $amount)\n * @method static Money MXN(numeric-string|int $amount)\n * @method static Money MXV(numeric-string|int $amount)\n * @method static Money MYR(numeric-string|int $amount)\n * @method static Money MZN(numeric-string|int $amount)\n * @method static Money NAD(numeric-string|int $amount)\n * @method static Money NGN(numeric-string|int $amount)\n * @method static Money NIO(numeric-string|int $amount)\n * @method static Money NOK(numeric-string|int $amount)\n * @method static Money NPR(numeric-string|int $amount)\n * @method static Money NZD(numeric-string|int $amount)\n * @method static Money OMR(numeric-string|int $amount)\n * @method static Money PAB(numeric-string|int $amount)\n * @method static Money PEN(numeric-string|int $amount)\n * @method static Money PGK(numeric-string|int $amount)\n * @method static Money PHP(numeric-string|int $amount)\n * @method static Money PKR(numeric-string|int $amount)\n * @method static Money PLN(numeric-string|int $amount)\n * @method static Money PYG(numeric-string|int $amount)\n * @method static Money QAR(numeric-string|int $amount)\n * @method static Money RON(numeric-string|int $amount)\n * @method static Money RSD(numeric-string|int $amount)\n * @method static Money RUB(numeric-string|int $amount)\n * @method static Money RWF(numeric-string|int $amount)\n * @method static Money SAR(numeric-string|int $amount)\n * @method static Money SBD(numeric-string|int $amount)\n * @method static Money SCR(numeric-string|int $amount)\n * @method static Money SDG(numeric-string|int $amount)\n * @method static Money SEK(numeric-string|int $amount)\n * @method static Money SGD(numeric-string|int $amount)\n * @method static Money SHP(numeric-string|int $amount)\n * @method static Money SLL(numeric-string|int $amount)\n * @method static Money SOS(numeric-string|int $amount)\n * @method static Money SRD(numeric-string|int $amount)\n * @method static Money SSP(numeric-string|int $amount)\n * @method static Money STN(numeric-string|int $amount)\n * @method static Money SVC(numeric-string|int $amount)\n * @method static Money SYP(numeric-string|int $amount)\n * @method static Money SZL(numeric-string|int $amount)\n * @method static Money THB(numeric-string|int $amount)\n * @method static Money TJS(numeric-string|int $amount)\n * @method static Money TMT(numeric-string|int $amount)\n * @method static Money TND(numeric-string|int $amount)\n * @method static Money TOP(numeric-string|int $amount)\n * @method static Money TRY(numeric-string|int $amount)\n * @method static Money TTD(numeric-string|int $amount)\n * @method static Money TWD(numeric-string|int $amount)\n * @method static Money TZS(numeric-string|int $amount)\n * @method static Money UAH(numeric-string|int $amount)\n * @method static Money UGX(numeric-string|int $amount)\n * @method static Money USD(numeric-string|int $amount)\n * @method static Money USN(numeric-string|int $amount)\n * @method static Money UYI(numeric-string|int $amount)\n * @method static Money UYU(numeric-string|int $amount)\n * @method static Money UYW(numeric-string|int $amount)\n * @method static Money UZS(numeric-string|int $amount)\n * @method static Money VES(numeric-string|int $amount)\n * @method static Money VND(numeric-string|int $amount)\n * @method static Money VUV(numeric-string|int $amount)\n * @method static Money WST(numeric-string|int $amount)\n * @method static Money XAF(numeric-string|int $amount)\n * @method static Money XAG(numeric-string|int $amount)\n * @method static Money XAU(numeric-string|int $amount)\n * @method static Money XBA(numeric-string|int $amount)\n * @method static Money XBB(numeric-string|int $amount)\n * @method static Money XBC(numeric-string|int $amount)\n * @method static Money XBD(numeric-string|int $amount)\n * @method static Money XBT(numeric-string|int $amount)\n * @method static Money XCD(numeric-string|int $amount)\n * @method static Money XDR(numeric-string|int $amount)\n * @method static Money XOF(numeric-string|int $amount)\n * @method static Money XPD(numeric-string|int $amount)\n * @method static Money XPF(numeric-string|int $amount)\n * @method static Money XPT(numeric-string|int $amount)\n * @method static Money XSU(numeric-string|int $amount)\n * @method static Money XTS(numeric-string|int $amount)\n * @method static Money XUA(numeric-string|int $amount)\n * @method static Money XXX(numeric-string|int $amount)\n * @method static Money YER(numeric-string|int $amount)\n * @method static Money ZAR(numeric-string|int $amount)\n * @method static Money ZMW(numeric-string|int $amount)\n * @method static Money ZWL(numeric-string|int $amount)\n * @psalm-immutable\n */\ntrait MoneyFactory\n{\n    /**\n     * Convenience factory method for a Money object.\n     *\n     * <code>\n     * $fiveDollar = Money::USD(500);\n     * </code>\n     *\n     * @param array $arguments\n     * @psalm-param non-empty-string          $method\n     * @psalm-param array{numeric-string|int} $arguments\n     *\n     * @throws InvalidArgumentException If amount is not integer(ish).\n     *\n     * @psalm-pure\n     */\n    public static function __callStatic(string $method, array $arguments): Money\n    {\n        return new Money($arguments[0], new Currency($method));\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/MoneyFormatter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\n/**\n * Formats Money objects.\n */\ninterface MoneyFormatter\n{\n    /**\n     * Formats a Money object as string.\n     *\n     * @psalm-return non-empty-string\n     *\n     * Exception\\FormatterException\n     */\n    public function format(Money $money): string;\n}\n"
  },
  {
    "path": "tests/test_code/php/money/MoneyParser.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\n/**\n * Parses a string into a Money object.\n */\ninterface MoneyParser\n{\n    /**\n     * Parses a string into a Money object (including currency).\n     *\n     * @throws Exception\\ParserException\n     */\n    public function parse(string $money, Currency|null $fallbackCurrency = null): Money;\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Number.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money;\n\nuse InvalidArgumentException;\n\nuse function abs;\nuse function explode;\nuse function is_int;\nuse function ltrim;\nuse function min;\nuse function rtrim;\nuse function sprintf;\nuse function str_pad;\nuse function strlen;\nuse function substr;\n\n/**\n * Represents a numeric value.\n *\n * @internal this is an internal utility of the library, and may vary at any time. It is mostly used to internally validate\n *           that a number is represented at digits, but by improving type system integration, we may be able to completely\n *           get rid of it.\n *\n * @psalm-immutable\n */\nfinal class Number\n{\n    /** @psalm-var numeric-string */\n    private string $integerPart;\n\n    /** @psalm-var numeric-string|'' */\n    private string $fractionalPart;\n    private const NUMBERS = [0 => 1, 1 => 1, 2 => 1, 3 => 1, 4 => 1, 5 => 1, 6 => 1, 7 => 1, 8 => 1, 9 => 1];\n\n    public function __construct(string $integerPart, string $fractionalPart = '')\n    {\n        if ($integerPart === '' && $fractionalPart === '') {\n            throw new InvalidArgumentException('Empty number is invalid');\n        }\n\n        $this->integerPart    = self::parseIntegerPart($integerPart);\n        $this->fractionalPart = self::parseFractionalPart($fractionalPart);\n    }\n\n    /** @psalm-pure */\n    public static function fromString(string $number): self\n    {\n        $portions = explode('.', $number, 2);\n\n        return new self(\n            $portions[0],\n            rtrim($portions[1] ?? '', '0')\n        );\n    }\n\n    /** @psalm-pure */\n    public static function fromFloat(float $number): self\n    {\n        return self::fromString(sprintf('%.14F', $number));\n    }\n\n    /** @psalm-pure */\n    public static function fromNumber(int|string $number): self\n    {\n        if (is_int($number)) {\n            return new self((string) $number);\n        }\n\n        return self::fromString($number);\n    }\n\n    public function isDecimal(): bool\n    {\n        return $this->fractionalPart !== '';\n    }\n\n    public function isInteger(): bool\n    {\n        return $this->fractionalPart === '';\n    }\n\n    public function isHalf(): bool\n    {\n        return $this->fractionalPart === '5';\n    }\n\n    public function isCurrentEven(): bool\n    {\n        $lastIntegerPartNumber = (int) $this->integerPart[strlen($this->integerPart) - 1];\n\n        return $lastIntegerPartNumber % 2 === 0;\n    }\n\n    public function isCloserToNext(): bool\n    {\n        if ($this->fractionalPart === '') {\n            return false;\n        }\n\n        return $this->fractionalPart[0] >= 5;\n    }\n\n    /** @psalm-return numeric-string */\n    public function __toString(): string\n    {\n        if ($this->fractionalPart === '') {\n            return $this->integerPart;\n        }\n\n        /** @psalm-suppress LessSpecificReturnStatement this operation is guaranteed to pruduce a numeric-string, but inference can't understand it */\n        return $this->integerPart . '.' . $this->fractionalPart;\n    }\n\n    public function isNegative(): bool\n    {\n        return $this->integerPart[0] === '-';\n    }\n\n    /** @psalm-return numeric-string */\n    public function getIntegerPart(): string\n    {\n        return $this->integerPart;\n    }\n\n    /** @psalm-return numeric-string|'' */\n    public function getFractionalPart(): string\n    {\n        return $this->fractionalPart;\n    }\n\n    /** @psalm-return numeric-string */\n    public function getIntegerRoundingMultiplier(): string\n    {\n        if ($this->integerPart[0] === '-') {\n            return '-1';\n        }\n\n        return '1';\n    }\n\n    public function base10(int $number): self\n    {\n        if ($this->integerPart === '0' && ! $this->fractionalPart) {\n            return $this;\n        }\n\n        $sign        = '';\n        $integerPart = $this->integerPart;\n\n        if ($integerPart[0] === '-') {\n            $sign        = '-';\n            $integerPart = substr($integerPart, 1);\n        }\n\n        if ($number >= 0) {\n            $integerPart       = ltrim($integerPart, '0');\n            $lengthIntegerPart = strlen($integerPart);\n            $integers          = $lengthIntegerPart - min($number, $lengthIntegerPart);\n            $zeroPad           = $number - min($number, $lengthIntegerPart);\n\n            return new self(\n                $sign . substr($integerPart, 0, $integers),\n                rtrim(str_pad('', $zeroPad, '0') . substr($integerPart, $integers) . $this->fractionalPart, '0')\n            );\n        }\n\n        $number               = abs($number);\n        $lengthFractionalPart = strlen($this->fractionalPart);\n        $fractions            = $lengthFractionalPart - min($number, $lengthFractionalPart);\n        $zeroPad              = $number - min($number, $lengthFractionalPart);\n\n        return new self(\n            $sign . ltrim($integerPart . substr($this->fractionalPart, 0, $lengthFractionalPart - $fractions) . str_pad('', $zeroPad, '0'), '0'),\n            substr($this->fractionalPart, $lengthFractionalPart - $fractions)\n        );\n    }\n\n    /**\n     * @psalm-return numeric-string\n     *\n     * @psalm-pure\n     *\n     * @psalm-suppress MoreSpecificReturnType      this operation is guaranteed to pruduce a numeric-string, but inference can't understand it\n     * @psalm-suppress LessSpecificReturnStatement this operation is guaranteed to pruduce a numeric-string, but inference can't understand it\n     */\n    private static function parseIntegerPart(string $number): string\n    {\n        if ($number === '' || $number === '0') {\n            return '0';\n        }\n\n        if ($number === '-') {\n            return '-0';\n        }\n\n        // Happy path performance optimization: number can be used as-is if it is within\n        // the platform's integer capabilities.\n        if ($number === (string) (int) $number) {\n            return $number;\n        }\n\n        $nonZero = false;\n\n        for ($position = 0, $characters = strlen($number); $position < $characters; ++$position) {\n            $digit = $number[$position];\n\n            /** @psalm-suppress InvalidArrayOffset we are, on purpose, checking if the digit is valid against a fixed structure */\n            if (! isset(self::NUMBERS[$digit]) && ! ($position === 0 && $digit === '-')) {\n                throw new InvalidArgumentException(sprintf('Invalid integer part %1$s. Invalid digit %2$s found', $number, $digit));\n            }\n\n            if ($nonZero === false && $digit === '0') {\n                throw new InvalidArgumentException('Leading zeros are not allowed');\n            }\n\n            $nonZero = true;\n        }\n\n        return $number;\n    }\n\n    /**\n     * @psalm-return numeric-string|''\n     *\n     * @psalm-pure\n     */\n    private static function parseFractionalPart(string $number): string\n    {\n        if ($number === '') {\n            return $number;\n        }\n\n        $intFraction = (int) $number;\n\n        // Happy path performance optimization: number can be used as-is if it is within\n        // the platform's integer capabilities, and it starts with zeroes only.\n        if ($intFraction > 0 && ltrim($number, '0') === (string) $intFraction) {\n            return $number;\n        }\n\n        for ($position = 0, $characters = strlen($number); $position < $characters; ++$position) {\n            $digit = $number[$position];\n\n            /** @psalm-suppress InvalidArrayOffset we are, on purpose, checking if the digit is valid against a fixed structure */\n            if (! isset(self::NUMBERS[$digit])) {\n                throw new InvalidArgumentException(sprintf('Invalid fractional part %1$s. Invalid digit %2$s found', $number, $digit));\n            }\n        }\n\n        return $number;\n    }\n\n    /**\n     * @psalm-pure\n     * @psalm-suppress InvalidOperand string and integers get concatenated here - that is by design, as we're computing remainders\n     */\n    public static function roundMoneyValue(string $moneyValue, int $targetDigits, int $havingDigits): string\n    {\n        $valueLength = strlen($moneyValue);\n        $shouldRound = $targetDigits < $havingDigits && $valueLength - $havingDigits + $targetDigits > 0;\n\n        if ($shouldRound && $moneyValue[$valueLength - $havingDigits + $targetDigits] >= 5) {\n            $position = $valueLength - $havingDigits + $targetDigits;\n            /** @psalm-var positive-int|0 $addend */\n            $addend = 1;\n\n            while ($position > 0) {\n                $newValue = (string) ((int) $moneyValue[$position - 1] + $addend);\n\n                if ($newValue >= 10) {\n                    $moneyValue[$position - 1] = $newValue[1];\n                    /** @psalm-var numeric-string $addend */\n                    $addend = $newValue[0];\n                    --$position;\n                    if ($position === 0) {\n                        $moneyValue = $addend . $moneyValue;\n                    }\n                } else {\n                    if ($moneyValue[$position - 1] === '-') {\n                        $moneyValue[$position - 1] = $newValue[0];\n                        $moneyValue                = '-' . $moneyValue;\n                    } else {\n                        $moneyValue[$position - 1] = $newValue[0];\n                    }\n\n                    break;\n                }\n            }\n        }\n\n        return $moneyValue;\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/PHPUnit/Comparator.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\PHPUnit;\n\nuse Money\\Currencies\\AggregateCurrencies;\nuse Money\\Currencies\\BitcoinCurrencies;\nuse Money\\Currencies\\ISOCurrencies;\nuse Money\\Formatter\\IntlMoneyFormatter;\nuse Money\\Money;\nuse NumberFormatter;\nuse SebastianBergmann\\Comparator\\ComparisonFailure;\n\nuse function assert;\n\n/**\n * The comparator is for comparing Money objects in PHPUnit tests.\n *\n * Add this to your bootstrap file:\n *\n * \\SebastianBergmann\\Comparator\\Factory::getInstance()->register(new \\Money\\PHPUnit\\Comparator());\n *\n * @internal do not use within your sources: this comparator is only to be used within the test suite of this library\n *\n * @psalm-suppress PropertyNotSetInConstructor the parent implementation includes factories that cannot be initialized here\n */\nfinal class Comparator extends \\SebastianBergmann\\Comparator\\Comparator\n{\n    private IntlMoneyFormatter $formatter;\n\n    public function __construct()\n    {\n        parent::__construct();\n\n        $currencies = new AggregateCurrencies([\n            new ISOCurrencies(),\n            new BitcoinCurrencies(),\n        ]);\n\n        $numberFormatter = new NumberFormatter('en_US', NumberFormatter::CURRENCY);\n        $this->formatter = new IntlMoneyFormatter($numberFormatter, $currencies);\n    }\n\n    /** {@inheritDoc} */\n    public function accepts($expected, $actual)\n    {\n        return $expected instanceof Money && $actual instanceof Money;\n    }\n\n    /** {@inheritDoc} */\n    public function assertEquals(\n        $expected,\n        $actual,\n        $delta = 0.0,\n        $canonicalize = false,\n        $ignoreCase = false\n    ): void {\n        assert($expected instanceof Money);\n        assert($actual instanceof Money);\n\n        if (! $expected->equals($actual)) {\n            throw new ComparisonFailure($expected, $actual, $this->formatter->format($expected), $this->formatter->format($actual), false, 'Failed asserting that two Money objects are equal.');\n        }\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Parser/AggregateMoneyParser.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Parser;\n\nuse Money\\Currency;\nuse Money\\Exception;\nuse Money\\Money;\nuse Money\\MoneyParser;\n\nuse function sprintf;\n\n/**\n * Parses a string into a Money object using other parsers.\n */\nfinal class AggregateMoneyParser implements MoneyParser\n{\n    /**\n     * @var MoneyParser[]\n     * @psalm-var non-empty-array<MoneyParser>\n     */\n    private array $parsers;\n\n    /**\n     * @param MoneyParser[] $parsers\n     * @psalm-param non-empty-array<MoneyParser> $parsers\n     */\n    public function __construct(array $parsers)\n    {\n        $this->parsers = $parsers;\n    }\n\n    public function parse(string $money, Currency|null $fallbackCurrency = null): Money\n    {\n        foreach ($this->parsers as $parser) {\n            try {\n                return $parser->parse($money, $fallbackCurrency);\n            } catch (Exception\\ParserException $e) {\n            }\n        }\n\n        throw new Exception\\ParserException(sprintf('Unable to parse %s', $money));\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Parser/BitcoinMoneyParser.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Parser;\n\nuse Money\\Currencies\\BitcoinCurrencies;\nuse Money\\Currency;\nuse Money\\Exception\\ParserException;\nuse Money\\Money;\nuse Money\\MoneyParser;\n\nuse function ltrim;\nuse function rtrim;\nuse function str_pad;\nuse function str_replace;\nuse function strlen;\nuse function strpos;\nuse function substr;\n\n/**\n * Parses Bitcoin currency to Money.\n */\nfinal class BitcoinMoneyParser implements MoneyParser\n{\n    private int $fractionDigits;\n\n    public function __construct(int $fractionDigits)\n    {\n        $this->fractionDigits = $fractionDigits;\n    }\n\n    public function parse(string $money, Currency|null $fallbackCurrency = null): Money\n    {\n        if (strpos($money, BitcoinCurrencies::SYMBOL) === false) {\n            throw new ParserException('Value cannot be parsed as Bitcoin');\n        }\n\n        $currency         = $fallbackCurrency ?? new Currency(BitcoinCurrencies::CODE);\n        $decimal          = str_replace(BitcoinCurrencies::SYMBOL, '', $money);\n        $decimalSeparator = strpos($decimal, '.');\n\n        if ($decimalSeparator !== false) {\n            $decimal       = rtrim($decimal, '0');\n            $lengthDecimal = strlen($decimal);\n            $decimal       = str_replace('.', '', $decimal);\n            $decimal      .= str_pad('', ($lengthDecimal - $decimalSeparator - $this->fractionDigits - 1) * -1, '0');\n        } else {\n            $decimal .= str_pad('', $this->fractionDigits, '0');\n        }\n\n        if (substr($decimal, 0, 1) === '-') {\n            $decimal = '-' . ltrim(substr($decimal, 1), '0');\n        } else {\n            $decimal = ltrim($decimal, '0');\n        }\n\n        if ($decimal === '') {\n            $decimal = '0';\n        }\n\n        /** @psalm-var numeric-string $decimal */\n        return new Money($decimal, $currency);\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Parser/DecimalMoneyParser.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Parser;\n\nuse Money\\Currencies;\nuse Money\\Currency;\nuse Money\\Exception\\ParserException;\nuse Money\\Money;\nuse Money\\MoneyParser;\nuse Money\\Number;\n\nuse function ltrim;\nuse function preg_match;\nuse function sprintf;\nuse function str_pad;\nuse function strlen;\nuse function substr;\nuse function trim;\n\n/**\n * Parses a decimal string into a Money object.\n */\nfinal class DecimalMoneyParser implements MoneyParser\n{\n    public const DECIMAL_PATTERN = '/^(?P<sign>-)?(?P<digits>0|[1-9]\\d*)?\\.?(?P<fraction>\\d+)?$/';\n\n    private Currencies $currencies;\n\n    public function __construct(Currencies $currencies)\n    {\n        $this->currencies = $currencies;\n    }\n\n    public function parse(string $money, Currency|null $fallbackCurrency = null): Money\n    {\n        if ($fallbackCurrency === null) {\n            throw new ParserException('DecimalMoneyParser cannot parse currency symbols. Use forceCurrency argument');\n        }\n\n        $decimal = trim($money);\n\n        if ($decimal === '') {\n            return new Money(0, $fallbackCurrency);\n        }\n\n        if (! preg_match(self::DECIMAL_PATTERN, $decimal, $matches) || ! isset($matches['digits'])) {\n            throw new ParserException(sprintf('Cannot parse \"%s\" to Money.', $decimal));\n        }\n\n        $negative = isset($matches['sign']) && $matches['sign'] === '-';\n\n        $decimal = $matches['digits'];\n\n        if ($negative) {\n            $decimal = '-' . $decimal;\n        }\n\n        $subunit = $this->currencies->subunitFor($fallbackCurrency);\n\n        if (isset($matches['fraction'])) {\n            $fractionDigits = strlen($matches['fraction']);\n            $decimal       .= $matches['fraction'];\n            $decimal        = Number::roundMoneyValue($decimal, $subunit, $fractionDigits);\n\n            if ($fractionDigits > $subunit) {\n                $decimal = substr($decimal, 0, $subunit - $fractionDigits);\n            } elseif ($fractionDigits < $subunit) {\n                $decimal .= str_pad('', $subunit - $fractionDigits, '0');\n            }\n        } else {\n            $decimal .= str_pad('', $subunit, '0');\n        }\n\n        if ($negative) {\n            $decimal = '-' . ltrim(substr($decimal, 1), '0');\n        } else {\n            $decimal = ltrim($decimal, '0');\n        }\n\n        if ($decimal === '' || $decimal === '-') {\n            $decimal = '0';\n        }\n\n        /** @psalm-var numeric-string $decimal */\n        return new Money($decimal, $fallbackCurrency);\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Parser/IntlLocalizedDecimalParser.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Parser;\n\nuse Money\\Currencies;\nuse Money\\Currency;\nuse Money\\Exception\\ParserException;\nuse Money\\Money;\nuse Money\\MoneyParser;\nuse Money\\Number;\nuse NumberFormatter;\n\nuse function ltrim;\nuse function str_pad;\nuse function str_replace;\nuse function strlen;\nuse function strpos;\nuse function substr;\n\n/**\n * Parses a string into a Money object using intl extension.\n */\nfinal class IntlLocalizedDecimalParser implements MoneyParser\n{\n    private NumberFormatter $formatter;\n\n    private Currencies $currencies;\n\n    public function __construct(NumberFormatter $formatter, Currencies $currencies)\n    {\n        $this->formatter  = $formatter;\n        $this->currencies = $currencies;\n    }\n\n    public function parse(string $money, Currency|null $fallbackCurrency = null): Money\n    {\n        if ($fallbackCurrency === null) {\n            throw new ParserException('IntlLocalizedDecimalParser cannot parse currency symbols. Use forceCurrency argument');\n        }\n\n        $decimal = $this->formatter->parse($money);\n\n        if ($decimal === false) {\n            throw new ParserException('Cannot parse ' . $money . ' to Money. ' . $this->formatter->getErrorMessage());\n        }\n\n        $decimal         = (string) $decimal;\n        $subunit         = $this->currencies->subunitFor($fallbackCurrency);\n        $decimalPosition = strpos($decimal, '.');\n\n        if ($decimalPosition !== false) {\n            $decimalLength  = strlen($decimal);\n            $fractionDigits = $decimalLength - $decimalPosition - 1;\n            $decimal        = str_replace('.', '', $decimal);\n            $decimal        = Number::roundMoneyValue($decimal, $subunit, $fractionDigits);\n\n            if ($fractionDigits > $subunit) {\n                $decimal = substr($decimal, 0, $decimalPosition + $subunit);\n            } elseif ($fractionDigits < $subunit) {\n                $decimal .= str_pad('', $subunit - $fractionDigits, '0');\n            }\n        } else {\n            $decimal .= str_pad('', $subunit, '0');\n        }\n\n        if ($decimal[0] === '-') {\n            $decimal = '-' . ltrim(substr($decimal, 1), '0');\n        } else {\n            $decimal = ltrim($decimal, '0');\n        }\n\n        if ($decimal === '') {\n            $decimal = '0';\n        }\n\n        /** @psalm-var numeric-string $decimal */\n        return new Money($decimal, $fallbackCurrency);\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/money/Parser/IntlMoneyParser.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Money\\Parser;\n\nuse Money\\Currencies;\nuse Money\\Currency;\nuse Money\\Exception\\ParserException;\nuse Money\\Money;\nuse Money\\MoneyParser;\nuse Money\\Number;\nuse NumberFormatter;\n\nuse function assert;\nuse function ltrim;\nuse function str_pad;\nuse function str_replace;\nuse function strlen;\nuse function strpos;\nuse function substr;\n\n/**\n * Parses a string into a Money object using intl extension.\n */\nfinal class IntlMoneyParser implements MoneyParser\n{\n    private NumberFormatter $formatter;\n\n    private Currencies $currencies;\n\n    public function __construct(NumberFormatter $formatter, Currencies $currencies)\n    {\n        $this->formatter  = $formatter;\n        $this->currencies = $currencies;\n    }\n\n    public function parse(string $money, Currency|null $fallbackCurrency = null): Money\n    {\n        $currency = null;\n        $decimal  = $this->formatter->parseCurrency($money, $currency);\n\n        if ($decimal === false) {\n            throw new ParserException('Cannot parse ' . $money . ' to Money. ' . $this->formatter->getErrorMessage());\n        }\n\n        if ($fallbackCurrency === null) {\n            assert(! empty($currency));\n\n            $fallbackCurrency = new Currency($currency);\n        }\n\n        $decimal         = (string) $decimal;\n        $subunit         = $this->currencies->subunitFor($fallbackCurrency);\n        $decimalPosition = strpos($decimal, '.');\n\n        if ($decimalPosition !== false) {\n            $decimalLength  = strlen($decimal);\n            $fractionDigits = $decimalLength - $decimalPosition - 1;\n            $decimal        = str_replace('.', '', $decimal);\n            $decimal        = Number::roundMoneyValue($decimal, $subunit, $fractionDigits);\n\n            if ($fractionDigits > $subunit) {\n                $decimal = substr($decimal, 0, $decimalPosition + $subunit);\n            } elseif ($fractionDigits < $subunit) {\n                $decimal .= str_pad('', $subunit - $fractionDigits, '0');\n            }\n        } else {\n            $decimal .= str_pad('', $subunit, '0');\n        }\n\n        if ($decimal[0] === '-') {\n            $decimal = '-' . ltrim(substr($decimal, 1), '0');\n        } else {\n            $decimal = ltrim($decimal, '0');\n        }\n\n        if ($decimal === '') {\n            $decimal = '0';\n        }\n\n        /** @psalm-var numeric-string $decimal */\n        return new Money($decimal, $fallbackCurrency);\n    }\n}\n"
  },
  {
    "path": "tests/test_code/php/namespace_a/namespace_a.php",
    "content": "<?php\nnamespace NS;\n\nfunction namespaced_func() {\n    echo \"namespaced_func\";\n}\n\nclass Namespaced_cls {\n    function __construct() {\n        echo \"__construct\";\n    }\n    function instance_method() {\n        echo \"instance_method\";\n    }\n}\n\nnamespaced_func();\n$a = new Namespaced_cls();\n$a->instance_method();\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/namespace_b/namespace_b1.php",
    "content": "<?php\n\nrequire_once(\"namespace_b2.php\");\nuse foo;\nuse foo as feline;\n\n// namespaced_func();\n// $a = new Namespaced_cls();\n// $a->instance_method();\n\necho \\foo\\Cat::meows(), \"<br />\\n\";\necho \\feline\\Cat::says(), \"<br />\\n\";\n\nuse animate;\necho \\animate\\Animal::meows(), \"<br />\\n\";\n\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/namespace_b/namespace_b2.php",
    "content": "<?php\n// namespace NSA {\n\n//     function namespaced_func() {\n//         echo \"namespaced_func\";\n//     }\n\n//     class Namespaced_cls {\n//         function __construct() {\n//             echo \"__construct\";\n//         }\n//         function instance_method() {\n//             echo \"instance_method\";\n//         }\n//     }\n// }\n\n// namespace NSB {\n\n//     function namespaced_func() {\n//         echo \"namespaced_func\";\n//     }\n\n//     class Namespaced_cls {\n//         function __construct() {\n//             echo \"__construct\";\n//         }\n//         function instance_method() {\n//             echo \"instance_method\";\n//         }\n//     }\n// }\n\nnamespace animate {\n    class Animal {\n        static function breathes() {echo 'air';}\n        static function meows() {echo 'meoow2';}\n    }\n}\n\nnamespace bar {\n    class Dog {\n        static function says() {echo 'ruff';}\n    }\n}\n\nnamespace foo {\n    use animate;\n    class Cat extends animate\\Animal {\n        static function says() {echo 'meoow';}\n        static function meows() {echo 'meoow2';}\n\n    }\n}\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/namespace_c/namespace_c1.php",
    "content": "<?php\nrequire_once(\"namespace_c2.php\");\n\nuse function Outer\\Inner\\speak as talk;\nuse Outer\\Inner\\Cat as Kitty;\n\ntalk();\nKitty::meow();\n\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/namespace_c/namespace_c2.php",
    "content": "<?php\n\nnamespace Outer\\Inner;\n\nfunction speak() {\n    echo \"global speak\";\n}\n\nclass Cat {\n    static function meow() {\n        echo \"cat meow\";\n    }\n    static function speak() {\n        echo \"cat speak\";\n    }\n}\n\nclass Dog {\n    static function meow() {\n        echo \"dog meow\";\n    }\n    static function speak() {\n        echo \"dog speak\";\n    }\n}\n\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/nested/nested.php",
    "content": "<?php\n\n// Nested functions in PHP are global so should be placed on the same scope\n\nfunction outer() {\n    echo \"outer\";\n    function inner() {\n        echo \"inner\";\n    }\n}\n\nouter();\ninner();\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/nested_calls/nested_calls.php",
    "content": "<?php\n\nfunction x_() {\n    (new Cls()).func2();\n}\nfunction y_() {}\nfunction z_() {}\n\nfunction func() {}\nfunction func2() {}\n\n\nclass Cls {\n    static function a($ret) {\n        echo $ret;\n    }\n    static function b($ret) {\n        echo $ret;\n    }\n    static function c($ret) {\n        echo $ret;\n    }\n\n    function func() {\n        self::a(self::b(self::c()));\n    }\n\n    function func2() {\n        $amount = x_(y_(z_($amount), z_('1' . str_pad('', $decimalPlaces, '0'))));\n    }\n}\n\n(new Cls()).func();\n\nfunc2()\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/publicprivateprotected/publicprivateprotected.php",
    "content": "<?php\n// From https://www.w3schools.com/php/php_oop_access_modifiers.asp\nclass Fruit {\n  public $name;\n  public $color;\n  public $weight;\n\n  function set_name($n) {  // a public function (default)\n    $this->name = $n;\n  }\n  protected function set_color($n) { // a protected function\n    $this->color = $n;\n  }\n  private function set_weight($n) { // a private function\n    $this->weight = $n;\n  }\n}\n\nfunction set_color_weight($fruit) {\n    $fruit.set_color('blue');\n    $fruit.set_weight(5);\n}\n\n$fruit = new Fruit();\n$fruit.set_name('blueberry');\nset_color_weight();\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/resolve_correct_class/rcc.php",
    "content": "<?php\nfunction func_1() {\n    echo \"this should never get called\";\n}\n\nclass Alpha {\n    function func_1() {\n        echo \"alpha func_1\";\n        $this.func_1();\n        $b = new Beta();\n        $b->func_2();\n    }\n\n    function func_2() {\n        echo \"alpha func_2\";\n    }\n}\n\nclass Beta {\n    function func_1() {\n        echo \"beta func_1\";\n        $al = new Alpha();\n        $al->func_2();\n    }\n\n    function func_2() {\n        echo \"beta func_2\";\n    }\n}\n?>\n"
  },
  {
    "path": "tests/test_code/php/simple_a/simple_a.php",
    "content": "<?php\n\nfunction func_b() {\n    echo \"hello world\";\n}\n\nfunction func_a() {\n    func_b();\n}\n\nfunc_a();\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/simple_b/simple_b.php",
    "content": "<?php\n\nrequire __DIR__.'/../vendor/autoload.php';\n\nfunction a($param) {\n    b($param);\n}\n\n\nfunction b() {\n    a(\"STRC #\");\n}\n\n\nclass C {\n    function d($param) {\n        a(\"AnotherSTR\");\n    }\n}\n\n$c = new C();\n$c->d();\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/static/static.php",
    "content": " <?php\n\nfunction say_name() {}\n\nclass Greeting {\n  public static function welcome() {\n    echo \"Hello World!\";\n  }\n\n  function say_name() {\n    echo $this->name;\n  }\n\n  function __construct($name) {\n      $this->name = $name;\n      echo self::welcome() + ' ' + $this->say_name();\n  }\n}\n\nfunction welcome() {}\n\n// Call static method\nGreeting::welcome();\n$greet = new Greeting(\"Scott\");\n?>\n"
  },
  {
    "path": "tests/test_code/php/traits/traits.php",
    "content": "<?php\ntrait message1 {\n  public function msg1() {\n    echo \"OOP is fun! \";\n  }\n}\n\ntrait message2 {\n  public function msg2() {\n    echo \"OOP reduces code duplication!\";\n  }\n}\n\nclass Welcome {\n  use message1;\n\n  function __construct() {\n    echo \"__construct\";\n  }\n}\n\nclass Welcome2 {\n  use message1, message2;\n}\n\nfunction welcome1() {\n  $obj = new Welcome();\n  $obj->msg1();\n  echo \"<br>\";\n}\n\nfunction welcome2() {\n  $obj2 = new Welcome2();\n  $obj2->msg1();\n  $obj2->msg2();\n}\n\n?>\n"
  },
  {
    "path": "tests/test_code/php/two_file_simple/file_a.php",
    "content": "<?php\ninclude_once('file_b.php');\nfunction a() {\n    b();\n}\n\n\na();\n?>\n"
  },
  {
    "path": "tests/test_code/php/two_file_simple/file_b.php",
    "content": "<?php\nfunction b() {\n    echo \"here\";\n}\n\n\nfunction c() {}\n?>\n"
  },
  {
    "path": "tests/test_code/php/weird_assign/weird_assign.php",
    "content": "<?php\n\nfunction a() {}\nfunction b() {}\n\nfunction c($x, $y) {\n    [$integer, $remainder] = a(b($x), b((string) $y));\n\n}\n\n?>\n"
  },
  {
    "path": "tests/test_code/py/ambiguous_resolution/ambiguous_resolution.py",
    "content": "class Abra():\n    def magic():\n        pass\n\n    def abra_it():\n        pass\n\n\nclass Cadabra():\n    def magic():\n        pass\n\n    def cadabra_it(a=None):\n        a.abra_it()\n\n\ndef main(cls):\n    obj = cls()\n    obj.magic()\n    obj.cadabra_it()\n\n\nmain()\n"
  },
  {
    "path": "tests/test_code/py/async_basic/async_basic.py",
    "content": "import asyncio\n\nclass A:\n    def __init__(self):\n        pass\n    async def test(self):\n        print(\"hello world\")\n\nasync def main():\n    a = A()\n    await a.test()\n\nasyncio.run(main())\n"
  },
  {
    "path": "tests/test_code/py/chained/chained.py",
    "content": "class Chain():\n    def __init__(self, val):\n        self.val = val\n\n    def add(self, b):\n        self.val += b\n        return self\n\n    def sub(self, b):\n        self.val -= b\n        return self\n\n    def mul(self, b):\n        self.val *= b\n        return self\n\n\nprint(Chain(5).add(5).sub(2).mul(10))\n"
  },
  {
    "path": "tests/test_code/py/exclude_modules/exclude_modules.py",
    "content": "import re\nfrom re import match\n\n\ndef search():\n    print(\"This is the wrong search\")\n\n\ndef beta():\n    print(\"this still connects\")\n    search()\n    b = Nothing()\n    b.beta()\n\n\ndef alpha():\n    re.search(\"hello world\")\n    beta()\n    match()\n    # ret = str('')\n    # ret()\n\n\nif __name__ == '__main__':\n    alpha()\n"
  },
  {
    "path": "tests/test_code/py/exclude_modules_two_files/exclude_modules_a.py",
    "content": "import re\nfrom exclude_modules_b import match\n\n\ndef a():\n    re.match()\n\n\ndef b():\n    match()\n\n\na()\nb()\n"
  },
  {
    "path": "tests/test_code/py/exclude_modules_two_files/exclude_modules_b.py",
    "content": "def match():\n    print(\"match\")\n"
  },
  {
    "path": "tests/test_code/py/import_paths/abra.py",
    "content": "def abra2():\n\tpass\n"
  },
  {
    "path": "tests/test_code/py/import_paths/cadabra.py",
    "content": "def cadabra2():\n\tpass\n"
  },
  {
    "path": "tests/test_code/py/import_paths/import_paths.py",
    "content": "import abra\nfrom . import cadabra\nfrom abra import abra2 as abra3\n\ndef main():\n\tabra3()\n\tcadabra.cadabra2()\n\tmain2()\n\ndef main2():\n\tabra.abra2()\n\nmain()\n"
  },
  {
    "path": "tests/test_code/py/inherits/inherits.py",
    "content": "# from https://ruby-doc.com/docs/ProgrammingRuby/html/tut_modules.html\n\nfrom inherits_import import MajorScales, PentatonicScales\n\n\ndef majorNum():\n    pass\n\n\ndef pentaNum():\n    pass\n\n\nclass FakePentatonicScales():\n    def pentaNum(self):\n        if self.numNotes is None:\n            self.numNotes = 5\n        return self.numNotes\n\n\nclass ScaleDemo(MajorScales, PentatonicScales):\n    def __init__(self):\n        self.numNotes = None\n        print(self.majorNum())\n        print(self.pentaNum())\n\n    def nothing(self):\n        pass\n\n\nclass ScaleDemoLimited(MajorScales):\n    def __init__(self):\n        self.numNotes = None\n        print(self.majorNum())\n\n\nsd = ScaleDemo()\nmajorNum()\n"
  },
  {
    "path": "tests/test_code/py/inherits/inherits_import.py",
    "content": "# from https://ruby-doc.com/docs/ProgrammingRuby/html/tut_modules.html\n\nclass MajorScales():\n    def majorNum(self):\n        if self.numNotes is None:\n            self.numNotes = 7\n        return self.numNotes\n\n\nclass PentatonicScales():\n    def pentaNum(self):\n        if self.numNotes is None:\n            self.numNotes = 5\n        return self.numNotes\n\n"
  },
  {
    "path": "tests/test_code/py/init/init.py",
    "content": "from the_import import ProvincialClass as pc, imported_func\n\n\nclass Abra():\n    def __init__(self):\n        self.cadabra()\n\n    def cadabra(self):\n        print(\"cadabra\")\n\n\ndef b():\n    Abra()\n\n\nb()\npc()\nHiddenClass()  # this is probably too defensive\nimported_func()\n"
  },
  {
    "path": "tests/test_code/py/init/the_import.py",
    "content": "class ProvincialClass():\n    def __init__(self):\n        print(\"here\")\n\n\nclass HiddenClass():\n    def __init__(self):\n        print(\"here\")\n\n\ndef imported_func():\n    print(\"here\")\n"
  },
  {
    "path": "tests/test_code/py/nested_calls/nested_calls.py",
    "content": "from typing import Callable\n\ndef trace(fn: Callable) -> Callable:\n    def wrapper(*args, **kwargs):\n        print('traced call')\n        return fn(*args, **kwargs)\n    return wrapper\n\ndef do_something(msg):\n    return msg + ' world'\n\nmessage = 'hello'\nnew_message = trace(do_something)(message)\n"
  },
  {
    "path": "tests/test_code/py/nested_class/nested_class.py",
    "content": "class Outer():\n    class Inner():\n        def inner_func():\n            Outer().outer_func()\n\n    def outer_func(a):\n        print(\"Outer_func\")\n        a.inner_func()\n\n    def __init__(self):\n        self.inner = self.Inner()\n        print(\"do something\")\n\n\nnew_obj = Outer()\nnew_obj.outer_func()\n"
  },
  {
    "path": "tests/test_code/py/pytz/__init__.py",
    "content": "'''\ndatetime.tzinfo timezone definitions generated from the\nOlson timezone database:\n\n    ftp://elsie.nci.nih.gov/pub/tz*.tar.gz\n\nSee the datetime section of the Python Library Reference for information\non how to use these modules.\n'''\n\nimport sys\nimport datetime\nimport os.path\n\nfrom pytz.exceptions import AmbiguousTimeError\nfrom pytz.exceptions import InvalidTimeError\nfrom pytz.exceptions import NonExistentTimeError\nfrom pytz.exceptions import UnknownTimeZoneError\nfrom pytz.lazy import LazyDict, LazyList, LazySet  # noqa\nfrom pytz.tzinfo import unpickler, BaseTzInfo\nfrom pytz.tzfile import build_tzinfo\n\n\n# The IANA (nee Olson) database is updated several times a year.\nOLSON_VERSION = '2021a'\nVERSION = '2021.1'  # pip compatible version number.\n__version__ = VERSION\n\nOLSEN_VERSION = OLSON_VERSION  # Old releases had this misspelling\n\n__all__ = [\n    'timezone', 'utc', 'country_timezones', 'country_names',\n    'AmbiguousTimeError', 'InvalidTimeError',\n    'NonExistentTimeError', 'UnknownTimeZoneError',\n    'all_timezones', 'all_timezones_set',\n    'common_timezones', 'common_timezones_set',\n    'BaseTzInfo', 'FixedOffset',\n]\n\n\nif sys.version_info[0] > 2:  # Python 3.x\n\n    # Python 3.x doesn't have unicode(), making writing code\n    # for Python 2.3 and Python 3.x a pain.\n    unicode = str\n\n    def ascii(s):\n        r\"\"\"\n        >>> ascii('Hello')\n        'Hello'\n        >>> ascii('\\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL\n        Traceback (most recent call last):\n            ...\n        UnicodeEncodeError: ...\n        \"\"\"\n        if type(s) == bytes:\n            s = s.decode('ASCII')\n        else:\n            s.encode('ASCII')  # Raise an exception if not ASCII\n        return s  # But the string - not a byte string.\n\nelse:  # Python 2.x\n\n    def ascii(s):\n        r\"\"\"\n        >>> ascii('Hello')\n        'Hello'\n        >>> ascii(u'Hello')\n        'Hello'\n        >>> ascii(u'\\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL\n        Traceback (most recent call last):\n            ...\n        UnicodeEncodeError: ...\n        \"\"\"\n        return s.encode('ASCII')\n\n\ndef open_resource(name):\n    \"\"\"Open a resource from the zoneinfo subdir for reading.\n\n    Uses the pkg_resources module if available and no standard file\n    found at the calculated location.\n\n    It is possible to specify different location for zoneinfo\n    subdir by using the PYTZ_TZDATADIR environment variable.\n    \"\"\"\n    name_parts = name.lstrip('/').split('/')\n    for part in name_parts:\n        if part == os.path.pardir or os.path.sep in part:\n            raise ValueError('Bad path segment: %r' % part)\n    zoneinfo_dir = os.environ.get('PYTZ_TZDATADIR', None)\n    if zoneinfo_dir is not None:\n        filename = os.path.join(zoneinfo_dir, *name_parts)\n    else:\n        filename = os.path.join(os.path.dirname(__file__),\n                                'zoneinfo', *name_parts)\n        if not os.path.exists(filename):\n            # http://bugs.launchpad.net/bugs/383171 - we avoid using this\n            # unless absolutely necessary to help when a broken version of\n            # pkg_resources is installed.\n            try:\n                from pkg_resources import resource_stream\n            except ImportError:\n                resource_stream = None\n\n            if resource_stream is not None:\n                return resource_stream(__name__, 'zoneinfo/' + name)\n    return open(filename, 'rb')\n\n\ndef resource_exists(name):\n    \"\"\"Return true if the given resource exists\"\"\"\n    try:\n        if os.environ.get('PYTZ_SKIPEXISTSCHECK', ''):\n            # In \"standard\" distributions, we can assume that\n            # all the listed timezones are present. As an\n            # import-speed optimization, you can set the\n            # PYTZ_SKIPEXISTSCHECK flag to skip checking\n            # for the presence of the resource file on disk.\n            return True\n        open_resource(name).close()\n        return True\n    except IOError:\n        return False\n\n\n_tzinfo_cache = {}\n\n\ndef timezone(zone):\n    r''' Return a datetime.tzinfo implementation for the given timezone\n\n    >>> from datetime import datetime, timedelta\n    >>> utc = timezone('UTC')\n    >>> eastern = timezone('US/Eastern')\n    >>> eastern.zone\n    'US/Eastern'\n    >>> timezone(unicode('US/Eastern')) is eastern\n    True\n    >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)\n    >>> loc_dt = utc_dt.astimezone(eastern)\n    >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'\n    >>> loc_dt.strftime(fmt)\n    '2002-10-27 01:00:00 EST (-0500)'\n    >>> (loc_dt - timedelta(minutes=10)).strftime(fmt)\n    '2002-10-27 00:50:00 EST (-0500)'\n    >>> eastern.normalize(loc_dt - timedelta(minutes=10)).strftime(fmt)\n    '2002-10-27 01:50:00 EDT (-0400)'\n    >>> (loc_dt + timedelta(minutes=10)).strftime(fmt)\n    '2002-10-27 01:10:00 EST (-0500)'\n\n    Raises UnknownTimeZoneError if passed an unknown zone.\n\n    >>> try:\n    ...     timezone('Asia/Shangri-La')\n    ... except UnknownTimeZoneError:\n    ...     print('Unknown')\n    Unknown\n\n    >>> try:\n    ...     timezone(unicode('\\N{TRADE MARK SIGN}'))\n    ... except UnknownTimeZoneError:\n    ...     print('Unknown')\n    Unknown\n\n    '''\n    if zone is None:\n        raise UnknownTimeZoneError(None)\n\n    if zone.upper() == 'UTC':\n        return utc\n\n    try:\n        zone = ascii(zone)\n    except UnicodeEncodeError:\n        # All valid timezones are ASCII\n        raise UnknownTimeZoneError(zone)\n\n    zone = _case_insensitive_zone_lookup(_unmunge_zone(zone))\n    if zone not in _tzinfo_cache:\n        if zone in all_timezones_set:  # noqa\n            fp = open_resource(zone)\n            try:\n                _tzinfo_cache[zone] = build_tzinfo(zone, fp)\n            finally:\n                fp.close()\n        else:\n            raise UnknownTimeZoneError(zone)\n\n    return _tzinfo_cache[zone]\n\n\ndef _unmunge_zone(zone):\n    \"\"\"Undo the time zone name munging done by older versions of pytz.\"\"\"\n    return zone.replace('_plus_', '+').replace('_minus_', '-')\n\n\n_all_timezones_lower_to_standard = None\n\n\ndef _case_insensitive_zone_lookup(zone):\n    \"\"\"case-insensitively matching timezone, else return zone unchanged\"\"\"\n    global _all_timezones_lower_to_standard\n    if _all_timezones_lower_to_standard is None:\n        _all_timezones_lower_to_standard = dict((tz.lower(), tz) for tz in all_timezones)  # noqa\n    return _all_timezones_lower_to_standard.get(zone.lower()) or zone  # noqa\n\n\nZERO = datetime.timedelta(0)\nHOUR = datetime.timedelta(hours=1)\n\n\nclass UTC(BaseTzInfo):\n    \"\"\"UTC\n\n    Optimized UTC implementation. It unpickles using the single module global\n    instance defined beneath this class declaration.\n    \"\"\"\n    zone = \"UTC\"\n\n    _utcoffset = ZERO\n    _dst = ZERO\n    _tzname = zone\n\n    def fromutc(self, dt):\n        if dt.tzinfo is None:\n            return self.localize(dt)\n        return super(utc.__class__, self).fromutc(dt)\n\n    def utcoffset(self, dt):\n        return ZERO\n\n    def tzname(self, dt):\n        return \"UTC\"\n\n    def dst(self, dt):\n        return ZERO\n\n    def __reduce__(self):\n        return _UTC, ()\n\n    def localize(self, dt, is_dst=False):\n        '''Convert naive time to local time'''\n        if dt.tzinfo is not None:\n            raise ValueError('Not naive datetime (tzinfo is already set)')\n        return dt.replace(tzinfo=self)\n\n    def normalize(self, dt, is_dst=False):\n        '''Correct the timezone information on the given datetime'''\n        if dt.tzinfo is self:\n            return dt\n        if dt.tzinfo is None:\n            raise ValueError('Naive time - no tzinfo set')\n        return dt.astimezone(self)\n\n    def __repr__(self):\n        return \"<UTC>\"\n\n    def __str__(self):\n        return \"UTC\"\n\n\nUTC = utc = UTC()  # UTC is a singleton\n\n\ndef _UTC():\n    \"\"\"Factory function for utc unpickling.\n\n    Makes sure that unpickling a utc instance always returns the same\n    module global.\n\n    These examples belong in the UTC class above, but it is obscured; or in\n    the README.rst, but we are not depending on Python 2.4 so integrating\n    the README.rst examples with the unit tests is not trivial.\n\n    >>> import datetime, pickle\n    >>> dt = datetime.datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc)\n    >>> naive = dt.replace(tzinfo=None)\n    >>> p = pickle.dumps(dt, 1)\n    >>> naive_p = pickle.dumps(naive, 1)\n    >>> len(p) - len(naive_p)\n    17\n    >>> new = pickle.loads(p)\n    >>> new == dt\n    True\n    >>> new is dt\n    False\n    >>> new.tzinfo is dt.tzinfo\n    True\n    >>> utc is UTC is timezone('UTC')\n    True\n    >>> utc is timezone('GMT')\n    False\n    \"\"\"\n    return utc\n\n\n_UTC.__safe_for_unpickling__ = True\n\n\ndef _p(*args):\n    \"\"\"Factory function for unpickling pytz tzinfo instances.\n\n    Just a wrapper around tzinfo.unpickler to save a few bytes in each pickle\n    by shortening the path.\n    \"\"\"\n    return unpickler(*args)\n\n\n_p.__safe_for_unpickling__ = True\n\n\nclass _CountryTimezoneDict(LazyDict):\n    \"\"\"Map ISO 3166 country code to a list of timezone names commonly used\n    in that country.\n\n    iso3166_code is the two letter code used to identify the country.\n\n    >>> def print_list(list_of_strings):\n    ...     'We use a helper so doctests work under Python 2.3 -> 3.x'\n    ...     for s in list_of_strings:\n    ...         print(s)\n\n    >>> print_list(country_timezones['nz'])\n    Pacific/Auckland\n    Pacific/Chatham\n    >>> print_list(country_timezones['ch'])\n    Europe/Zurich\n    >>> print_list(country_timezones['CH'])\n    Europe/Zurich\n    >>> print_list(country_timezones[unicode('ch')])\n    Europe/Zurich\n    >>> print_list(country_timezones['XXX'])\n    Traceback (most recent call last):\n    ...\n    KeyError: 'XXX'\n\n    Previously, this information was exposed as a function rather than a\n    dictionary. This is still supported::\n\n    >>> print_list(country_timezones('nz'))\n    Pacific/Auckland\n    Pacific/Chatham\n    \"\"\"\n    def __call__(self, iso3166_code):\n        \"\"\"Backwards compatibility.\"\"\"\n        return self[iso3166_code]\n\n    def _fill(self):\n        data = {}\n        zone_tab = open_resource('zone.tab')\n        try:\n            for line in zone_tab:\n                line = line.decode('UTF-8')\n                if line.startswith('#'):\n                    continue\n                code, coordinates, zone = line.split(None, 4)[:3]\n                if zone not in all_timezones_set:  # noqa\n                    continue\n                try:\n                    data[code].append(zone)\n                except KeyError:\n                    data[code] = [zone]\n            self.data = data\n        finally:\n            zone_tab.close()\n\n\ncountry_timezones = _CountryTimezoneDict()\n\n\nclass _CountryNameDict(LazyDict):\n    '''Dictionary proving ISO3166 code -> English name.\n\n    >>> print(country_names['au'])\n    Australia\n    '''\n    def _fill(self):\n        data = {}\n        zone_tab = open_resource('iso3166.tab')\n        try:\n            for line in zone_tab.readlines():\n                line = line.decode('UTF-8')\n                if line.startswith('#'):\n                    continue\n                code, name = line.split(None, 1)\n                data[code] = name.strip()\n            self.data = data\n        finally:\n            zone_tab.close()\n\n\ncountry_names = _CountryNameDict()\n\n\n# Time-zone info based solely on fixed offsets\n\nclass _FixedOffset(datetime.tzinfo):\n\n    zone = None  # to match the standard pytz API\n\n    def __init__(self, minutes):\n        if abs(minutes) >= 1440:\n            raise ValueError(\"absolute offset is too large\", minutes)\n        self._minutes = minutes\n        self._offset = datetime.timedelta(minutes=minutes)\n\n    def utcoffset(self, dt):\n        return self._offset\n\n    def __reduce__(self):\n        return FixedOffset, (self._minutes, )\n\n    def dst(self, dt):\n        return ZERO\n\n    def tzname(self, dt):\n        return None\n\n    def __repr__(self):\n        return 'pytz.FixedOffset(%d)' % self._minutes\n\n    def localize(self, dt, is_dst=False):\n        '''Convert naive time to local time'''\n        if dt.tzinfo is not None:\n            raise ValueError('Not naive datetime (tzinfo is already set)')\n        return dt.replace(tzinfo=self)\n\n    def normalize(self, dt, is_dst=False):\n        '''Correct the timezone information on the given datetime'''\n        if dt.tzinfo is self:\n            return dt\n        if dt.tzinfo is None:\n            raise ValueError('Naive time - no tzinfo set')\n        return dt.astimezone(self)\n\n\ndef FixedOffset(offset, _tzinfos={}):\n    \"\"\"return a fixed-offset timezone based off a number of minutes.\n\n        >>> one = FixedOffset(-330)\n        >>> one\n        pytz.FixedOffset(-330)\n        >>> str(one.utcoffset(datetime.datetime.now()))\n        '-1 day, 18:30:00'\n        >>> str(one.dst(datetime.datetime.now()))\n        '0:00:00'\n\n        >>> two = FixedOffset(1380)\n        >>> two\n        pytz.FixedOffset(1380)\n        >>> str(two.utcoffset(datetime.datetime.now()))\n        '23:00:00'\n        >>> str(two.dst(datetime.datetime.now()))\n        '0:00:00'\n\n    The datetime.timedelta must be between the range of -1 and 1 day,\n    non-inclusive.\n\n        >>> FixedOffset(1440)\n        Traceback (most recent call last):\n        ...\n        ValueError: ('absolute offset is too large', 1440)\n\n        >>> FixedOffset(-1440)\n        Traceback (most recent call last):\n        ...\n        ValueError: ('absolute offset is too large', -1440)\n\n    An offset of 0 is special-cased to return UTC.\n\n        >>> FixedOffset(0) is UTC\n        True\n\n    There should always be only one instance of a FixedOffset per timedelta.\n    This should be true for multiple creation calls.\n\n        >>> FixedOffset(-330) is one\n        True\n        >>> FixedOffset(1380) is two\n        True\n\n    It should also be true for pickling.\n\n        >>> import pickle\n        >>> pickle.loads(pickle.dumps(one)) is one\n        True\n        >>> pickle.loads(pickle.dumps(two)) is two\n        True\n    \"\"\"\n    if offset == 0:\n        return UTC\n\n    info = _tzinfos.get(offset)\n    if info is None:\n        # We haven't seen this one before. we need to save it.\n\n        # Use setdefault to avoid a race condition and make sure we have\n        # only one\n        info = _tzinfos.setdefault(offset, _FixedOffset(offset))\n\n    return info\n\n\nFixedOffset.__safe_for_unpickling__ = True\n\n\ndef _test():\n    import doctest\n    sys.path.insert(0, os.pardir)\n    import pytz\n    return doctest.testmod(pytz)\n\n\nif __name__ == '__main__':\n    _test()\n"
  },
  {
    "path": "tests/test_code/py/pytz/exceptions.py",
    "content": "'''\nCustom exceptions raised by pytz.\n'''\n\n__all__ = [\n    'UnknownTimeZoneError', 'InvalidTimeError', 'AmbiguousTimeError',\n    'NonExistentTimeError',\n]\n\n\nclass Error(Exception):\n    '''Base class for all exceptions raised by the pytz library'''\n\n\nclass UnknownTimeZoneError(KeyError, Error):\n    '''Exception raised when pytz is passed an unknown timezone.\n\n    >>> isinstance(UnknownTimeZoneError(), LookupError)\n    True\n\n    This class is actually a subclass of KeyError to provide backwards\n    compatibility with code relying on the undocumented behavior of earlier\n    pytz releases.\n\n    >>> isinstance(UnknownTimeZoneError(), KeyError)\n    True\n\n    And also a subclass of pytz.exceptions.Error, as are other pytz\n    exceptions.\n\n    >>> isinstance(UnknownTimeZoneError(), Error)\n    True\n\n    '''\n    pass\n\n\nclass InvalidTimeError(Error):\n    '''Base class for invalid time exceptions.'''\n\n\nclass AmbiguousTimeError(InvalidTimeError):\n    '''Exception raised when attempting to create an ambiguous wallclock time.\n\n    At the end of a DST transition period, a particular wallclock time will\n    occur twice (once before the clocks are set back, once after). Both\n    possibilities may be correct, unless further information is supplied.\n\n    See DstTzInfo.normalize() for more info\n    '''\n\n\nclass NonExistentTimeError(InvalidTimeError):\n    '''Exception raised when attempting to create a wallclock time that\n    cannot exist.\n\n    At the start of a DST transition period, the wallclock time jumps forward.\n    The instants jumped over never occur.\n    '''\n"
  },
  {
    "path": "tests/test_code/py/pytz/lazy.py",
    "content": "from threading import RLock\ntry:\n    from collections.abc import Mapping as DictMixin\nexcept ImportError:  # Python < 3.3\n    try:\n        from UserDict import DictMixin  # Python 2\n    except ImportError:  # Python 3.0-3.3\n        from collections import Mapping as DictMixin\n\n\n# With lazy loading, we might end up with multiple threads triggering\n# it at the same time. We need a lock.\n_fill_lock = RLock()\n\n\nclass LazyDict(DictMixin):\n    \"\"\"Dictionary populated on first use.\"\"\"\n    data = None\n\n    def __getitem__(self, key):\n        if self.data is None:\n            _fill_lock.acquire()\n            try:\n                if self.data is None:\n                    self._fill()\n            finally:\n                _fill_lock.release()\n        return self.data[key.upper()]\n\n    def __contains__(self, key):\n        if self.data is None:\n            _fill_lock.acquire()\n            try:\n                if self.data is None:\n                    self._fill()\n            finally:\n                _fill_lock.release()\n        return key in self.data\n\n    def __iter__(self):\n        if self.data is None:\n            _fill_lock.acquire()\n            try:\n                if self.data is None:\n                    self._fill()\n            finally:\n                _fill_lock.release()\n        return iter(self.data)\n\n    def __len__(self):\n        if self.data is None:\n            _fill_lock.acquire()\n            try:\n                if self.data is None:\n                    self._fill()\n            finally:\n                _fill_lock.release()\n        return len(self.data)\n\n    def keys(self):\n        if self.data is None:\n            _fill_lock.acquire()\n            try:\n                if self.data is None:\n                    self._fill()\n            finally:\n                _fill_lock.release()\n        return self.data.keys()\n\n\nclass LazyList(list):\n    \"\"\"List populated on first use.\"\"\"\n\n    _props = [\n        '__str__', '__repr__', '__unicode__',\n        '__hash__', '__sizeof__', '__cmp__',\n        '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__',\n        'append', 'count', 'index', 'extend', 'insert', 'pop', 'remove',\n        'reverse', 'sort', '__add__', '__radd__', '__iadd__', '__mul__',\n        '__rmul__', '__imul__', '__contains__', '__len__', '__nonzero__',\n        '__getitem__', '__setitem__', '__delitem__', '__iter__',\n        '__reversed__', '__getslice__', '__setslice__', '__delslice__']\n\n    def __new__(cls, fill_iter=None):\n\n        if fill_iter is None:\n            return list()\n\n        # We need a new class as we will be dynamically messing with its\n        # methods.\n        class LazyList(list):\n            pass\n\n        fill_iter = [fill_iter]\n\n        def lazy(name):\n            def _lazy(self, *args, **kw):\n                _fill_lock.acquire()\n                try:\n                    if len(fill_iter) > 0:\n                        list.extend(self, fill_iter.pop())\n                        for method_name in cls._props:\n                            delattr(LazyList, method_name)\n                finally:\n                    _fill_lock.release()\n                return getattr(list, name)(self, *args, **kw)\n            return _lazy\n\n        for name in cls._props:\n            setattr(LazyList, name, lazy(name))\n\n        new_list = LazyList()\n        return new_list\n\n# Not all versions of Python declare the same magic methods.\n# Filter out properties that don't exist in this version of Python\n# from the list.\nLazyList._props = [prop for prop in LazyList._props if hasattr(list, prop)]\n\n\nclass LazySet(set):\n    \"\"\"Set populated on first use.\"\"\"\n\n    _props = (\n        '__str__', '__repr__', '__unicode__',\n        '__hash__', '__sizeof__', '__cmp__',\n        '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__',\n        '__contains__', '__len__', '__nonzero__',\n        '__getitem__', '__setitem__', '__delitem__', '__iter__',\n        '__sub__', '__and__', '__xor__', '__or__',\n        '__rsub__', '__rand__', '__rxor__', '__ror__',\n        '__isub__', '__iand__', '__ixor__', '__ior__',\n        'add', 'clear', 'copy', 'difference', 'difference_update',\n        'discard', 'intersection', 'intersection_update', 'isdisjoint',\n        'issubset', 'issuperset', 'pop', 'remove',\n        'symmetric_difference', 'symmetric_difference_update',\n        'union', 'update')\n\n    def __new__(cls, fill_iter=None):\n\n        if fill_iter is None:\n            return set()\n\n        class LazySet(set):\n            pass\n\n        fill_iter = [fill_iter]\n\n        def lazy(name):\n            def _lazy(self, *args, **kw):\n                _fill_lock.acquire()\n                try:\n                    if len(fill_iter) > 0:\n                        for i in fill_iter.pop():\n                            set.add(self, i)\n                        for method_name in cls._props:\n                            delattr(LazySet, method_name)\n                finally:\n                    _fill_lock.release()\n                return getattr(set, name)(self, *args, **kw)\n            return _lazy\n\n        for name in cls._props:\n            setattr(LazySet, name, lazy(name))\n\n        new_set = LazySet()\n        return new_set\n\n# Not all versions of Python declare the same magic methods.\n# Filter out properties that don't exist in this version of Python\n# from the list.\nLazySet._props = [prop for prop in LazySet._props if hasattr(set, prop)]\n"
  },
  {
    "path": "tests/test_code/py/pytz/reference.py",
    "content": "'''\nReference tzinfo implementations from the Python docs.\nUsed for testing against as they are only correct for the years\n1987 to 2006. Do not use these for real code.\n'''\n\nfrom datetime import tzinfo, timedelta, datetime\nfrom pytz import HOUR, ZERO, UTC\n\n__all__ = [\n    'FixedOffset',\n    'LocalTimezone',\n    'USTimeZone',\n    'Eastern',\n    'Central',\n    'Mountain',\n    'Pacific',\n    'UTC'\n]\n\n\n# A class building tzinfo objects for fixed-offset time zones.\n# Note that FixedOffset(0, \"UTC\") is a different way to build a\n# UTC tzinfo object.\nclass FixedOffset(tzinfo):\n    \"\"\"Fixed offset in minutes east from UTC.\"\"\"\n\n    def __init__(self, offset, name):\n        self.__offset = timedelta(minutes=offset)\n        self.__name = name\n\n    def utcoffset(self, dt):\n        return self.__offset\n\n    def tzname(self, dt):\n        return self.__name\n\n    def dst(self, dt):\n        return ZERO\n\n\nimport time as _time\n\nSTDOFFSET = timedelta(seconds=-_time.timezone)\nif _time.daylight:\n    DSTOFFSET = timedelta(seconds=-_time.altzone)\nelse:\n    DSTOFFSET = STDOFFSET\n\nDSTDIFF = DSTOFFSET - STDOFFSET\n\n\n# A class capturing the platform's idea of local time.\nclass LocalTimezone(tzinfo):\n\n    def utcoffset(self, dt):\n        if self._isdst(dt):\n            return DSTOFFSET\n        else:\n            return STDOFFSET\n\n    def dst(self, dt):\n        if self._isdst(dt):\n            return DSTDIFF\n        else:\n            return ZERO\n\n    def tzname(self, dt):\n        return _time.tzname[self._isdst(dt)]\n\n    def _isdst(self, dt):\n        tt = (dt.year, dt.month, dt.day,\n              dt.hour, dt.minute, dt.second,\n              dt.weekday(), 0, -1)\n        stamp = _time.mktime(tt)\n        tt = _time.localtime(stamp)\n        return tt.tm_isdst > 0\n\nLocal = LocalTimezone()\n\n\ndef first_sunday_on_or_after(dt):\n    days_to_go = 6 - dt.weekday()\n    if days_to_go:\n        dt += timedelta(days_to_go)\n    return dt\n\n\n# In the US, DST starts at 2am (standard time) on the first Sunday in April.\nDSTSTART = datetime(1, 4, 1, 2)\n# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.\n# which is the first Sunday on or after Oct 25.\nDSTEND = datetime(1, 10, 25, 1)\n\n\n# A complete implementation of current DST rules for major US time zones.\nclass USTimeZone(tzinfo):\n\n    def __init__(self, hours, reprname, stdname, dstname):\n        self.stdoffset = timedelta(hours=hours)\n        self.reprname = reprname\n        self.stdname = stdname\n        self.dstname = dstname\n\n    def __repr__(self):\n        return self.reprname\n\n    def tzname(self, dt):\n        if self.dst(dt):\n            return self.dstname\n        else:\n            return self.stdname\n\n    def utcoffset(self, dt):\n        return self.stdoffset + self.dst(dt)\n\n    def dst(self, dt):\n        if dt is None or dt.tzinfo is None:\n            # An exception may be sensible here, in one or both cases.\n            # It depends on how you want to treat them.  The default\n            # fromutc() implementation (called by the default astimezone()\n            # implementation) passes a datetime with dt.tzinfo is self.\n            return ZERO\n        assert dt.tzinfo is self\n\n        # Find first Sunday in April & the last in October.\n        start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))\n        end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))\n\n        # Can't compare naive to aware objects, so strip the timezone from\n        # dt first.\n        if start <= dt.replace(tzinfo=None) < end:\n            return HOUR\n        else:\n            return ZERO\n\nEastern = USTimeZone(-5, \"Eastern\", \"EST\", \"EDT\")\nCentral = USTimeZone(-6, \"Central\", \"CST\", \"CDT\")\nMountain = USTimeZone(-7, \"Mountain\", \"MST\", \"MDT\")\nPacific = USTimeZone(-8, \"Pacific\", \"PST\", \"PDT\")\n"
  },
  {
    "path": "tests/test_code/py/pytz/tzfile.py",
    "content": "'''\n$Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $\n'''\n\nfrom datetime import datetime\nfrom struct import unpack, calcsize\n\nfrom pytz.tzinfo import StaticTzInfo, DstTzInfo, memorized_ttinfo\nfrom pytz.tzinfo import memorized_datetime, memorized_timedelta\n\n\ndef _byte_string(s):\n    \"\"\"Cast a string or byte string to an ASCII byte string.\"\"\"\n    return s.encode('ASCII')\n\n_NULL = _byte_string('\\0')\n\n\ndef _std_string(s):\n    \"\"\"Cast a string or byte string to an ASCII string.\"\"\"\n    return str(s.decode('ASCII'))\n\n\ndef build_tzinfo(zone, fp):\n    head_fmt = '>4s c 15x 6l'\n    head_size = calcsize(head_fmt)\n    (magic, format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt,\n        typecnt, charcnt) = unpack(head_fmt, fp.read(head_size))\n\n    # Make sure it is a tzfile(5) file\n    assert magic == _byte_string('TZif'), 'Got magic %s' % repr(magic)\n\n    # Read out the transition times, localtime indices and ttinfo structures.\n    data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict(\n        timecnt=timecnt, ttinfo='lBB' * typecnt, charcnt=charcnt)\n    data_size = calcsize(data_fmt)\n    data = unpack(data_fmt, fp.read(data_size))\n\n    # make sure we unpacked the right number of values\n    assert len(data) == 2 * timecnt + 3 * typecnt + 1\n    transitions = [memorized_datetime(trans)\n                   for trans in data[:timecnt]]\n    lindexes = list(data[timecnt:2 * timecnt])\n    ttinfo_raw = data[2 * timecnt:-1]\n    tznames_raw = data[-1]\n    del data\n\n    # Process ttinfo into separate structs\n    ttinfo = []\n    tznames = {}\n    i = 0\n    while i < len(ttinfo_raw):\n        # have we looked up this timezone name yet?\n        tzname_offset = ttinfo_raw[i + 2]\n        if tzname_offset not in tznames:\n            nul = tznames_raw.find(_NULL, tzname_offset)\n            if nul < 0:\n                nul = len(tznames_raw)\n            tznames[tzname_offset] = _std_string(\n                tznames_raw[tzname_offset:nul])\n        ttinfo.append((ttinfo_raw[i],\n                       bool(ttinfo_raw[i + 1]),\n                       tznames[tzname_offset]))\n        i += 3\n\n    # Now build the timezone object\n    if len(ttinfo) == 1 or len(transitions) == 0:\n        ttinfo[0][0], ttinfo[0][2]\n        cls = type(zone, (StaticTzInfo,), dict(\n            zone=zone,\n            _utcoffset=memorized_timedelta(ttinfo[0][0]),\n            _tzname=ttinfo[0][2]))\n    else:\n        # Early dates use the first standard time ttinfo\n        i = 0\n        while ttinfo[i][1]:\n            i += 1\n        if ttinfo[i] == ttinfo[lindexes[0]]:\n            transitions[0] = datetime.min\n        else:\n            transitions.insert(0, datetime.min)\n            lindexes.insert(0, i)\n\n        # calculate transition info\n        transition_info = []\n        for i in range(len(transitions)):\n            inf = ttinfo[lindexes[i]]\n            utcoffset = inf[0]\n            if not inf[1]:\n                dst = 0\n            else:\n                for j in range(i - 1, -1, -1):\n                    prev_inf = ttinfo[lindexes[j]]\n                    if not prev_inf[1]:\n                        break\n                dst = inf[0] - prev_inf[0]  # dst offset\n\n                # Bad dst? Look further. DST > 24 hours happens when\n                # a timzone has moved across the international dateline.\n                if dst <= 0 or dst > 3600 * 3:\n                    for j in range(i + 1, len(transitions)):\n                        stdinf = ttinfo[lindexes[j]]\n                        if not stdinf[1]:\n                            dst = inf[0] - stdinf[0]\n                            if dst > 0:\n                                break  # Found a useful std time.\n\n            tzname = inf[2]\n\n            # Round utcoffset and dst to the nearest minute or the\n            # datetime library will complain. Conversions to these timezones\n            # might be up to plus or minus 30 seconds out, but it is\n            # the best we can do.\n            utcoffset = int((utcoffset + 30) // 60) * 60\n            dst = int((dst + 30) // 60) * 60\n            transition_info.append(memorized_ttinfo(utcoffset, dst, tzname))\n\n        cls = type(zone, (DstTzInfo,), dict(\n            zone=zone,\n            _utc_transition_times=transitions,\n            _transition_info=transition_info))\n\n    return cls()\n\nif __name__ == '__main__':\n    import os.path\n    from pprint import pprint\n    base = os.path.join(os.path.dirname(__file__), 'zoneinfo')\n    tz = build_tzinfo('Australia/Melbourne',\n                      open(os.path.join(base, 'Australia', 'Melbourne'), 'rb'))\n    tz = build_tzinfo('US/Eastern',\n                      open(os.path.join(base, 'US', 'Eastern'), 'rb'))\n    pprint(tz._utc_transition_times)\n"
  },
  {
    "path": "tests/test_code/py/pytz/tzinfo.py",
    "content": "'''Base classes and helpers for building zone specific tzinfo classes'''\n\nfrom datetime import datetime, timedelta, tzinfo\nfrom bisect import bisect_right\ntry:\n    set\nexcept NameError:\n    from sets import Set as set\n\nimport pytz\nfrom pytz.exceptions import AmbiguousTimeError, NonExistentTimeError\n\n__all__ = []\n\n_timedelta_cache = {}\n\n\ndef memorized_timedelta(seconds):\n    '''Create only one instance of each distinct timedelta'''\n    try:\n        return _timedelta_cache[seconds]\n    except KeyError:\n        delta = timedelta(seconds=seconds)\n        _timedelta_cache[seconds] = delta\n        return delta\n\n_epoch = datetime.utcfromtimestamp(0)\n_datetime_cache = {0: _epoch}\n\n\ndef memorized_datetime(seconds):\n    '''Create only one instance of each distinct datetime'''\n    try:\n        return _datetime_cache[seconds]\n    except KeyError:\n        # NB. We can't just do datetime.utcfromtimestamp(seconds) as this\n        # fails with negative values under Windows (Bug #90096)\n        dt = _epoch + timedelta(seconds=seconds)\n        _datetime_cache[seconds] = dt\n        return dt\n\n_ttinfo_cache = {}\n\n\ndef memorized_ttinfo(*args):\n    '''Create only one instance of each distinct tuple'''\n    try:\n        return _ttinfo_cache[args]\n    except KeyError:\n        ttinfo = (\n            memorized_timedelta(args[0]),\n            memorized_timedelta(args[1]),\n            args[2]\n        )\n        _ttinfo_cache[args] = ttinfo\n        return ttinfo\n\n_notime = memorized_timedelta(0)\n\n\ndef _to_seconds(td):\n    '''Convert a timedelta to seconds'''\n    return td.seconds + td.days * 24 * 60 * 60\n\n\nclass BaseTzInfo(tzinfo):\n    # Overridden in subclass\n    _utcoffset = None\n    _tzname = None\n    zone = None\n\n    def __str__(self):\n        return self.zone\n\n\nclass StaticTzInfo(BaseTzInfo):\n    '''A timezone that has a constant offset from UTC\n\n    These timezones are rare, as most locations have changed their\n    offset at some point in their history\n    '''\n    def fromutc(self, dt):\n        '''See datetime.tzinfo.fromutc'''\n        if dt.tzinfo is not None and dt.tzinfo is not self:\n            raise ValueError('fromutc: dt.tzinfo is not self')\n        return (dt + self._utcoffset).replace(tzinfo=self)\n\n    def utcoffset(self, dt, is_dst=None):\n        '''See datetime.tzinfo.utcoffset\n\n        is_dst is ignored for StaticTzInfo, and exists only to\n        retain compatibility with DstTzInfo.\n        '''\n        return self._utcoffset\n\n    def dst(self, dt, is_dst=None):\n        '''See datetime.tzinfo.dst\n\n        is_dst is ignored for StaticTzInfo, and exists only to\n        retain compatibility with DstTzInfo.\n        '''\n        return _notime\n\n    def tzname(self, dt, is_dst=None):\n        '''See datetime.tzinfo.tzname\n\n        is_dst is ignored for StaticTzInfo, and exists only to\n        retain compatibility with DstTzInfo.\n        '''\n        return self._tzname\n\n    def localize(self, dt, is_dst=False):\n        '''Convert naive time to local time'''\n        if dt.tzinfo is not None:\n            raise ValueError('Not naive datetime (tzinfo is already set)')\n        return dt.replace(tzinfo=self)\n\n    def normalize(self, dt, is_dst=False):\n        '''Correct the timezone information on the given datetime.\n\n        This is normally a no-op, as StaticTzInfo timezones never have\n        ambiguous cases to correct:\n\n        >>> from pytz import timezone\n        >>> gmt = timezone('GMT')\n        >>> isinstance(gmt, StaticTzInfo)\n        True\n        >>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt)\n        >>> gmt.normalize(dt) is dt\n        True\n\n        The supported method of converting between timezones is to use\n        datetime.astimezone(). Currently normalize() also works:\n\n        >>> la = timezone('America/Los_Angeles')\n        >>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3))\n        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'\n        >>> gmt.normalize(dt).strftime(fmt)\n        '2011-05-07 08:02:03 GMT (+0000)'\n        '''\n        if dt.tzinfo is self:\n            return dt\n        if dt.tzinfo is None:\n            raise ValueError('Naive time - no tzinfo set')\n        return dt.astimezone(self)\n\n    def __repr__(self):\n        return '<StaticTzInfo %r>' % (self.zone,)\n\n    def __reduce__(self):\n        # Special pickle to zone remains a singleton and to cope with\n        # database changes.\n        return pytz._p, (self.zone,)\n\n\nclass DstTzInfo(BaseTzInfo):\n    '''A timezone that has a variable offset from UTC\n\n    The offset might change if daylight saving time comes into effect,\n    or at a point in history when the region decides to change their\n    timezone definition.\n    '''\n    # Overridden in subclass\n\n    # Sorted list of DST transition times, UTC\n    _utc_transition_times = None\n\n    # [(utcoffset, dstoffset, tzname)] corresponding to\n    # _utc_transition_times entries\n    _transition_info = None\n\n    zone = None\n\n    # Set in __init__\n\n    _tzinfos = None\n    _dst = None  # DST offset\n\n    def __init__(self, _inf=None, _tzinfos=None):\n        if _inf:\n            self._tzinfos = _tzinfos\n            self._utcoffset, self._dst, self._tzname = _inf\n        else:\n            _tzinfos = {}\n            self._tzinfos = _tzinfos\n            self._utcoffset, self._dst, self._tzname = (\n                self._transition_info[0])\n            _tzinfos[self._transition_info[0]] = self\n            for inf in self._transition_info[1:]:\n                if inf not in _tzinfos:\n                    _tzinfos[inf] = self.__class__(inf, _tzinfos)\n\n    def fromutc(self, dt):\n        '''See datetime.tzinfo.fromutc'''\n        if (dt.tzinfo is not None and\n                getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos):\n            raise ValueError('fromutc: dt.tzinfo is not self')\n        dt = dt.replace(tzinfo=None)\n        idx = max(0, bisect_right(self._utc_transition_times, dt) - 1)\n        inf = self._transition_info[idx]\n        return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf])\n\n    def normalize(self, dt):\n        '''Correct the timezone information on the given datetime\n\n        If date arithmetic crosses DST boundaries, the tzinfo\n        is not magically adjusted. This method normalizes the\n        tzinfo to the correct one.\n\n        To test, first we need to do some setup\n\n        >>> from pytz import timezone\n        >>> utc = timezone('UTC')\n        >>> eastern = timezone('US/Eastern')\n        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'\n\n        We next create a datetime right on an end-of-DST transition point,\n        the instant when the wallclocks are wound back one hour.\n\n        >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)\n        >>> loc_dt = utc_dt.astimezone(eastern)\n        >>> loc_dt.strftime(fmt)\n        '2002-10-27 01:00:00 EST (-0500)'\n\n        Now, if we subtract a few minutes from it, note that the timezone\n        information has not changed.\n\n        >>> before = loc_dt - timedelta(minutes=10)\n        >>> before.strftime(fmt)\n        '2002-10-27 00:50:00 EST (-0500)'\n\n        But we can fix that by calling the normalize method\n\n        >>> before = eastern.normalize(before)\n        >>> before.strftime(fmt)\n        '2002-10-27 01:50:00 EDT (-0400)'\n\n        The supported method of converting between timezones is to use\n        datetime.astimezone(). Currently, normalize() also works:\n\n        >>> th = timezone('Asia/Bangkok')\n        >>> am = timezone('Europe/Amsterdam')\n        >>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3))\n        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'\n        >>> am.normalize(dt).strftime(fmt)\n        '2011-05-06 20:02:03 CEST (+0200)'\n        '''\n        if dt.tzinfo is None:\n            raise ValueError('Naive time - no tzinfo set')\n\n        # Convert dt in localtime to UTC\n        offset = dt.tzinfo._utcoffset\n        dt = dt.replace(tzinfo=None)\n        dt = dt - offset\n        # convert it back, and return it\n        return self.fromutc(dt)\n\n    def localize(self, dt, is_dst=False):\n        '''Convert naive time to local time.\n\n        This method should be used to construct localtimes, rather\n        than passing a tzinfo argument to a datetime constructor.\n\n        is_dst is used to determine the correct timezone in the ambiguous\n        period at the end of daylight saving time.\n\n        >>> from pytz import timezone\n        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'\n        >>> amdam = timezone('Europe/Amsterdam')\n        >>> dt  = datetime(2004, 10, 31, 2, 0, 0)\n        >>> loc_dt1 = amdam.localize(dt, is_dst=True)\n        >>> loc_dt2 = amdam.localize(dt, is_dst=False)\n        >>> loc_dt1.strftime(fmt)\n        '2004-10-31 02:00:00 CEST (+0200)'\n        >>> loc_dt2.strftime(fmt)\n        '2004-10-31 02:00:00 CET (+0100)'\n        >>> str(loc_dt2 - loc_dt1)\n        '1:00:00'\n\n        Use is_dst=None to raise an AmbiguousTimeError for ambiguous\n        times at the end of daylight saving time\n\n        >>> try:\n        ...     loc_dt1 = amdam.localize(dt, is_dst=None)\n        ... except AmbiguousTimeError:\n        ...     print('Ambiguous')\n        Ambiguous\n\n        is_dst defaults to False\n\n        >>> amdam.localize(dt) == amdam.localize(dt, False)\n        True\n\n        is_dst is also used to determine the correct timezone in the\n        wallclock times jumped over at the start of daylight saving time.\n\n        >>> pacific = timezone('US/Pacific')\n        >>> dt = datetime(2008, 3, 9, 2, 0, 0)\n        >>> ploc_dt1 = pacific.localize(dt, is_dst=True)\n        >>> ploc_dt2 = pacific.localize(dt, is_dst=False)\n        >>> ploc_dt1.strftime(fmt)\n        '2008-03-09 02:00:00 PDT (-0700)'\n        >>> ploc_dt2.strftime(fmt)\n        '2008-03-09 02:00:00 PST (-0800)'\n        >>> str(ploc_dt2 - ploc_dt1)\n        '1:00:00'\n\n        Use is_dst=None to raise a NonExistentTimeError for these skipped\n        times.\n\n        >>> try:\n        ...     loc_dt1 = pacific.localize(dt, is_dst=None)\n        ... except NonExistentTimeError:\n        ...     print('Non-existent')\n        Non-existent\n        '''\n        if dt.tzinfo is not None:\n            raise ValueError('Not naive datetime (tzinfo is already set)')\n\n        # Find the two best possibilities.\n        possible_loc_dt = set()\n        for delta in [timedelta(days=-1), timedelta(days=1)]:\n            loc_dt = dt + delta\n            idx = max(0, bisect_right(\n                self._utc_transition_times, loc_dt) - 1)\n            inf = self._transition_info[idx]\n            tzinfo = self._tzinfos[inf]\n            loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo))\n            if loc_dt.replace(tzinfo=None) == dt:\n                possible_loc_dt.add(loc_dt)\n\n        if len(possible_loc_dt) == 1:\n            return possible_loc_dt.pop()\n\n        # If there are no possibly correct timezones, we are attempting\n        # to convert a time that never happened - the time period jumped\n        # during the start-of-DST transition period.\n        if len(possible_loc_dt) == 0:\n            # If we refuse to guess, raise an exception.\n            if is_dst is None:\n                raise NonExistentTimeError(dt)\n\n            # If we are forcing the pre-DST side of the DST transition, we\n            # obtain the correct timezone by winding the clock forward a few\n            # hours.\n            elif is_dst:\n                return self.localize(\n                    dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6)\n\n            # If we are forcing the post-DST side of the DST transition, we\n            # obtain the correct timezone by winding the clock back.\n            else:\n                return self.localize(\n                    dt - timedelta(hours=6),\n                    is_dst=False) + timedelta(hours=6)\n\n        # If we get this far, we have multiple possible timezones - this\n        # is an ambiguous case occuring during the end-of-DST transition.\n\n        # If told to be strict, raise an exception since we have an\n        # ambiguous case\n        if is_dst is None:\n            raise AmbiguousTimeError(dt)\n\n        # Filter out the possiblilities that don't match the requested\n        # is_dst\n        filtered_possible_loc_dt = [\n            p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst\n        ]\n\n        # Hopefully we only have one possibility left. Return it.\n        if len(filtered_possible_loc_dt) == 1:\n            return filtered_possible_loc_dt[0]\n\n        if len(filtered_possible_loc_dt) == 0:\n            filtered_possible_loc_dt = list(possible_loc_dt)\n\n        # If we get this far, we have in a wierd timezone transition\n        # where the clocks have been wound back but is_dst is the same\n        # in both (eg. Europe/Warsaw 1915 when they switched to CET).\n        # At this point, we just have to guess unless we allow more\n        # hints to be passed in (such as the UTC offset or abbreviation),\n        # but that is just getting silly.\n        #\n        # Choose the earliest (by UTC) applicable timezone if is_dst=True\n        # Choose the latest (by UTC) applicable timezone if is_dst=False\n        # i.e., behave like end-of-DST transition\n        dates = {}  # utc -> local\n        for local_dt in filtered_possible_loc_dt:\n            utc_time = (\n                local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset)\n            assert utc_time not in dates\n            dates[utc_time] = local_dt\n        return dates[[min, max][not is_dst](dates)]\n\n    def utcoffset(self, dt, is_dst=None):\n        '''See datetime.tzinfo.utcoffset\n\n        The is_dst parameter may be used to remove ambiguity during DST\n        transitions.\n\n        >>> from pytz import timezone\n        >>> tz = timezone('America/St_Johns')\n        >>> ambiguous = datetime(2009, 10, 31, 23, 30)\n\n        >>> str(tz.utcoffset(ambiguous, is_dst=False))\n        '-1 day, 20:30:00'\n\n        >>> str(tz.utcoffset(ambiguous, is_dst=True))\n        '-1 day, 21:30:00'\n\n        >>> try:\n        ...     tz.utcoffset(ambiguous)\n        ... except AmbiguousTimeError:\n        ...     print('Ambiguous')\n        Ambiguous\n\n        '''\n        if dt is None:\n            return None\n        elif dt.tzinfo is not self:\n            dt = self.localize(dt, is_dst)\n            return dt.tzinfo._utcoffset\n        else:\n            return self._utcoffset\n\n    def dst(self, dt, is_dst=None):\n        '''See datetime.tzinfo.dst\n\n        The is_dst parameter may be used to remove ambiguity during DST\n        transitions.\n\n        >>> from pytz import timezone\n        >>> tz = timezone('America/St_Johns')\n\n        >>> normal = datetime(2009, 9, 1)\n\n        >>> str(tz.dst(normal))\n        '1:00:00'\n        >>> str(tz.dst(normal, is_dst=False))\n        '1:00:00'\n        >>> str(tz.dst(normal, is_dst=True))\n        '1:00:00'\n\n        >>> ambiguous = datetime(2009, 10, 31, 23, 30)\n\n        >>> str(tz.dst(ambiguous, is_dst=False))\n        '0:00:00'\n        >>> str(tz.dst(ambiguous, is_dst=True))\n        '1:00:00'\n        >>> try:\n        ...     tz.dst(ambiguous)\n        ... except AmbiguousTimeError:\n        ...     print('Ambiguous')\n        Ambiguous\n\n        '''\n        if dt is None:\n            return None\n        elif dt.tzinfo is not self:\n            dt = self.localize(dt, is_dst)\n            return dt.tzinfo._dst\n        else:\n            return self._dst\n\n    def tzname(self, dt, is_dst=None):\n        '''See datetime.tzinfo.tzname\n\n        The is_dst parameter may be used to remove ambiguity during DST\n        transitions.\n\n        >>> from pytz import timezone\n        >>> tz = timezone('America/St_Johns')\n\n        >>> normal = datetime(2009, 9, 1)\n\n        >>> tz.tzname(normal)\n        'NDT'\n        >>> tz.tzname(normal, is_dst=False)\n        'NDT'\n        >>> tz.tzname(normal, is_dst=True)\n        'NDT'\n\n        >>> ambiguous = datetime(2009, 10, 31, 23, 30)\n\n        >>> tz.tzname(ambiguous, is_dst=False)\n        'NST'\n        >>> tz.tzname(ambiguous, is_dst=True)\n        'NDT'\n        >>> try:\n        ...     tz.tzname(ambiguous)\n        ... except AmbiguousTimeError:\n        ...     print('Ambiguous')\n        Ambiguous\n        '''\n        if dt is None:\n            return self.zone\n        elif dt.tzinfo is not self:\n            dt = self.localize(dt, is_dst)\n            return dt.tzinfo._tzname\n        else:\n            return self._tzname\n\n    def __repr__(self):\n        if self._dst:\n            dst = 'DST'\n        else:\n            dst = 'STD'\n        if self._utcoffset > _notime:\n            return '<DstTzInfo %r %s+%s %s>' % (\n                self.zone, self._tzname, self._utcoffset, dst\n            )\n        else:\n            return '<DstTzInfo %r %s%s %s>' % (\n                self.zone, self._tzname, self._utcoffset, dst\n            )\n\n    def __reduce__(self):\n        # Special pickle to zone remains a singleton and to cope with\n        # database changes.\n        return pytz._p, (\n            self.zone,\n            _to_seconds(self._utcoffset),\n            _to_seconds(self._dst),\n            self._tzname\n        )\n\n\ndef unpickler(zone, utcoffset=None, dstoffset=None, tzname=None):\n    \"\"\"Factory function for unpickling pytz tzinfo instances.\n\n    This is shared for both StaticTzInfo and DstTzInfo instances, because\n    database changes could cause a zones implementation to switch between\n    these two base classes and we can't break pickles on a pytz version\n    upgrade.\n    \"\"\"\n    # Raises a KeyError if zone no longer exists, which should never happen\n    # and would be a bug.\n    tz = pytz.timezone(zone)\n\n    # A StaticTzInfo - just return it\n    if utcoffset is None:\n        return tz\n\n    # This pickle was created from a DstTzInfo. We need to\n    # determine which of the list of tzinfo instances for this zone\n    # to use in order to restore the state of any datetime instances using\n    # it correctly.\n    utcoffset = memorized_timedelta(utcoffset)\n    dstoffset = memorized_timedelta(dstoffset)\n    try:\n        return tz._tzinfos[(utcoffset, dstoffset, tzname)]\n    except KeyError:\n        # The particular state requested in this timezone no longer exists.\n        # This indicates a corrupt pickle, or the timezone database has been\n        # corrected violently enough to make this particular\n        # (utcoffset,dstoffset) no longer exist in the zone, or the\n        # abbreviation has been changed.\n        pass\n\n    # See if we can find an entry differing only by tzname. Abbreviations\n    # get changed from the initial guess by the database maintainers to\n    # match reality when this information is discovered.\n    for localized_tz in tz._tzinfos.values():\n        if (localized_tz._utcoffset == utcoffset and\n                localized_tz._dst == dstoffset):\n            return localized_tz\n\n    # This (utcoffset, dstoffset) information has been removed from the\n    # zone. Add it back. This might occur when the database maintainers have\n    # corrected incorrect information. datetime instances using this\n    # incorrect information will continue to do so, exactly as they were\n    # before being pickled. This is purely an overly paranoid safety net - I\n    # doubt this will ever been needed in real life.\n    inf = (utcoffset, dstoffset, tzname)\n    tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos)\n    return tz._tzinfos[inf]\n"
  },
  {
    "path": "tests/test_code/py/resolve_correct_class/rcc.py",
    "content": "def func_1():\n    print(\"global func_1\")\n\n\nclass Alpha():\n    def func_1(self):\n        print(\"alpha func_1\")\n        self.func_1()\n        func_1()\n        b = Beta()\n        b.func_2()\n\n    def func_2(self):\n        print(\"alpha func_2\")\n\n\nclass Beta():\n    def func_1(self):\n        print(\"beta func_1\")\n        al = Alpha()\n        al.func_2()\n\n    def func_2(self):\n        print(\"beta func_2\")\n"
  },
  {
    "path": "tests/test_code/py/simple_a/simple_a.py",
    "content": "def func_b():\n    pass\n\ndef func_a():\n    func_b()\n"
  },
  {
    "path": "tests/test_code/py/simple_b/simple_b.py",
    "content": "\"\"\"\nComments\n\"\"\"\n\n\ndef a():\n    b()\n\n\n# comments\ndef b():\n    a(\"\"\"STRC #\"\"\")\n\n\nclass c():\n    def d(a=\"String\"):\n        a(\"AnotherSTR\")\n\n\nc.d()\n"
  },
  {
    "path": "tests/test_code/py/subset_find_exception/two.py",
    "content": "def private():\n\tpass\n\nclass Abra:\n\tdef func():\n\t\tprivate()\n\nclass Cadabra:\n\tdef func():\n\t\tprivate()\n"
  },
  {
    "path": "tests/test_code/py/subset_find_exception/zero.py",
    "content": "def private():\n\tpass\n\nclass Abra:\n\tdef other():\n\t\tprivate()\n\n"
  },
  {
    "path": "tests/test_code/py/two_file_simple/file_a.py",
    "content": "from file_b import b\n\n\ndef a():\n    b()\n\n\nif __name__ == '__main__':\n    a()\n"
  },
  {
    "path": "tests/test_code/py/two_file_simple/file_b.py",
    "content": "def b():\n    print(\"here\")\n\n\ndef c():\n    pass\n"
  },
  {
    "path": "tests/test_code/py/two_file_simple/shouldntberead",
    "content": "def d():\n    e()\n"
  },
  {
    "path": "tests/test_code/py/weird_calls/weird_calls.py",
    "content": "def print_it(string):\n    print(string)\n\n\ndef func_a():\n    print_it(\"func_a\")\n\n\ndef func_b():\n    print_it(\"func_a\")\n\n\ndef func_c():\n    print_it(\"func_a\")\n\n\nfunc_dict = {\n    'func_a': func_a,\n    'func_b': func_b,\n    'func_c': func_c,\n}\n\nfunc_dict['func_a']()\nfunc_b()\n\n\ndef factory():\n    return lambda x: x**x\n\n\nfactory()(5)\n"
  },
  {
    "path": "tests/test_code/py/weird_encoding/weird_encoding.py",
    "content": "def a():\n    print(\"😅😂😘\")\n\n\na()\n"
  },
  {
    "path": "tests/test_code/py/weird_imports/weird_imports.py",
    "content": "from re import (search, match)\nimport re.match as mitch\n\n\ndef main():\n    a = b = search(\"abc\", \"def\")\n    c = mitch(\"abc\", \"def\")\n    return a, b, c\n\n\nmatch(\"abc\", \"def\")\nmain()\n"
  },
  {
    "path": "tests/test_code/rb/ambiguous_resolution/ambiguous_resolution.rb",
    "content": "class Abra\n    def magic()\n    end\n\n    def abra_it()\n    end\nend\n\nclass Cadabra\n    def magic()\n    end\n\n    def cadabra_it(a: nil)\n        a.abra_it()\n    end\nend\n\ndef main(cls)\n    obj = cls.new()\n    obj.magic()\n    obj.cadabra_it()\nend\n\nmain(Cadabra)\n"
  },
  {
    "path": "tests/test_code/rb/chains/chains.rb",
    "content": "def a\n    puts \"A\"\n    b\nend\n\ndef b\n    puts \"B\"\n    a\nend\n\nclass Cls\n    def initialize\n        puts \"init\"\n    end\n\n    def a\n        puts \"a\"\n        self\n    end\n\n    def b\n        puts \"b\"\n        return self\n    end\nend\n\ndef c\n    obj = Cls.new()\n    obj.a.b.a.c\nend\n\na\nb\nc\n"
  },
  {
    "path": "tests/test_code/rb/doublecolon/doublecolon.rb",
    "content": "# def func_a\n#     puts \"global func_a\"\n# end\n\n# def func_b\n#     puts \"global func_b\"\n# end\n\nclass Class1\n    def self.func_a\n        Class2::func_b()\n        puts \"Class1::func_a\"\n    end\n\n    def self.func_b\n        func_a()\n        puts \"Class1::func_b\"\n    end\nend\n\n\nclass Class2\n    def self.func_a\n        Class1::func_b()\n        puts \"Class2::func_a\"\n    end\n\n    def self.func_b\n        func_a()\n        puts \"Class2::func_b\"\n    end\nend\n\na = 5\nClass2::func_b\n"
  },
  {
    "path": "tests/test_code/rb/inheritance_2/inheritance_2.rb",
    "content": "# from https://launchschool.com/books/oo_ruby/read/inheritance\n\ndef speak\n  puts \"WOOF\"\nend\n\nclass Animal\n  def speak\n    puts \"Hello!\"\n  end\nend\n\nclass GoodDog < Animal\nend\n\nclass Cat < Animal\n  def meow\n    speak\n  end\nend\n\nsparky = GoodDog.new\npaws = Cat.new\nputs sparky.speak\nputs paws.meow\n"
  },
  {
    "path": "tests/test_code/rb/instance_methods/instance_methods.rb",
    "content": "def main\n    puts(\"This is not called\")\nend\n\nclass Abra\n    def main\n        def nested\n            puts(\"This will reference the main on Abra\")\n            main\n        end\n        nested\n        puts(\"Hello from main\")\n    end\n\n    def main2\n        def nested2\n            main()\n        end\n    end\nend\n\na = Abra.new()\na.main()\na.nested()\na.main2()\na.nested2()\n"
  },
  {
    "path": "tests/test_code/rb/modules/modules.rb",
    "content": "# from https://ruby-doc.com/docs/ProgrammingRuby/html/tut_modules.html\n\ndef majorNum\nend\n\ndef pentaNum\nend\n\nmodule MajorScales\n  def majorNum\n    @numNotes = 7 if @numNotes.nil?\n    @numNotes # Return 7\n  end\nend\n\nmodule PentatonicScales\n  def pentaNum\n    @numNotes = 5 if @numNotes.nil?\n    @numNotes # Return 5?\n  end\nend\n\nclass ScaleDemo\n  include MajorScales\n  include PentatonicScales\n  def initialize\n    puts majorNum # Should be 7\n    puts pentaNum # Should be 5\n  end\nend\n\nclass ScaleDemoLimited\n  include MajorScales\n  def initialize\n    puts majorNum # Should be 7\n  end\nend\n\nsd = ScaleDemo.new()\nmajorNum\n"
  },
  {
    "path": "tests/test_code/rb/nested/nested.rb",
    "content": "module Mod\n    def func_1\n        puts(\"func_1 outer\")\n    end\n\n    def func_2\n        puts(\"func_2 outer\")\n    end\n\n    class Nested\n        def initialize\n            puts(\"hello world\")\n            func_1\n        end\n        def func_1\n            puts(\"func_1\")\n            Mod::func_1\n        end\n        def func_2\n            puts(\"func_2\")\n            func_1()\n            Mod::func_2\n        end\n    end\nend\n\ndef func_1\n    puts(\"func_1 top_level\")\n    func_2\nend\n\ndef func_2\n    puts(\"func_2 top_level\")\n    func_1\nend\n\nobj = Mod::Nested.new()\nobj.func_2()\nMod::func_1()\n"
  },
  {
    "path": "tests/test_code/rb/nested_classes/nested_classes.rb",
    "content": "class Mod\n    def func_1\n        puts(\"func_1 outer\")\n    end\n\n    def func_2\n        puts(\"func_2 outer\")\n    end\n\n    class Nested\n        def initialize\n            puts(\"hello world\")\n            func_1\n        end\n        def func_1\n            puts(\"func_1\")\n            Mod::func_1\n        end\n        def func_2\n            puts(\"func_2\")\n            func_1()\n            Mod::func_2\n        end\n    end\nend\n\ndef func_1\n    puts(\"func_1 top_level\")\n    func_2\nend\n\ndef func_2\n    puts(\"func_2 top_level\")\n    func_1\nend\n\nobj = Mod::Nested.new()\nobj.func_2()\nouter_obj = Mod.new()\nouter_obj.func_1()\n"
  },
  {
    "path": "tests/test_code/rb/onelinefile/onelinefile.rb",
    "content": "adder = lambda { |a, b| a + b}\n"
  },
  {
    "path": "tests/test_code/rb/public_suffix/public_suffix/domain.rb",
    "content": "# frozen_string_literal: true\n\n# = Public Suffix\n#\n# Domain name parser based on the Public Suffix List.\n#\n# Copyright (c) 2009-2020 Simone Carletti <weppos@weppos.net>\n\nmodule PublicSuffix\n\n  # Domain represents a domain name, composed by a TLD, SLD and TRD.\n  class Domain\n\n    # Splits a string into the labels, that is the dot-separated parts.\n    #\n    # The input is not validated, but it is assumed to be a valid domain name.\n    #\n    # @example\n    #\n    #   name_to_labels('example.com')\n    #   # => ['example', 'com']\n    #\n    #   name_to_labels('example.co.uk')\n    #   # => ['example', 'co', 'uk']\n    #\n    # @param  name [String, #to_s] The domain name to split.\n    # @return [Array<String>]\n    def self.name_to_labels(name)\n      name.to_s.split(DOT)\n    end\n\n\n    attr_reader :tld, :sld, :trd\n\n    # Creates and returns a new {PublicSuffix::Domain} instance.\n    #\n    # @overload initialize(tld)\n    #   Initializes with a +tld+.\n    #   @param [String] tld The TLD (extension)\n    # @overload initialize(tld, sld)\n    #   Initializes with a +tld+ and +sld+.\n    #   @param [String] tld The TLD (extension)\n    #   @param [String] sld The TRD (domain)\n    # @overload initialize(tld, sld, trd)\n    #   Initializes with a +tld+, +sld+ and +trd+.\n    #   @param [String] tld The TLD (extension)\n    #   @param [String] sld The SLD (domain)\n    #   @param [String] trd The TRD (subdomain)\n    #\n    # @yield [self] Yields on self.\n    # @yieldparam [PublicSuffix::Domain] self The newly creates instance\n    #\n    # @example Initialize with a TLD\n    #   PublicSuffix::Domain.new(\"com\")\n    #   # => #<PublicSuffix::Domain @tld=\"com\">\n    #\n    # @example Initialize with a TLD and SLD\n    #   PublicSuffix::Domain.new(\"com\", \"example\")\n    #   # => #<PublicSuffix::Domain @tld=\"com\", @trd=nil>\n    #\n    # @example Initialize with a TLD, SLD and TRD\n    #   PublicSuffix::Domain.new(\"com\", \"example\", \"wwww\")\n    #   # => #<PublicSuffix::Domain @tld=\"com\", @trd=nil, @sld=\"example\">\n    #\n    def initialize(*args)\n      @tld, @sld, @trd = args\n      yield(self) if block_given?\n    end\n\n    # Returns a string representation of this object.\n    #\n    # @return [String]\n    def to_s\n      name\n    end\n\n    # Returns an array containing the domain parts.\n    #\n    # @return [Array<String, nil>]\n    #\n    # @example\n    #\n    #   PublicSuffix::Domain.new(\"google.com\").to_a\n    #   # => [nil, \"google\", \"com\"]\n    #\n    #   PublicSuffix::Domain.new(\"www.google.com\").to_a\n    #   # => [nil, \"google\", \"com\"]\n    #\n    def to_a\n      [@trd, @sld, @tld]\n    end\n\n    # Returns the full domain name.\n    #\n    # @return [String]\n    #\n    # @example Gets the domain name of a domain\n    #   PublicSuffix::Domain.new(\"com\", \"google\").name\n    #   # => \"google.com\"\n    #\n    # @example Gets the domain name of a subdomain\n    #   PublicSuffix::Domain.new(\"com\", \"google\", \"www\").name\n    #   # => \"www.google.com\"\n    #\n    def name\n      [@trd, @sld, @tld].compact.join(DOT)\n    end\n\n    # Returns a domain-like representation of this object\n    # if the object is a {#domain?}, <tt>nil</tt> otherwise.\n    #\n    #   PublicSuffix::Domain.new(\"com\").domain\n    #   # => nil\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\").domain\n    #   # => \"google.com\"\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\", \"www\").domain\n    #   # => \"www.google.com\"\n    #\n    # This method doesn't validate the input. It handles the domain\n    # as a valid domain name and simply applies the necessary transformations.\n    #\n    # This method returns a FQD, not just the domain part.\n    # To get the domain part, use <tt>#sld</tt> (aka second level domain).\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\", \"www\").domain\n    #   # => \"google.com\"\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\", \"www\").sld\n    #   # => \"google\"\n    #\n    # @see #domain?\n    # @see #subdomain\n    #\n    # @return [String]\n    def domain\n      [@sld, @tld].join(DOT) if domain?\n    end\n\n    # Returns a subdomain-like representation of this object\n    # if the object is a {#subdomain?}, <tt>nil</tt> otherwise.\n    #\n    #   PublicSuffix::Domain.new(\"com\").subdomain\n    #   # => nil\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\").subdomain\n    #   # => nil\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\", \"www\").subdomain\n    #   # => \"www.google.com\"\n    #\n    # This method doesn't validate the input. It handles the domain\n    # as a valid domain name and simply applies the necessary transformations.\n    #\n    # This method returns a FQD, not just the subdomain part.\n    # To get the subdomain part, use <tt>#trd</tt> (aka third level domain).\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\", \"www\").subdomain\n    #   # => \"www.google.com\"\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\", \"www\").trd\n    #   # => \"www\"\n    #\n    # @see #subdomain?\n    # @see #domain\n    #\n    # @return [String]\n    def subdomain\n      [@trd, @sld, @tld].join(DOT) if subdomain?\n    end\n\n    # Checks whether <tt>self</tt> looks like a domain.\n    #\n    # This method doesn't actually validate the domain.\n    # It only checks whether the instance contains\n    # a value for the {#tld} and {#sld} attributes.\n    #\n    # @example\n    #\n    #   PublicSuffix::Domain.new(\"com\").domain?\n    #   # => false\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\").domain?\n    #   # => true\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\", \"www\").domain?\n    #   # => true\n    #\n    #   # This is an invalid domain, but returns true\n    #   # because this method doesn't validate the content.\n    #   PublicSuffix::Domain.new(\"com\", nil).domain?\n    #   # => true\n    #\n    # @see #subdomain?\n    #\n    # @return [Boolean]\n    def domain?\n      !(@tld.nil? || @sld.nil?)\n    end\n\n    # Checks whether <tt>self</tt> looks like a subdomain.\n    #\n    # This method doesn't actually validate the subdomain.\n    # It only checks whether the instance contains\n    # a value for the {#tld}, {#sld} and {#trd} attributes.\n    # If you also want to validate the domain,\n    # use {#valid_subdomain?} instead.\n    #\n    # @example\n    #\n    #   PublicSuffix::Domain.new(\"com\").subdomain?\n    #   # => false\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\").subdomain?\n    #   # => false\n    #\n    #   PublicSuffix::Domain.new(\"com\", \"google\", \"www\").subdomain?\n    #   # => true\n    #\n    #   # This is an invalid domain, but returns true\n    #   # because this method doesn't validate the content.\n    #   PublicSuffix::Domain.new(\"com\", \"example\", nil).subdomain?\n    #   # => true\n    #\n    # @see #domain?\n    #\n    # @return [Boolean]\n    def subdomain?\n      !(@tld.nil? || @sld.nil? || @trd.nil?)\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "tests/test_code/rb/public_suffix/public_suffix/errors.rb",
    "content": "# frozen_string_literal: true\n\n# = Public Suffix\n#\n# Domain name parser based on the Public Suffix List.\n#\n# Copyright (c) 2009-2020 Simone Carletti <weppos@weppos.net>\n\nmodule PublicSuffix\n\n  class Error < StandardError\n  end\n\n  # Raised when trying to parse an invalid name.\n  # A name is considered invalid when no rule is found in the definition list.\n  #\n  # @example\n  #\n  #   PublicSuffix.parse(\"nic.test\")\n  #   # => PublicSuffix::DomainInvalid\n  #\n  #   PublicSuffix.parse(\"http://www.nic.it\")\n  #   # => PublicSuffix::DomainInvalid\n  #\n  class DomainInvalid < Error\n  end\n\n  # Raised when trying to parse a name that matches a suffix.\n  #\n  # @example\n  #\n  #   PublicSuffix.parse(\"nic.do\")\n  #   # => PublicSuffix::DomainNotAllowed\n  #\n  #   PublicSuffix.parse(\"www.nic.do\")\n  #   # => PublicSuffix::Domain\n  #\n  class DomainNotAllowed < DomainInvalid\n  end\n\nend\n"
  },
  {
    "path": "tests/test_code/rb/public_suffix/public_suffix/list.rb",
    "content": "# frozen_string_literal: true\n\n# = Public Suffix\n#\n# Domain name parser based on the Public Suffix List.\n#\n# Copyright (c) 2009-2020 Simone Carletti <weppos@weppos.net>\n\nmodule PublicSuffix\n\n  # A {PublicSuffix::List} is a collection of one\n  # or more {PublicSuffix::Rule}.\n  #\n  # Given a {PublicSuffix::List},\n  # you can add or remove {PublicSuffix::Rule},\n  # iterate all items in the list or search for the first rule\n  # which matches a specific domain name.\n  #\n  #   # Create a new list\n  #   list =  PublicSuffix::List.new\n  #\n  #   # Push two rules to the list\n  #   list << PublicSuffix::Rule.factory(\"it\")\n  #   list << PublicSuffix::Rule.factory(\"com\")\n  #\n  #   # Get the size of the list\n  #   list.size\n  #   # => 2\n  #\n  #   # Search for the rule matching given domain\n  #   list.find(\"example.com\")\n  #   # => #<PublicSuffix::Rule::Normal>\n  #   list.find(\"example.org\")\n  #   # => nil\n  #\n  # You can create as many {PublicSuffix::List} you want.\n  # The {PublicSuffix::List.default} rule list is used\n  # to tokenize and validate a domain.\n  #\n  class List\n\n    DEFAULT_LIST_PATH = File.expand_path(\"../../data/list.txt\", __dir__)\n\n    # Gets the default rule list.\n    #\n    # Initializes a new {PublicSuffix::List} parsing the content\n    # of {PublicSuffix::List.default_list_content}, if required.\n    #\n    # @return [PublicSuffix::List]\n    def self.default(**options)\n      @default ||= parse(File.read(DEFAULT_LIST_PATH), **options)\n    end\n\n    # Sets the default rule list to +value+.\n    #\n    # @param  value [PublicSuffix::List] the new list\n    # @return [PublicSuffix::List]\n    def self.default=(value)\n      @default = value\n    end\n\n    # Parse given +input+ treating the content as Public Suffix List.\n    #\n    # See http://publicsuffix.org/format/ for more details about input format.\n    #\n    # @param  string [#each_line] the list to parse\n    # @param  private_domains [Boolean] whether to ignore the private domains section\n    # @return [PublicSuffix::List]\n    def self.parse(input, private_domains: true)\n      comment_token = \"//\"\n      private_token = \"===BEGIN PRIVATE DOMAINS===\"\n      section = nil # 1 == ICANN, 2 == PRIVATE\n\n      new do |list|\n        input.each_line do |line|\n          line.strip!\n          case # rubocop:disable Style/EmptyCaseCondition\n\n          # skip blank lines\n          when line.empty?\n            next\n\n          # include private domains or stop scanner\n          when line.include?(private_token)\n            break if !private_domains\n\n            section = 2\n\n          # skip comments\n          when line.start_with?(comment_token)\n            next\n\n          else\n            list.add(Rule.factory(line, private: section == 2))\n\n          end\n        end\n      end\n    end\n\n\n    # Initializes an empty {PublicSuffix::List}.\n    #\n    # @yield [self] Yields on self.\n    # @yieldparam [PublicSuffix::List] self The newly created instance.\n    def initialize\n      @rules = {}\n      yield(self) if block_given?\n    end\n\n\n    # Checks whether two lists are equal.\n    #\n    # List <tt>one</tt> is equal to <tt>two</tt>, if <tt>two</tt> is an instance of\n    # {PublicSuffix::List} and each +PublicSuffix::Rule::*+\n    # in list <tt>one</tt> is available in list <tt>two</tt>, in the same order.\n    #\n    # @param  other [PublicSuffix::List] the List to compare\n    # @return [Boolean]\n    def ==(other)\n      return false unless other.is_a?(List)\n\n      equal?(other) || @rules == other.rules\n    end\n    alias eql? ==\n\n    # Iterates each rule in the list.\n    def each(&block)\n      Enumerator.new do |y|\n        @rules.each do |key, node|\n          y << entry_to_rule(node, key)\n        end\n      end.each(&block)\n    end\n\n\n    # Adds the given object to the list and optionally refreshes the rule index.\n    #\n    # @param  rule [PublicSuffix::Rule::*] the rule to add to the list\n    # @return [self]\n    def add(rule)\n      @rules[rule.value] = rule_to_entry(rule)\n      self\n    end\n    alias << add\n\n    # Gets the number of rules in the list.\n    #\n    # @return [Integer]\n    def size\n      @rules.size\n    end\n\n    # Checks whether the list is empty.\n    #\n    # @return [Boolean]\n    def empty?\n      @rules.empty?\n    end\n\n    # Removes all rules.\n    #\n    # @return [self]\n    def clear\n      @rules.clear\n      self\n    end\n\n    # Finds and returns the rule corresponding to the longest public suffix for the hostname.\n    #\n    # @param  name [#to_s] the hostname\n    # @param  default [PublicSuffix::Rule::*] the default rule to return in case no rule matches\n    # @return [PublicSuffix::Rule::*]\n    def find(name, default: default_rule, **options)\n      rule = select(name, **options).inject do |l, r|\n        return r if r.class == Rule::Exception\n\n        l.length > r.length ? l : r\n      end\n      rule || default\n    end\n\n    # Selects all the rules matching given hostame.\n    #\n    # If `ignore_private` is set to true, the algorithm will skip the rules that are flagged as\n    # private domain. Note that the rules will still be part of the loop.\n    # If you frequently need to access lists ignoring the private domains,\n    # you should create a list that doesn't include these domains setting the\n    # `private_domains: false` option when calling {.parse}.\n    #\n    # Note that this method is currently private, as you should not rely on it. Instead,\n    # the public interface is {#find}. The current internal algorithm allows to return all\n    # matching rules, but different data structures may not be able to do it, and instead would\n    # return only the match. For this reason, you should rely on {#find}.\n    #\n    # @param  name [#to_s] the hostname\n    # @param  ignore_private [Boolean]\n    # @return [Array<PublicSuffix::Rule::*>]\n    def select(name, ignore_private: false)\n      name = name.to_s\n\n      parts = name.split(DOT).reverse!\n      index = 0\n      query = parts[index]\n      rules = []\n\n      loop do\n        match = @rules[query]\n        rules << entry_to_rule(match, query) if !match.nil? && (ignore_private == false || match.private == false)\n\n        index += 1\n        break if index >= parts.size\n\n        query = parts[index] + DOT + query\n      end\n\n      rules\n    end\n    private :select\n\n    # Gets the default rule.\n    #\n    # @see PublicSuffix::Rule.default_rule\n    #\n    # @return [PublicSuffix::Rule::*]\n    def default_rule\n      PublicSuffix::Rule.default\n    end\n\n\n    protected\n\n    attr_reader :rules\n\n\n    private\n\n    def entry_to_rule(entry, value)\n      entry.type.new(value: value, length: entry.length, private: entry.private)\n    end\n\n    def rule_to_entry(rule)\n      Rule::Entry.new(rule.class, rule.length, rule.private)\n    end\n\n  end\nend\n"
  },
  {
    "path": "tests/test_code/rb/public_suffix/public_suffix/rule.rb",
    "content": "# frozen_string_literal: true\n\n# = Public Suffix\n#\n# Domain name parser based on the Public Suffix List.\n#\n# Copyright (c) 2009-2020 Simone Carletti <weppos@weppos.net>\n\nmodule PublicSuffix\n\n  # A Rule is a special object which holds a single definition\n  # of the Public Suffix List.\n  #\n  # There are 3 types of rules, each one represented by a specific\n  # subclass within the +PublicSuffix::Rule+ namespace.\n  #\n  # To create a new Rule, use the {PublicSuffix::Rule#factory} method.\n  #\n  #   PublicSuffix::Rule.factory(\"ar\")\n  #   # => #<PublicSuffix::Rule::Normal>\n  #\n  module Rule\n\n    # @api internal\n    Entry = Struct.new(:type, :length, :private) # rubocop:disable Lint/StructNewOverride\n\n    # = Abstract rule class\n    #\n    # This represent the base class for a Rule definition\n    # in the {Public Suffix List}[https://publicsuffix.org].\n    #\n    # This is intended to be an Abstract class\n    # and you shouldn't create a direct instance. The only purpose\n    # of this class is to expose a common interface\n    # for all the available subclasses.\n    #\n    # * {PublicSuffix::Rule::Normal}\n    # * {PublicSuffix::Rule::Exception}\n    # * {PublicSuffix::Rule::Wildcard}\n    #\n    # ## Properties\n    #\n    # A rule is composed by 4 properties:\n    #\n    # value   - A normalized version of the rule name.\n    #           The normalization process depends on rule tpe.\n    #\n    # Here's an example\n    #\n    #   PublicSuffix::Rule.factory(\"*.google.com\")\n    #   #<PublicSuffix::Rule::Wildcard:0x1015c14b0\n    #       @value=\"google.com\"\n    #   >\n    #\n    # ## Rule Creation\n    #\n    # The best way to create a new rule is passing the rule name\n    # to the <tt>PublicSuffix::Rule.factory</tt> method.\n    #\n    #   PublicSuffix::Rule.factory(\"com\")\n    #   # => PublicSuffix::Rule::Normal\n    #\n    #   PublicSuffix::Rule.factory(\"*.com\")\n    #   # => PublicSuffix::Rule::Wildcard\n    #\n    # This method will detect the rule type and create an instance\n    # from the proper rule class.\n    #\n    # ## Rule Usage\n    #\n    # A rule describes the composition of a domain name and explains how to tokenize\n    # the name into tld, sld and trd.\n    #\n    # To use a rule, you first need to be sure the name you want to tokenize\n    # can be handled by the current rule.\n    # You can use the <tt>#match?</tt> method.\n    #\n    #   rule = PublicSuffix::Rule.factory(\"com\")\n    #\n    #   rule.match?(\"google.com\")\n    #   # => true\n    #\n    #   rule.match?(\"google.com\")\n    #   # => false\n    #\n    # Rule order is significant. A name can match more than one rule.\n    # See the {Public Suffix Documentation}[http://publicsuffix.org/format/]\n    # to learn more about rule priority.\n    #\n    # When you have the right rule, you can use it to tokenize the domain name.\n    #\n    #   rule = PublicSuffix::Rule.factory(\"com\")\n    #\n    #   rule.decompose(\"google.com\")\n    #   # => [\"google\", \"com\"]\n    #\n    #   rule.decompose(\"www.google.com\")\n    #   # => [\"www.google\", \"com\"]\n    #\n    # @abstract\n    #\n    class Base\n\n      # @return [String] the rule definition\n      attr_reader :value\n\n      # @return [String] the length of the rule\n      attr_reader :length\n\n      # @return [Boolean] true if the rule is a private domain\n      attr_reader :private\n\n\n      # Initializes a new rule from the content.\n      #\n      # @param  content [String] the content of the rule\n      # @param  private [Boolean]\n      def self.build(content, private: false)\n        new(value: content, private: private)\n      end\n\n      # Initializes a new rule.\n      #\n      # @param  value [String]\n      # @param  private [Boolean]\n      def initialize(value:, length: nil, private: false)\n        @value    = value.to_s\n        @length   = length || @value.count(DOT) + 1\n        @private  = private\n      end\n\n      # Checks whether this rule is equal to <tt>other</tt>.\n      #\n      # @param  [PublicSuffix::Rule::*] other The rule to compare\n      # @return [Boolean]\n      #   Returns true if this rule and other are instances of the same class\n      #   and has the same value, false otherwise.\n      def ==(other)\n        equal?(other) || (self.class == other.class && value == other.value)\n      end\n      alias eql? ==\n\n      # Checks if this rule matches +name+.\n      #\n      # A domain name is said to match a rule if and only if\n      # all of the following conditions are met:\n      #\n      # - When the domain and rule are split into corresponding labels,\n      #   that the domain contains as many or more labels than the rule.\n      # - Beginning with the right-most labels of both the domain and the rule,\n      #   and continuing for all labels in the rule, one finds that for every pair,\n      #   either they are identical, or that the label from the rule is \"*\".\n      #\n      # @see https://publicsuffix.org/list/\n      #\n      # @example\n      #   PublicSuffix::Rule.factory(\"com\").match?(\"example.com\")\n      #   # => true\n      #   PublicSuffix::Rule.factory(\"com\").match?(\"example.net\")\n      #   # => false\n      #\n      # @param  name [String] the domain name to check\n      # @return [Boolean]\n      def match?(name)\n        # Note: it works because of the assumption there are no\n        # rules like foo.*.com. If the assumption is incorrect,\n        # we need to properly walk the input and skip parts according\n        # to wildcard component.\n        diff = name.chomp(value)\n        diff.empty? || diff.end_with?(DOT)\n      end\n\n      # @abstract\n      def parts\n        raise NotImplementedError\n      end\n\n      # @abstract\n      # @param  [String, #to_s] name The domain name to decompose\n      # @return [Array<String, nil>]\n      def decompose(*)\n        raise NotImplementedError\n      end\n\n    end\n\n    # Normal represents a standard rule (e.g. com).\n    class Normal < Base\n\n      # Gets the original rule definition.\n      #\n      # @return [String] The rule definition.\n      def rule\n        value\n      end\n\n      # Decomposes the domain name according to rule properties.\n      #\n      # @param  [String, #to_s] name The domain name to decompose\n      # @return [Array<String>] The array with [trd + sld, tld].\n      def decompose(domain)\n        suffix = parts.join('\\.')\n        matches = domain.to_s.match(/^(.*)\\.(#{suffix})$/)\n        matches ? matches[1..2] : [nil, nil]\n      end\n\n      # dot-split rule value and returns all rule parts\n      # in the order they appear in the value.\n      #\n      # @return [Array<String>]\n      def parts\n        @value.split(DOT)\n      end\n\n    end\n\n    # Wildcard represents a wildcard rule (e.g. *.co.uk).\n    class Wildcard < Base\n\n      # Initializes a new rule from the content.\n      #\n      # @param  content [String] the content of the rule\n      # @param  private [Boolean]\n      def self.build(content, private: false)\n        new(value: content.to_s[2..-1], private: private)\n      end\n\n      # Initializes a new rule.\n      #\n      # @param  value [String]\n      # @param  private [Boolean]\n      def initialize(value:, length: nil, private: false)\n        super(value: value, length: length, private: private)\n        length or @length += 1 # * counts as 1\n      end\n\n      # Gets the original rule definition.\n      #\n      # @return [String] The rule definition.\n      def rule\n        value == \"\" ? STAR : STAR + DOT + value\n      end\n\n      # Decomposes the domain name according to rule properties.\n      #\n      # @param  [String, #to_s] name The domain name to decompose\n      # @return [Array<String>] The array with [trd + sld, tld].\n      def decompose(domain)\n        suffix = ([\".*?\"] + parts).join('\\.')\n        matches = domain.to_s.match(/^(.*)\\.(#{suffix})$/)\n        matches ? matches[1..2] : [nil, nil]\n      end\n\n      # dot-split rule value and returns all rule parts\n      # in the order they appear in the value.\n      #\n      # @return [Array<String>]\n      def parts\n        @value.split(DOT)\n      end\n\n    end\n\n    # Exception represents an exception rule (e.g. !parliament.uk).\n    class Exception < Base\n\n      # Initializes a new rule from the content.\n      #\n      # @param  content [String] the content of the rule\n      # @param  private [Boolean]\n      def self.build(content, private: false)\n        new(value: content.to_s[1..-1], private: private)\n      end\n\n      # Gets the original rule definition.\n      #\n      # @return [String] The rule definition.\n      def rule\n        BANG + value\n      end\n\n      # Decomposes the domain name according to rule properties.\n      #\n      # @param  [String, #to_s] name The domain name to decompose\n      # @return [Array<String>] The array with [trd + sld, tld].\n      def decompose(domain)\n        suffix = parts.join('\\.')\n        matches = domain.to_s.match(/^(.*)\\.(#{suffix})$/)\n        matches ? matches[1..2] : [nil, nil]\n      end\n\n      # dot-split rule value and returns all rule parts\n      # in the order they appear in the value.\n      # The leftmost label is not considered a label.\n      #\n      # See http://publicsuffix.org/format/:\n      # If the prevailing rule is a exception rule,\n      # modify it by removing the leftmost label.\n      #\n      # @return [Array<String>]\n      def parts\n        @value.split(DOT)[1..-1]\n      end\n\n    end\n\n\n    # Takes the +name+ of the rule, detects the specific rule class\n    # and creates a new instance of that class.\n    # The +name+ becomes the rule +value+.\n    #\n    # @example Creates a Normal rule\n    #   PublicSuffix::Rule.factory(\"ar\")\n    #   # => #<PublicSuffix::Rule::Normal>\n    #\n    # @example Creates a Wildcard rule\n    #   PublicSuffix::Rule.factory(\"*.ar\")\n    #   # => #<PublicSuffix::Rule::Wildcard>\n    #\n    # @example Creates an Exception rule\n    #   PublicSuffix::Rule.factory(\"!congresodelalengua3.ar\")\n    #   # => #<PublicSuffix::Rule::Exception>\n    #\n    # @param  [String] content The rule content.\n    # @return [PublicSuffix::Rule::*] A rule instance.\n    def self.factory(content, private: false)\n      case content.to_s[0, 1]\n      when STAR\n        Wildcard\n      when BANG\n        Exception\n      else\n        Normal\n      end.build(content, private: private)\n    end\n\n    # The default rule to use if no rule match.\n    #\n    # The default rule is \"*\". From https://publicsuffix.org/list/:\n    #\n    # > If no rules match, the prevailing rule is \"*\".\n    #\n    # @return [PublicSuffix::Rule::Wildcard] The default rule.\n    def self.default\n      factory(STAR)\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "tests/test_code/rb/public_suffix/public_suffix/version.rb",
    "content": "# frozen_string_literal: true\n\n#\n# = Public Suffix\n#\n# Domain name parser based on the Public Suffix List.\n#\n# Copyright (c) 2009-2020 Simone Carletti <weppos@weppos.net>\n\nmodule PublicSuffix\n  # The current library version.\n  VERSION = \"4.0.6\"\nend\n"
  },
  {
    "path": "tests/test_code/rb/public_suffix/public_suffix.rb",
    "content": "# frozen_string_literal: true\n\n# = Public Suffix\n#\n# Domain name parser based on the Public Suffix List.\n#\n# Copyright (c) 2009-2020 Simone Carletti <weppos@weppos.net>\n\nrequire_relative \"public_suffix/domain\"\nrequire_relative \"public_suffix/version\"\nrequire_relative \"public_suffix/errors\"\nrequire_relative \"public_suffix/rule\"\nrequire_relative \"public_suffix/list\"\n\n# PublicSuffix is a Ruby domain name parser based on the Public Suffix List.\n#\n# The [Public Suffix List](https://publicsuffix.org) is a cross-vendor initiative\n# to provide an accurate list of domain name suffixes.\n#\n# The Public Suffix List is an initiative of the Mozilla Project,\n# but is maintained as a community resource. It is available for use in any software,\n# but was originally created to meet the needs of browser manufacturers.\nmodule PublicSuffix\n\n  DOT   = \".\"\n  BANG  = \"!\"\n  STAR  = \"*\"\n\n  # Parses +name+ and returns the {PublicSuffix::Domain} instance.\n  #\n  # @example Parse a valid domain\n  #   PublicSuffix.parse(\"google.com\")\n  #   # => #<PublicSuffix::Domain:0x007fec2e51e588 @sld=\"google\", @tld=\"com\", @trd=nil>\n  #\n  # @example Parse a valid subdomain\n  #   PublicSuffix.parse(\"www.google.com\")\n  #   # => #<PublicSuffix::Domain:0x007fec276d4cf8 @sld=\"google\", @tld=\"com\", @trd=\"www\">\n  #\n  # @example Parse a fully qualified domain\n  #   PublicSuffix.parse(\"google.com.\")\n  #   # => #<PublicSuffix::Domain:0x007fec257caf38 @sld=\"google\", @tld=\"com\", @trd=nil>\n  #\n  # @example Parse a fully qualified domain (subdomain)\n  #   PublicSuffix.parse(\"www.google.com.\")\n  #   # => #<PublicSuffix::Domain:0x007fec27b6bca8 @sld=\"google\", @tld=\"com\", @trd=\"www\">\n  #\n  # @example Parse an invalid (unlisted) domain\n  #   PublicSuffix.parse(\"x.yz\")\n  #   # => #<PublicSuffix::Domain:0x007fec2f49bec0 @sld=\"x\", @tld=\"yz\", @trd=nil>\n  #\n  # @example Parse an invalid (unlisted) domain with strict checking (without applying the default * rule)\n  #   PublicSuffix.parse(\"x.yz\", default_rule: nil)\n  #   # => PublicSuffix::DomainInvalid: `x.yz` is not a valid domain\n  #\n  # @example Parse an URL (not supported, only domains)\n  #   PublicSuffix.parse(\"http://www.google.com\")\n  #   # => PublicSuffix::DomainInvalid: http://www.google.com is not expected to contain a scheme\n  #\n  #\n  # @param  [String, #to_s] name The domain name or fully qualified domain name to parse.\n  # @param  [PublicSuffix::List] list The rule list to search, defaults to the default {PublicSuffix::List}\n  # @param  [Boolean] ignore_private\n  # @return [PublicSuffix::Domain]\n  #\n  # @raise [PublicSuffix::DomainInvalid]\n  #   If domain is not a valid domain.\n  # @raise [PublicSuffix::DomainNotAllowed]\n  #   If a rule for +domain+ is found, but the rule doesn't allow +domain+.\n  def self.parse(name, list: List.default, default_rule: list.default_rule, ignore_private: false)\n    what = normalize(name)\n    raise what if what.is_a?(DomainInvalid)\n\n    rule = list.find(what, default: default_rule, ignore_private: ignore_private)\n\n    # rubocop:disable Style/IfUnlessModifier\n    if rule.nil?\n      raise DomainInvalid, \"`#{what}` is not a valid domain\"\n    end\n    if rule.decompose(what).last.nil?\n      raise DomainNotAllowed, \"`#{what}` is not allowed according to Registry policy\"\n    end\n\n    # rubocop:enable Style/IfUnlessModifier\n\n    decompose(what, rule)\n  end\n\n  # Checks whether +domain+ is assigned and allowed, without actually parsing it.\n  #\n  # This method doesn't care whether domain is a domain or subdomain.\n  # The validation is performed using the default {PublicSuffix::List}.\n  #\n  # @example Validate a valid domain\n  #   PublicSuffix.valid?(\"example.com\")\n  #   # => true\n  #\n  # @example Validate a valid subdomain\n  #   PublicSuffix.valid?(\"www.example.com\")\n  #   # => true\n  #\n  # @example Validate a not-listed domain\n  #   PublicSuffix.valid?(\"example.tldnotlisted\")\n  #   # => true\n  #\n  # @example Validate a not-listed domain with strict checking (without applying the default * rule)\n  #   PublicSuffix.valid?(\"example.tldnotlisted\")\n  #   # => true\n  #   PublicSuffix.valid?(\"example.tldnotlisted\", default_rule: nil)\n  #   # => false\n  #\n  # @example Validate a fully qualified domain\n  #   PublicSuffix.valid?(\"google.com.\")\n  #   # => true\n  #   PublicSuffix.valid?(\"www.google.com.\")\n  #   # => true\n  #\n  # @example Check an URL (which is not a valid domain)\n  #   PublicSuffix.valid?(\"http://www.example.com\")\n  #   # => false\n  #\n  #\n  # @param  [String, #to_s] name The domain name or fully qualified domain name to validate.\n  # @param  [Boolean] ignore_private\n  # @return [Boolean]\n  def self.valid?(name, list: List.default, default_rule: list.default_rule, ignore_private: false)\n    what = normalize(name)\n    return false if what.is_a?(DomainInvalid)\n\n    rule = list.find(what, default: default_rule, ignore_private: ignore_private)\n\n    !rule.nil? && !rule.decompose(what).last.nil?\n  end\n\n  # Attempt to parse the name and returns the domain, if valid.\n  #\n  # This method doesn't raise. Instead, it returns nil if the domain is not valid for whatever reason.\n  #\n  # @param  [String, #to_s] name The domain name or fully qualified domain name to parse.\n  # @param  [PublicSuffix::List] list The rule list to search, defaults to the default {PublicSuffix::List}\n  # @param  [Boolean] ignore_private\n  # @return [String]\n  def self.domain(name, **options)\n    parse(name, **options).domain\n  rescue PublicSuffix::Error\n    nil\n  end\n\n\n  # private\n\n  def self.decompose(name, rule)\n    left, right = rule.decompose(name)\n\n    parts = left.split(DOT)\n    # If we have 0 parts left, there is just a tld and no domain or subdomain\n    # If we have 1 part  left, there is just a tld, domain and not subdomain\n    # If we have 2 parts left, the last part is the domain, the other parts (combined) are the subdomain\n    tld = right\n    sld = parts.empty? ? nil : parts.pop\n    trd = parts.empty? ? nil : parts.join(DOT)\n\n    Domain.new(tld, sld, trd)\n  end\n\n  # Pretend we know how to deal with user input.\n  def self.normalize(name)\n    name = name.to_s.dup\n    name.strip!\n    name.chomp!(DOT)\n    name.downcase!\n\n    return DomainInvalid.new(\"Name is blank\") if name.empty?\n    return DomainInvalid.new(\"Name starts with a dot\") if name.start_with?(DOT)\n    return DomainInvalid.new(\"%s is not expected to contain a scheme\" % name) if name.include?(\"://\")\n\n    name\n  end\n\nend\n"
  },
  {
    "path": "tests/test_code/rb/resolve_correct_class/rcc.rb",
    "content": "def func_1\n    puts \"this should never get called\"\nend\n\nclass Alpha\n    def func_1()\n        puts \"alpha func_1\"\n        self.func_1()\n        func_1()\n        b = Beta.new()\n        b.func_2()\n    end\n\n    def func_2()\n        puts \"alpha func_2\"\n    end\nend\n\nclass Beta\n    def func_1()\n        puts \"beta func_1\"\n        al = Alpha.new()\n        al.func_2()\n    end\n\n    def func_2()\n        puts \"beta func_2\"\n    end\nend\n"
  },
  {
    "path": "tests/test_code/rb/simple_a/simple_a.rb",
    "content": "def func_b(var)\n    puts \"hello world\"\nend\n\ndef func_a()\n    func_b()\nend\n\nfunc_a()\n"
  },
  {
    "path": "tests/test_code/rb/simple_b/simple_b.rb",
    "content": "\"\"\"\nComments\n\"\"\"\n\n\ndef a()\n    b()\nend\n\n# comments\ndef b()\n    a(\"\"\"STRC #\"\"\")\nend\n\nclass Cls\n    def initialize(val)\n        @val = val\n    end\n    def d(a=\"String\")\n        a(\"AnotherSTR\")\n    end\nend\n\nc = Cls.new()\nc.d()\n"
  },
  {
    "path": "tests/test_code/rb/split_modules/split_modules_a.rb",
    "content": "require_relative(\"split_modules_b\")\n\ndef say_hi\nend\n\ndef say_bye\nend\n\nmodule Split\n    def self.say_hi\n        puts \"hi\"\n        say_bye\n    end\nend\n\n\nobj = Split::MyClass.new()\nobj.doit()\n"
  },
  {
    "path": "tests/test_code/rb/split_modules/split_modules_b.rb",
    "content": "module Split\n\n    class MyClass\n        def initialize\n            puts \"initialize\"\n        end\n\n        def doit\n            puts \"doit\"\n            Split::say_hi\n        end\n    end\n\n    def self.say_bye\n        puts \"bye\"\n    end\nend\n"
  },
  {
    "path": "tests/test_code/rb/two_file_simple/file_a.rb",
    "content": "require_relative 'file_b.rb'\n\ndef abra()\n    babra()\nend\n\n\nif true\n    abra()\nend\n"
  },
  {
    "path": "tests/test_code/rb/two_file_simple/file_b.rb",
    "content": "def babra()\n    print(\"here\")\nend\n\n\ndef cadabra()\nend\n\n\n"
  },
  {
    "path": "tests/test_code/rb/weird_chains/weird_chains.rb",
    "content": "class DivByTwo\n    def initialize(val: 0)\n        @val = val\n    end\n\n    def +(val)\n        @val += (val / 2)\n        self\n    end\n\n    def -(val)\n        @val -= (val / 2)\n        self\n    end\n\n    def *(val)\n        @val *= (val / 2)\n        self\n    end\n\n    def result()\n        @val\n    end\nend\n\nnum = DivByTwo.new(val: 5)\nputs ((num + 5 - 5) * 5).result\n\nputs 5 + 6 + 3\n"
  },
  {
    "path": "tests/test_graphs.py",
    "content": "import io\nimport os\nimport sys\n\nimport pygraphviz\nimport pytest\n\nsys.path.append(os.getcwd().split('/tests')[0])\n\nfrom code2flow.engine import code2flow, LanguageParams, SubsetParams\nfrom tests.testdata import testdata\n\nLANGUAGES = (\n    'py',\n    'js',\n    'mjs',\n    'rb',\n    'php',\n)\n\nflattened_tests = {}\nfor lang, tests in testdata.items():\n    if lang not in LANGUAGES:\n        continue\n    for test_dict in tests:\n        flattened_tests[lang + ': ' + test_dict['test_name']] = (lang, test_dict)\n\n\ndef _edge(tup):\n    return f\"{tup[0]}->{tup[1]}\"\n\n\ndef assert_eq(seq_a, seq_b):\n    try:\n        assert seq_a == seq_b\n    except AssertionError:\n        print(\"generated\", file=sys.stderr)\n        for el in seq_a:\n            print(_edge(el), file=sys.stderr)\n        print(\"expected\", file=sys.stderr)\n        for el in seq_b:\n            print(_edge(el), file=sys.stderr)\n\n        extra = seq_a - seq_b\n        missing = seq_b - seq_a\n        if extra:\n            print(\"extra\", file=sys.stderr)\n            for el in extra:\n                print(_edge(el), file=sys.stderr)\n        if missing:\n            print(\"missing\", file=sys.stderr)\n            for el in missing:\n                print(_edge(el), file=sys.stderr)\n\n        sys.stderr.flush()\n        raise AssertionError()\n\n\n@pytest.mark.parametrize(\"test_tup\", flattened_tests)\ndef test_all(test_tup):\n    os.chdir(os.path.dirname(os.path.abspath(__file__)))\n    (language, test_dict) = flattened_tests[test_tup]\n    print(\"Running test %r...\" % test_dict['test_name'])\n    directory_path = os.path.join('test_code', language, test_dict['directory'])\n    kwargs = test_dict.get('kwargs', {})\n    kwargs['lang_params'] = LanguageParams(kwargs.pop('source_type', 'script'),\n                                           kwargs.pop('ruby_version', '27'))\n    kwargs['subset_params'] = SubsetParams.generate(kwargs.pop('target_function', ''),\n                                                    int(kwargs.pop('upstream_depth', 0)),\n                                                    int(kwargs.pop('downstream_depth', 0)))\n    output_file = io.StringIO()\n    code2flow([directory_path], output_file, language, **kwargs)\n\n    generated_edges = get_edges_set_from_file(output_file)\n    print(\"generated_edges eq\", file=sys.stderr)\n    assert_eq(generated_edges, set(map(tuple, test_dict['expected_edges'])))\n\n    generated_nodes = get_nodes_set_from_file(output_file)\n    print(\"generated_nodes eq\", file=sys.stderr)\n    assert_eq(generated_nodes, set(test_dict['expected_nodes']))\n\n\ndef get_nodes_set_from_file(dot_file):\n    dot_file.seek(0)\n    ag = pygraphviz.AGraph(dot_file.read())\n    generated_nodes = []\n    for node in ag.nodes():\n        if not node.attr['name']:\n            # skip the first which is a legend\n            continue\n        generated_nodes.append(node.attr['name'])\n    ret = set(generated_nodes)\n    assert_eq(set(list(ret)), set(generated_nodes))  # assert no dupes\n    return ret\n\n\ndef get_edges_set_from_file(dot_file):\n    dot_file.seek(0)\n    ag = pygraphviz.AGraph(dot_file.read())\n    generated_edges = []\n    for edge in ag.edges():\n        to_add = (edge[0].attr['name'],\n                  edge[1].attr['name'])\n        generated_edges.append(to_add)\n    ret = set(generated_edges)\n    assert_eq(set(list(ret)), set(generated_edges))  # assert no dupes\n    return ret\n"
  },
  {
    "path": "tests/test_interface.py",
    "content": "import json\nimport locale\nimport logging\nimport os\nimport shutil\nimport sys\n\nimport pytest\n\nsys.path.append(os.getcwd().split('/tests')[0])\n\nfrom code2flow.engine import code2flow, main, _generate_graphviz, SubsetParams\nfrom code2flow import model\n\nIMG_PATH = '/tmp/code2flow/output.png'\nif os.path.exists(\"/tmp/code2flow\"):\n    try:\n        shutil.rmtree('/tmp/code2flow')\n    except FileNotFoundError:\n        os.remove('/tmp/code2flow')\nos.mkdir('/tmp/code2flow')\n\nos.chdir(os.path.dirname(os.path.abspath(__file__)))\n\n\ndef test_generate_image():\n    if os.path.exists(IMG_PATH):\n        os.remove(IMG_PATH)\n    code2flow(os.path.abspath(__file__),\n              output_file=IMG_PATH,\n              hide_legend=True)\n    assert os.path.exists(IMG_PATH)\n    os.remove(IMG_PATH)\n    code2flow(os.path.abspath(__file__),\n              output_file=IMG_PATH,\n              hide_legend=False)\n    assert os.path.exists(IMG_PATH)\n\n\ndef test_not_installed():\n    if os.path.exists(IMG_PATH):\n        os.remove(IMG_PATH)\n    tmp_path = os.environ['PATH']\n    os.environ['PATH'] = ''\n    with pytest.raises(AssertionError):\n        code2flow(os.path.abspath(__file__),\n                  output_file=IMG_PATH)\n    os.environ['PATH'] = tmp_path\n\n\ndef test_invalid_extension():\n    with pytest.raises(AssertionError):\n        code2flow(os.path.abspath(__file__),\n                  output_file='out.pdf')\n\n\ndef test_no_files():\n    with pytest.raises(AssertionError):\n        code2flow(os.path.abspath(__file__) + \"fakefile\",\n                  output_file=IMG_PATH)\n\n\ndef test_graphviz_error(caplog):\n    caplog.set_level(logging.DEBUG)\n    _generate_graphviz(\"/tmp/code2flow/nothing\", \"/tmp/code2flow/nothing\",\n                       \"/tmp/code2flow/nothing\")\n    assert \"non-zero exit\" in caplog.text\n\n\ndef test_no_files_2():\n    if not os.path.exists('/tmp/code2flow/no_source_dir'):\n        os.mkdir('/tmp/code2flow/no_source_dir')\n    if not os.path.exists('/tmp/code2flow/no_source_dir/fakefile'):\n        with open('/tmp/code2flow/no_source_dir/fakefile', 'w') as f:\n            f.write(\"hello world\")\n\n    with pytest.raises(AssertionError):\n        code2flow('/tmp/code2flow/no_source_dir',\n                  output_file=IMG_PATH)\n\n    with pytest.raises(AssertionError):\n        code2flow('/tmp/code2flow/no_source_dir',\n                  language='py',\n                  output_file=IMG_PATH)\n\n\ndef test_json():\n    code2flow('test_code/py/simple_b',\n              output_file='/tmp/code2flow/out.json',\n              hide_legend=False)\n    with open('/tmp/code2flow/out.json') as f:\n        jobj = json.loads(f.read())\n    assert set(jobj.keys()) == {'graph'}\n    assert set(jobj['graph'].keys()) == {'nodes', 'edges', 'directed'}\n    assert jobj['graph']['directed'] is True\n    assert isinstance(jobj['graph']['nodes'], dict)\n    assert len(jobj['graph']['nodes']) == 4\n    assert set(n['name'] for n in jobj['graph']['nodes'].values()) == {'simple_b::a', 'simple_b::(global)', 'simple_b::c.d', 'simple_b::b'}\n\n    assert isinstance(jobj['graph']['edges'], list)\n    assert len(jobj['graph']['edges']) == 4\n    assert len(set(n['source'] for n in jobj['graph']['edges'])) == 4\n    assert len(set(n['target'] for n in jobj['graph']['edges'])) == 3\n\n\ndef test_weird_encoding():\n    \"\"\"\n    To address https://github.com/scottrogowski/code2flow/issues/28\n    The windows user had an error b/c their default encoding was cp1252\n    and they were trying to read a unicode file with emojis\n    I don't have that installed but was able to reproduce by changing to\n    US-ASCII which I assume is a little more universal anyway.\n    \"\"\"\n\n    locale.setlocale(locale.LC_ALL, 'en_US.US-ASCII')\n    code2flow('test_code/py/weird_encoding',\n              output_file='/tmp/code2flow/out.json',\n              hide_legend=False)\n    with open('/tmp/code2flow/out.json') as f:\n        jobj = json.loads(f.read())\n    assert set(jobj.keys()) == {'graph'}\n\n\ndef test_repr():\n    module = model.Group('my_file', model.GROUP_TYPE.FILE, [], 0)\n    group = model.Group('Obj', model.GROUP_TYPE.CLASS, [], 0)\n    call = model.Call('tostring', 'obj', 42)\n    variable = model.Variable('the_string', call, 42)\n    node_a = model.Node('tostring', [], [], [], 13, group)\n    node_b = model.Node('main', [call], [], [], 59, module)\n    edge = model.Edge(node_b, node_a)\n    print(module)\n    print(group)\n    print(call)\n    print(variable)\n    print(node_a)\n    print(node_b)\n    print(edge)\n\n\ndef test_bad_acorn(mocker, caplog):\n    caplog.set_level(logging.DEBUG)\n    mocker.patch('code2flow.javascript.get_acorn_version', return_value='7.6.9')\n    code2flow(\"test_code/js/simple_a_js\", \"/tmp/code2flow/out.json\")\n    assert \"Acorn\" in caplog.text and \"8.*\" in caplog.text\n\n\ndef test_bad_ruby_parse(mocker):\n    mocker.patch('subprocess.check_output', return_value=b'blah blah')\n    with pytest.raises(AssertionError) as ex:\n        code2flow(\"test_code/rb/simple_b\", \"/tmp/code2flow/out.json\")\n        assert \"ruby-parse\" in ex and \"syntax\" in ex\n\n\ndef test_bad_php_parse_a():\n    with pytest.raises(AssertionError) as ex:\n        code2flow(\"test_code/php/bad_php/bad_php_a.php\", \"/tmp/code2flow/out.json\")\n        assert \"parse\" in ex and \"syntax\" in ex\n\n\ndef test_bad_php_parse_b():\n    with pytest.raises(AssertionError) as ex:\n        code2flow(\"test_code/php/bad_php/bad_php_b.php\", \"/tmp/code2flow/out.json\")\n        assert \"parse\" in ex and \"php\" in ex.lower()\n\n\ndef test_no_source_type():\n    with pytest.raises(AssertionError):\n        code2flow('test_code/js/exclude_modules_es6',\n                  output_file='/tmp/code2flow/out.json',\n                  hide_legend=False)\n\n\ndef test_cli_no_args(capsys):\n    with pytest.raises(SystemExit):\n        main([])\n    assert 'the following arguments are required' in capsys.readouterr().err\n\n\ndef test_cli_verbose_quiet(capsys):\n    with pytest.raises(AssertionError):\n        main(['test_code/py/simple_a', '--verbose', '--quiet'])\n\n\ndef test_cli_log_default(mocker):\n    logging.basicConfig = mocker.MagicMock()\n    main(['test_code/py/simple_a'])\n    logging.basicConfig.assert_called_once_with(format=\"Code2Flow: %(message)s\",\n                                                level=logging.INFO)\n\n\ndef test_cli_log_verbose(mocker):\n    logging.basicConfig = mocker.MagicMock()\n    main(['test_code/py/simple_a', '--verbose'])\n    logging.basicConfig.assert_called_once_with(format=\"Code2Flow: %(message)s\",\n                                                level=logging.DEBUG)\n\n\ndef test_cli_log_quiet(mocker):\n    logging.basicConfig = mocker.MagicMock()\n    main(['test_code/py/simple_a', '--quiet'])\n    logging.basicConfig.assert_called_once_with(format=\"Code2Flow: %(message)s\",\n                                                level=logging.WARNING)\n\ndef test_subset_cli(mocker):\n    with pytest.raises(AssertionError):\n        SubsetParams.generate(target_function='', upstream_depth=1, downstream_depth=0)\n    with pytest.raises(AssertionError):\n        SubsetParams.generate(target_function='', upstream_depth=0, downstream_depth=1)\n    with pytest.raises(AssertionError):\n        SubsetParams.generate(target_function='test', upstream_depth=0, downstream_depth=0)\n    with pytest.raises(AssertionError):\n        SubsetParams.generate(target_function='test', upstream_depth=-1, downstream_depth=0)\n    with pytest.raises(AssertionError):\n        SubsetParams.generate(target_function='test', upstream_depth=0, downstream_depth=-1)\n\n    with pytest.raises(AssertionError):\n        main(['test_code/py/subset_find_exception/zero.py', '--target-function', 'func', '--upstream-depth', '1'])\n\n    with pytest.raises(AssertionError):\n        main(['test_code/py/subset_find_exception/two.py', '--target-function', 'func', '--upstream-depth', '1'])\n\n\n"
  },
  {
    "path": "tests/testdata.py",
    "content": "testdata = {\n    \"py\": [\n        {\n            \"test_name\": \"simple_a\",\n            \"directory\": \"simple_a\",\n            \"expected_edges\": [[\"simple_a::func_a\", \"simple_a::func_b\"]],\n            \"expected_nodes\": [\"simple_a::func_a\", \"simple_a::func_b\"]\n        },\n        {\n            \"test_name\": \"simple_b\",\n            \"directory\": \"simple_b\",\n            \"expected_edges\": [\n                [\"simple_b::c.d\", \"simple_b::a\"],\n                [\"simple_b::a\", \"simple_b::b\"],\n                [\"simple_b::(global)\", \"simple_b::c.d\"],\n                [\"simple_b::b\", \"simple_b::a\"]],\n            \"expected_nodes\": [\"simple_b::c.d\", \"simple_b::a\", \"simple_b::b\",\n                               \"simple_b::(global)\"]\n        },\n        {\n            \"test_name\": \"simple_b --exclude-functions\",\n            \"comment\": \"We don't have a function c so nothing should happen\",\n            \"directory\": \"simple_b\",\n            \"kwargs\": {\"exclude_functions\": [\"c\"]},\n            \"expected_edges\": [\n                [\"simple_b::c.d\", \"simple_b::a\"],\n                [\"simple_b::a\", \"simple_b::b\"],\n                [\"simple_b::(global)\", \"simple_b::c.d\"],\n                [\"simple_b::b\", \"simple_b::a\"]\n            ],\n            \"expected_nodes\": [\"simple_b::c.d\", \"simple_b::a\", \"simple_b::b\",\n                               \"simple_b::(global)\"]\n        },\n        {\n            \"test_name\": \"simple_b --exclude-functions2\",\n            \"comment\": \"Exclude all edges with function a\",\n            \"directory\": \"simple_b\",\n            \"kwargs\": {\"exclude_functions\": [\"a\"]},\n            \"expected_edges\": [\n                [\"simple_b::(global)\", \"simple_b::c.d\"]\n            ],\n            \"expected_nodes\": [\"simple_b::c.d\", \"simple_b::(global)\"]\n        },\n        {\n            \"test_name\": \"simple_b --exclude-functions2 no_trimming\",\n            \"comment\": \"Exclude all edges with function a. No trimming keeps nodes except a\",\n            \"directory\": \"simple_b\",\n            \"kwargs\": {\"exclude_functions\": [\"a\"], \"no_trimming\": True},\n            \"expected_edges\": [\n                [\"simple_b::(global)\", \"simple_b::c.d\"]\n            ],\n            \"expected_nodes\": [\"simple_b::c.d\", \"simple_b::b\", \"simple_b::(global)\"]\n        },\n        {\n            \"test_name\": \"simple_b --exclude-functions2\",\n            \"comment\": \"Exclude all edges with function d (d is in a class)\",\n            \"directory\": \"simple_b\",\n            \"kwargs\": {\"exclude_functions\": [\"d\"]},\n            \"expected_edges\": [\n                [\"simple_b::a\", \"simple_b::b\"],\n                [\"simple_b::b\", \"simple_b::a\"]\n            ],\n            \"expected_nodes\": [\"simple_b::a\", \"simple_b::b\"]\n        },\n        {\n            \"test_name\": \"simple_b --exclude-namespaces\",\n            \"comment\": \"Exclude the file itself\",\n            \"directory\": \"simple_b\",\n            \"kwargs\": {\"exclude_namespaces\": [\"simple_b\"]},\n            \"expected_edges\": [],\n            \"expected_nodes\": []\n        },\n        {\n            \"test_name\": \"simple_b --exclude-namespaces 2\",\n            \"comment\": \"Exclude a class in the file\",\n            \"directory\": \"simple_b\",\n            \"kwargs\": {\"exclude_namespaces\": [\"c\"]},\n            \"expected_edges\": [\n                [\"simple_b::a\", \"simple_b::b\"],\n                [\"simple_b::b\", \"simple_b::a\"]\n            ],\n            \"expected_nodes\": [\"simple_b::a\", \"simple_b::b\"]\n        },\n\n        {\n            \"test_name\": \"simple_b --include-only-functions\",\n            \"comment\": \"Include one a and b\",\n            \"directory\": \"simple_b\",\n            \"kwargs\": {\"include_only_functions\": [\"a\", \"b\"]},\n            \"expected_edges\": [\n                [\"simple_b::a\", \"simple_b::b\"],\n                [\"simple_b::b\", \"simple_b::a\"]\n            ],\n            \"expected_nodes\": [\"simple_b::a\", \"simple_b::b\"]\n        },\n        {\n            \"test_name\": \"pytz --include-only-functions\",\n            \"comment\": \"Include only two functions\",\n            \"directory\": \"pytz\",\n            \"kwargs\": {\"include_only_functions\": [\"_fill\", \"open_resource\"]},\n            \"expected_edges\": [\n                [\"__init__::_CountryTimezoneDict._fill\", \"__init__::open_resource\"],\n                [\"__init__::_CountryNameDict._fill\", \"__init__::open_resource\"]\n            ],\n            \"expected_nodes\": [\"__init__::_CountryTimezoneDict._fill\", \"__init__::_CountryNameDict._fill\", \"__init__::open_resource\"]\n        },\n\n        {\n            \"test_name\": \"--include-only-namespaces=reference\",\n            \"directory\": \"pytz\",\n            \"kwargs\":{\"include_only_namespaces\": [\"reference\"]},\n            \"expected_edges\": [[\"reference::USTimeZone.utcoffset\",\n                                \"reference::USTimeZone.dst\"],\n                               [\"reference::LocalTimezone.utcoffset\",\n                                \"reference::LocalTimezone._isdst\"],\n                               [\"reference::(global)\", \"reference::USTimeZone.__init__\"],\n                               [\"reference::USTimeZone.tzname\",\n                                \"reference::USTimeZone.dst\"],\n                               [\"reference::LocalTimezone.dst\",\n                                \"reference::LocalTimezone._isdst\"],\n                               [\"reference::LocalTimezone.tzname\",\n                                \"reference::LocalTimezone._isdst\"],\n                               [\"reference::USTimeZone.dst\",\n                                \"reference::first_sunday_on_or_after\"]],\n            \"expected_nodes\": [\"reference::USTimeZone.utcoffset\",\n                               \"reference::LocalTimezone.dst\",\n                               \"reference::LocalTimezone.tzname\",\n                               \"reference::USTimeZone.__init__\",\n                               \"reference::USTimeZone.tzname\",\n                               \"reference::USTimeZone.dst\",\n                               \"reference::(global)\",\n                               \"reference::LocalTimezone._isdst\",\n                               \"reference::LocalTimezone.utcoffset\",\n                               \"reference::first_sunday_on_or_after\"]\n        },\n\n\n        {\n            \"test_name\": \"--include-only-namespaces=USTimeZone\",\n            \"directory\": \"pytz\",\n            \"kwargs\":{\"include_only_namespaces\": [\"USTimeZone\"]},\n            \"expected_edges\": [[\"reference::USTimeZone.utcoffset\",\n                                \"reference::USTimeZone.dst\"],\n                               [\"reference::USTimeZone.tzname\",\n                                \"reference::USTimeZone.dst\"]],\n            \"expected_nodes\": [\"reference::USTimeZone.dst\",\n                               \"reference::USTimeZone.utcoffset\",\n                               \"reference::USTimeZone.tzname\"]\n        },\n        {\n            \"test_name\": \"include_exclude_namespaces\",\n            \"comment\": \"Complex including/excluding namespaces\",\n            \"directory\": \"pytz\",\n            \"kwargs\": {\"include_only_namespaces\": [\"tzfile\",\"tzinfo\"],\n                       \"exclude_namespaces\": [\"DstTzInfo\"]},\n            \"expected_edges\": [[\"tzinfo::(global)\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzfile::build_tzinfo\", \"tzfile::_std_string\"],\n                               [\"tzfile::(global)\", \"tzfile::build_tzinfo\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_ttinfo\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzfile::(global)\", \"tzfile::_byte_string\"],\n                               [\"tzinfo::unpickler\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzfile::build_tzinfo\", \"tzfile::_byte_string\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_datetime\"],\n                               [\"tzinfo::memorized_ttinfo\",\n                                \"tzinfo::memorized_timedelta\"]],\n            \"expected_nodes\": [\"tzinfo::unpickler\",\n                               \"tzfile::build_tzinfo\",\n                               \"tzinfo::(global)\",\n                               \"tzfile::_std_string\",\n                               \"tzinfo::memorized_timedelta\",\n                               \"tzfile::(global)\",\n                               \"tzinfo::memorized_datetime\",\n                               \"tzfile::_byte_string\",\n                               \"tzinfo::memorized_ttinfo\"]\n        },\n        {\n            \"test_name\": \"include_exclude_namespaces_exclude_functions\",\n            \"comment\": \"Very complex including/excluding namespaces and excluding functions\",\n            \"directory\": \"pytz\",\n            \"kwargs\": {\"include_only_namespaces\": [\"tzfile\",\"tzinfo\"],\n                       \"exclude_namespaces\": [\"DstTzInfo\"],\n                       \"exclude_functions\": [\"(global)\"]},\n            \"expected_edges\": [[\"tzfile::build_tzinfo\", \"tzinfo::memorized_ttinfo\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_datetime\"],\n                               [\"tzinfo::memorized_ttinfo\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzfile::build_tzinfo\", \"tzfile::_std_string\"],\n                               [\"tzfile::build_tzinfo\", \"tzfile::_byte_string\"],\n                               [\"tzinfo::unpickler\", \"tzinfo::memorized_timedelta\"]],\n            \"expected_nodes\": [\"tzinfo::unpickler\",\n                               \"tzinfo::memorized_datetime\",\n                               \"tzinfo::memorized_ttinfo\",\n                               \"tzfile::build_tzinfo\",\n                               \"tzfile::_std_string\",\n                               \"tzinfo::memorized_timedelta\",\n                               \"tzfile::_byte_string\"]\n        },\n        {\n            \"test_name\": \"simple_b --exclude-namespaces not found\",\n            \"comment\": \"Exclude something not there\",\n            \"directory\": \"simple_b\",\n            \"kwargs\": {\"exclude_namespaces\": [\"notreal\"]},\n            \"expected_edges\": [\n                [\"simple_b::c.d\", \"simple_b::a\"],\n                [\"simple_b::a\", \"simple_b::b\"],\n                [\"simple_b::(global)\", \"simple_b::c.d\"],\n                [\"simple_b::b\", \"simple_b::a\"]\n            ],\n            \"expected_nodes\": [\"simple_b::c.d\", \"simple_b::a\", \"simple_b::b\",\n                               \"simple_b::(global)\"]\n        },\n        {\n            \"test_name\": \"two_file_simple\",\n            \"directory\": \"two_file_simple\",\n            \"expected_edges\": [[\"file_a::(global)\", \"file_a::a\"],\n                               [\"file_a::a\", \"file_b::b\"]],\n            \"expected_nodes\": [\"file_a::(global)\", \"file_a::a\", \"file_b::b\"]\n        },\n        {\n            \"test_name\": \"two_file_simple --exclude_functions\",\n            \"comment\": \"Function a is in both files so should be removed from both\",\n            \"directory\": \"two_file_simple\",\n            \"kwargs\": {\"exclude_functions\": [\"a\"]},\n            \"expected_edges\": [],\n            \"expected_nodes\": []\n        },\n        {\n            \"test_name\": \"two_file_simple --exclude_functions no-trim\",\n            \"comment\": \"Function a is in both files but don't trim so leave file_b nodes\",\n            \"directory\": \"two_file_simple\",\n            \"kwargs\": {\"exclude_functions\": [\"a\"], \"no_trimming\": True},\n            \"expected_edges\": [],\n            \"expected_nodes\": [\"file_a::(global)\", \"file_b::(global)\", \"file_b::b\", \"file_b::c\"]\n        },\n        {\n            \"test_name\": \"two_file_simple --exclude_namespaces no-trim\",\n            \"comment\": \"Trim one file and leave the other intact\",\n            \"directory\": \"two_file_simple\",\n            \"kwargs\": {\"exclude_namespaces\": [\"file_a\"], \"no_trimming\": True},\n            \"expected_edges\": [],\n            \"expected_nodes\": [\"file_b::(global)\", \"file_b::b\", \"file_b::c\"]\n        },\n        {\n            \"test_name\": \"async_basic\",\n            \"directory\": \"async_basic\",\n            \"kwargs\": {},\n            \"expected_edges\": [[\"async_basic::main\", \"async_basic::A.__init__\"],\n                               [\"async_basic::(global)\", \"async_basic::main\"],\n                               [\"async_basic::main\", \"async_basic::A.test\"]],\n            \"expected_nodes\": [\"async_basic::(global)\",\n                               \"async_basic::A.__init__\",\n                               \"async_basic::A.test\",\n                               \"async_basic::main\"]\n        },\n        {\n            \"test_name\": \"exclude_modules\",\n            \"comment\": \"Correct name resolution when third-party modules are involved\",\n            \"directory\": \"exclude_modules\",\n            \"kwargs\": {},\n            \"expected_edges\": [[\"exclude_modules::(global)\", \"exclude_modules::alpha\"],\n                               [\"exclude_modules::alpha\", \"exclude_modules::beta\"],\n                               [\"exclude_modules::beta\", \"exclude_modules::search\"]],\n            \"expected_nodes\": [\"exclude_modules::(global)\", \"exclude_modules::alpha\",\n                               \"exclude_modules::beta\", \"exclude_modules::search\"]\n        },\n        {\n            \"test_name\": \"exclude_modules_two_files\",\n            \"comment\": \"Correct name resolution when third-party modules are involved with two files\",\n            \"directory\": \"exclude_modules_two_files\",\n            \"expected_edges\": [[\"exclude_modules_a::b\", \"exclude_modules_b::match\"],\n                               [\"exclude_modules_a::(global)\", \"exclude_modules_a::b\"],\n                               [\"exclude_modules_a::(global)\", \"exclude_modules_a::a\"]],\n            \"expected_nodes\": [\"exclude_modules_a::a\",\n                               \"exclude_modules_a::b\",\n                               \"exclude_modules_b::match\",\n                               \"exclude_modules_a::(global)\"]\n        },\n        {\n            \"test_name\": \"resolve_correct_class\",\n            \"comment\": \"Correct name resolution with multiple classes\",\n            \"directory\": \"resolve_correct_class\",\n            \"kwargs\": {},\n            \"expected_edges\": [[\"rcc::Alpha.func_1\", \"rcc::Alpha.func_1\"],\n                               [\"rcc::Alpha.func_1\", \"rcc::func_1\"],\n                               [\"rcc::Alpha.func_1\", \"rcc::Beta.func_2\"],\n                               [\"rcc::Beta.func_1\", \"rcc::Alpha.func_2\"],\n                               ],\n            \"expected_nodes\": [\"rcc::Alpha.func_1\", \"rcc::Alpha.func_2\",\n                               \"rcc::func_1\", \"rcc::Beta.func_2\",\n                               \"rcc::Beta.func_1\"]\n        },\n        {\n            \"test_name\": \"ambibuous resolution\",\n            \"comment\": \"If we can't resolve, do not inlude node.\",\n            \"directory\": \"ambiguous_resolution\",\n            \"expected_edges\": [[\"ambiguous_resolution::(global)\",\n                                \"ambiguous_resolution::main\"],\n                               [\"ambiguous_resolution::main\",\n                                \"ambiguous_resolution::Cadabra.cadabra_it\"],\n                               [\"ambiguous_resolution::Cadabra.cadabra_it\",\n                                \"ambiguous_resolution::Abra.abra_it\"]],\n            \"expected_nodes\": [\"ambiguous_resolution::(global)\",\n                               \"ambiguous_resolution::main\",\n                               \"ambiguous_resolution::Cadabra.cadabra_it\",\n                               \"ambiguous_resolution::Abra.abra_it\"]\n        },\n        {\n            \"test_name\": \"weird_imports\",\n            \"directory\": \"weird_imports\",\n            \"expected_edges\": [[\"weird_imports::(global)\", \"weird_imports::main\"]],\n            \"expected_nodes\": [\"weird_imports::main\", \"weird_imports::(global)\"]\n        },\n        {\n            \"test_name\": \"nested classes\",\n            \"directory\": \"nested_class\",\n            \"expected_edges\": [[\"nested_class::(global)\",\n                                \"nested_class::Outer.outer_func\"],\n                               [\"nested_class::(global)\", \"nested_class::Outer.__init__\"]],\n            \"expected_nodes\": [\"nested_class::Outer.outer_func\",\n                               \"nested_class::Outer.__init__\",\n                               \"nested_class::(global)\"]\n        },\n        {\n            \"test_name\": \"init\",\n            \"comment\": \"working with constructors\",\n            \"directory\": \"init\",\n            \"expected_edges\": [[\"init::Abra.__init__\", \"init::Abra.cadabra\"],\n                               [\"init::(global)\", \"the_import::imported_func\"],\n                               [\"init::(global)\", \"init::b\"],\n                               [\"init::b\", \"init::Abra.__init__\"],\n                               [\"init::(global)\", \"the_import::HiddenClass.__init__\"],\n                               [\"init::(global)\", \"the_import::ProvincialClass.__init__\"]],\n            \"expected_nodes\": [\"init::(global)\",\n                               \"the_import::ProvincialClass.__init__\",\n                               \"init::Abra.__init__\",\n                               \"init::Abra.cadabra\",\n                               \"the_import::HiddenClass.__init__\",\n                               \"the_import::imported_func\",\n                               \"init::b\"]\n        },\n        {\n            \"test_name\": \"weird_calls\",\n            \"directory\": \"weird_calls\",\n            \"comment\": \"Subscript calls\",\n            \"expected_edges\": [[\"weird_calls::func_c\", \"weird_calls::print_it\"],\n                               [\"weird_calls::func_b\", \"weird_calls::print_it\"],\n                               [\"weird_calls::(global)\", \"weird_calls::func_b\"],\n                               [\"weird_calls::func_a\", \"weird_calls::print_it\"],\n                               [\"weird_calls::(global)\", \"weird_calls::factory\"]],\n            \"expected_nodes\": [\"weird_calls::func_b\",\n                               \"weird_calls::print_it\",\n                               \"weird_calls::(global)\",\n                               \"weird_calls::func_c\",\n                               \"weird_calls::func_a\",\n                               \"weird_calls::factory\"]\n        },\n        {\n            \"test_name\": \"chained\",\n            \"directory\": \"chained\",\n            \"expected_edges\": [[\"chained::(global)\", \"chained::Chain.sub\"],\n                               [\"chained::(global)\", \"chained::Chain.__init__\"],\n                               [\"chained::(global)\", \"chained::Chain.add\"],\n                               [\"chained::(global)\", \"chained::Chain.mul\"]],\n            \"expected_nodes\": [\"chained::Chain.__init__\",\n                               \"chained::(global)\",\n                               \"chained::Chain.sub\",\n                               \"chained::Chain.add\",\n                               \"chained::Chain.mul\"]\n        },\n        {\n            \"test_name\": \"inherits\",\n            \"directory\": \"inherits\",\n            \"expected_edges\": [[\"inherits::(global)\", \"inherits::ScaleDemo.__init__\"],\n                               [\"inherits::ScaleDemoLimited.__init__\",\n                                \"inherits_import::MajorScales.majorNum\"],\n                               [\"inherits::ScaleDemo.__init__\",\n                                \"inherits_import::MajorScales.majorNum\"],\n                               [\"inherits::(global)\", \"inherits::majorNum\"],\n                               [\"inherits::ScaleDemo.__init__\",\n                                \"inherits_import::PentatonicScales.pentaNum\"]],\n            \"expected_nodes\": [\"inherits_import::MajorScales.majorNum\",\n                               \"inherits::(global)\",\n                               \"inherits::ScaleDemoLimited.__init__\",\n                               \"inherits_import::PentatonicScales.pentaNum\",\n                               \"inherits::majorNum\",\n                               \"inherits::ScaleDemo.__init__\"]\n        },\n        {\n            \"test_name\": \"import_paths\",\n            \"directory\": \"import_paths\",\n            \"comment\": \"relative and absolute imports\",\n            \"expected_edges\": [[\"import_paths::main\", \"abra::abra2\"],\n                               [\"import_paths::main\", \"cadabra::cadabra2\"],\n                               [\"import_paths::main2\", \"abra::abra2\"],\n                               [\"import_paths::main\", \"import_paths::main2\"],\n                               [\"import_paths::(global)\", \"import_paths::main\"]],\n            \"expected_nodes\": [\"abra::abra2\",\n                               \"import_paths::main2\",\n                               \"import_paths::main\",\n                               \"cadabra::cadabra2\",\n                               \"import_paths::(global)\"]\n        },\n        {\n            \"test_name\": \"nested_calls\",\n            \"directory\": \"nested_calls\",\n            \"comment\": \"Something like func(a)(b)\",\n            \"expected_edges\": [[\"nested_calls::(global)\", \"nested_calls::trace\"]],\n            \"expected_nodes\": [\"nested_calls::trace\", \"nested_calls::(global)\"]\n        },\n        {\n            \"test_name\": \"pytz\",\n            \"directory\": \"pytz\",\n            \"kwargs\": {\"exclude_namespaces\": [\"test_tzinfo\"]},\n            \"expected_edges\": [[\"tzinfo::(global)\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzfile::(global)\", \"tzfile::_byte_string\"],\n                               [\"__init__::timezone\", \"tzfile::build_tzinfo\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_datetime\"],\n                               [\"__init__::timezone\",\n                                \"__init__::_case_insensitive_zone_lookup\"],\n                               [\"tzinfo::DstTzInfo.utcoffset\",\n                                \"tzinfo::DstTzInfo.localize\"],\n                               [\"tzinfo::memorized_ttinfo\", \"tzinfo::memorized_timedelta\"],\n                               [\"reference::USTimeZone.tzname\",\n                                \"reference::USTimeZone.dst\"],\n                               [\"tzfile::build_tzinfo\", \"tzfile::_std_string\"],\n                               [\"__init__::_CountryTimezoneDict._fill\",\n                                \"__init__::open_resource\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzinfo::DstTzInfo.dst\", \"tzinfo::DstTzInfo.localize\"],\n                               [\"reference::LocalTimezone.tzname\",\n                                \"reference::LocalTimezone._isdst\"],\n                               [\"reference::LocalTimezone.utcoffset\",\n                                \"reference::LocalTimezone._isdst\"],\n                               [\"__init__::(global)\", \"__init__::_test\"],\n                               [\"tzinfo::DstTzInfo.__reduce__\", \"tzinfo::_to_seconds\"],\n                               [\"__init__::_p\", \"tzinfo::unpickler\"],\n                               [\"reference::USTimeZone.utcoffset\",\n                                \"reference::USTimeZone.dst\"],\n                               [\"__init__::_CountryNameDict._fill\",\n                                \"__init__::open_resource\"],\n                               [\"reference::(global)\", \"reference::USTimeZone.__init__\"],\n                               [\"__init__::timezone\", \"__init__::open_resource\"],\n                               [\"__init__::timezone\", \"__init__::_unmunge_zone\"],\n                               [\"__init__::timezone\", \"__init__::ascii\"],\n                               [\"__init__::UTC.fromutc\", \"__init__::UTC.localize\"],\n                               [\"lazy::LazyList.__new__\", \"lazy::LazyList.__new__\"],\n                               [\"lazy::LazySet.__new__\", \"lazy::LazySet.__new__\"],\n                               [\"tzinfo::DstTzInfo.tzname\", \"tzinfo::DstTzInfo.localize\"],\n                               [\"tzfile::(global)\", \"tzfile::build_tzinfo\"],\n                               [\"tzinfo::DstTzInfo.localize\",\n                                \"tzinfo::DstTzInfo.localize\"],\n                               [\"reference::USTimeZone.dst\",\n                                \"reference::first_sunday_on_or_after\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_ttinfo\"],\n                               [\"tzinfo::unpickler\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzinfo::DstTzInfo.normalize\",\n                                \"tzinfo::DstTzInfo.fromutc\"],\n                               [\"lazy::LazyDict.keys\", \"lazy::LazyDict.keys\"],\n                               [\"__init__::resource_exists\", \"__init__::open_resource\"],\n                               [\"tzfile::build_tzinfo\", \"tzfile::_byte_string\"],\n                               [\"__init__::FixedOffset\",\n                                \"__init__::_FixedOffset.__init__\"],\n                               [\"reference::LocalTimezone.dst\",\n                                \"reference::LocalTimezone._isdst\"]],\n            \"expected_nodes\": [\"reference::USTimeZone.__init__\",\n                               \"tzinfo::_to_seconds\",\n                               \"__init__::_p\",\n                               \"lazy::LazyDict.keys\",\n                               \"tzinfo::memorized_ttinfo\",\n                               \"lazy::LazySet.__new__\",\n                               \"__init__::_CountryTimezoneDict._fill\",\n                               \"__init__::FixedOffset\",\n                               \"tzinfo::DstTzInfo.tzname\",\n                               \"tzfile::build_tzinfo\",\n                               \"lazy::LazyList.__new__\",\n                               \"reference::first_sunday_on_or_after\",\n                               \"tzinfo::DstTzInfo.__reduce__\",\n                               \"tzfile::(global)\",\n                               \"reference::LocalTimezone.dst\",\n                               \"reference::USTimeZone.dst\",\n                               \"__init__::timezone\",\n                               \"reference::USTimeZone.tzname\",\n                               \"tzinfo::DstTzInfo.dst\",\n                               \"reference::USTimeZone.utcoffset\",\n                               \"reference::LocalTimezone._isdst\",\n                               \"tzfile::_std_string\",\n                               \"__init__::open_resource\",\n                               \"tzinfo::DstTzInfo.localize\",\n                               \"__init__::_case_insensitive_zone_lookup\",\n                               \"__init__::UTC.localize\",\n                               \"reference::LocalTimezone.tzname\",\n                               \"tzinfo::DstTzInfo.normalize\",\n                               \"tzfile::_byte_string\",\n                               \"__init__::UTC.fromutc\",\n                               \"tzinfo::DstTzInfo.fromutc\",\n                               \"__init__::_unmunge_zone\",\n                               \"__init__::ascii\",\n                               \"__init__::_FixedOffset.__init__\",\n                               \"tzinfo::memorized_datetime\",\n                               \"tzinfo::memorized_timedelta\",\n                               \"__init__::_test\",\n                               \"reference::(global)\",\n                               \"tzinfo::DstTzInfo.utcoffset\",\n                               \"reference::LocalTimezone.utcoffset\",\n                               \"__init__::_CountryNameDict._fill\",\n                               \"__init__::(global)\",\n                               \"__init__::resource_exists\",\n                               \"tzinfo::(global)\",\n                               \"tzinfo::unpickler\"]\n        },\n        {\n            \"test_name\": \"pytz_basic_upstream_downstream\",\n            \"directory\": \"pytz\",\n            \"kwargs\": {\"target_function\": \"build_tzinfo\",\n                       \"upstream_depth\": \"1\",\n                       \"downstream_depth\": \"1\"},\n            \"expected_edges\": [[\"__init__::timezone\", \"tzfile::build_tzinfo\"],\n                               [\"tzfile::(global)\", \"tzfile::build_tzinfo\"],\n                               [\"tzinfo::memorized_ttinfo\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzfile::build_tzinfo\", \"tzfile::_std_string\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzfile::build_tzinfo\", \"tzfile::_byte_string\"],\n                               [\"tzfile::(global)\", \"tzfile::_byte_string\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_ttinfo\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_datetime\"]],\n            \"expected_nodes\": [\"__init__::timezone\",\n                               \"tzfile::(global)\",\n                               \"tzfile::_std_string\",\n                               \"tzinfo::memorized_ttinfo\",\n                               \"tzfile::_byte_string\",\n                               \"tzinfo::memorized_datetime\",\n                               \"tzfile::build_tzinfo\",\n                               \"tzinfo::memorized_timedelta\"]\n        },\n        {\n            \"test_name\": \"pytz_no_upstream_file_handle\",\n            \"directory\": \"pytz\",\n            \"kwargs\": {\"target_function\": \"tzfile::(global)\",\n                        \"downstream_depth\": \"2\"},\n            \"expected_edges\": [[\"tzfile::(global)\", \"tzfile::build_tzinfo\"],\n                               [\"tzfile::(global)\", \"tzfile::_byte_string\"],\n                               [\"tzinfo::memorized_ttinfo\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzfile::build_tzinfo\", \"tzfile::_std_string\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_timedelta\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_ttinfo\"],\n                               [\"tzfile::build_tzinfo\", \"tzinfo::memorized_datetime\"],\n                               [\"tzfile::build_tzinfo\", \"tzfile::_byte_string\"]],\n            \"expected_nodes\": [\"tzinfo::memorized_datetime\",\n                               \"tzfile::_byte_string\",\n                               \"tzinfo::memorized_ttinfo\",\n                               \"tzfile::_std_string\",\n                               \"tzfile::build_tzinfo\",\n                               \"tzfile::(global)\",\n                               \"tzinfo::memorized_timedelta\"]\n        },\n    ],\n    \"js\": [\n        {\n            \"test_name\": \"simple_a_js\",\n            \"directory\": \"simple_a_js\",\n            \"expected_edges\": [[\"simple_a::func_a\", \"simple_a::func_b\"]],\n            \"expected_nodes\": [\"simple_a::func_a\", \"simple_a::func_b\"]\n        },\n        {\n            \"test_name\": \"simple_b_js\",\n            \"directory\": \"simple_b_js\",\n            \"expected_edges\": [\n                [\"simple_b::C.d\", \"simple_b::a\"],\n                [\"simple_b::a\", \"simple_b::b\"],\n                [\"simple_b::(global)\", \"simple_b::C.d\"],\n                [\"simple_b::b\", \"simple_b::a\"]],\n            \"expected_nodes\": [\"simple_b::C.d\", \"simple_b::a\", \"simple_b::b\",\n                               \"simple_b::(global)\"]\n        },\n        {\n            \"test_name\": \"two_file_simple\",\n            \"directory\": \"two_file_simple\",\n            \"expected_edges\": [[\"file_a::(global)\", \"file_a::a\"],\n                               [\"file_a::a\", \"file_b::b\"]],\n            \"expected_nodes\": [\"file_a::(global)\", \"file_a::a\", \"file_b::b\"]\n        },\n        {\n            \"test_name\": \"two_file_simple exclude entire file\",\n            \"directory\": \"two_file_simple\",\n            \"kwargs\": {\"exclude_namespaces\": [\"file_b\", \"file_c\"]},\n            \"expected_edges\": [[\"file_a::(global)\", \"file_a::a\"]],\n            \"expected_nodes\": [\"file_a::(global)\", \"file_a::a\"]\n        },\n\n        {\n            \"test_name\": \"exclude modules\",\n            \"directory\": \"exclude_modules\",\n            \"expected_edges\": [[\"exclude_modules::(global)\", \"exclude_modules::alpha\"],\n                               [\"exclude_modules::alpha\", \"exclude_modules::alpha\"],\n                               [\"exclude_modules::alpha\", \"exclude_modules::beta\"],\n                               [\"exclude_modules::beta\", \"exclude_modules::readFileSync\"]],\n            \"expected_nodes\": [\"exclude_modules::alpha\",\n                               \"exclude_modules::beta\",\n                               \"exclude_modules::(global)\",\n                               \"exclude_modules::readFileSync\"]\n        },\n        {\n            \"test_name\": \"exclude modules exclude_functions no_trimming\",\n            \"comment\": \"makes sense to test in js as well. Include a function that's not there for more coverage\",\n            \"directory\": \"exclude_modules\",\n            \"kwargs\": {\"exclude_functions\": [\"beta\", \"gamma\"], \"no_trimming\": True},\n            \"expected_edges\": [[\"exclude_modules::(global)\", \"exclude_modules::alpha\"],\n                               [\"exclude_modules::alpha\", \"exclude_modules::alpha\"]],\n            \"expected_nodes\": [\"exclude_modules::alpha\",\n                               \"exclude_modules::(global)\",\n                               \"exclude_modules::readFileSync\"],\n        },\n        {\n            \"test_name\": \"exclude modules es6\",\n            \"directory\": \"exclude_modules_es6\",\n            \"kwargs\": {'source_type': 'module'},\n            \"expected_edges\": [[\"exclude_modules_es6::(global)\", \"exclude_modules_es6::alpha\"],\n                               [\"exclude_modules_es6::alpha\", \"exclude_modules_es6::alpha\"],\n                               [\"exclude_modules_es6::alpha\", \"exclude_modules_es6::beta\"],\n                               [\"exclude_modules_es6::beta\", \"exclude_modules_es6::readFileSync\"]],\n            \"expected_nodes\": [\"exclude_modules_es6::alpha\",\n                               \"exclude_modules_es6::beta\",\n                               \"exclude_modules_es6::(global)\",\n                               \"exclude_modules_es6::readFileSync\"]\n        },\n        {\n            \"test_name\": \"ambiguous_names\",\n            \"directory\": \"ambiguous_names\",\n            \"expected_edges\": [[\"ambiguous_names::Cadabra.cadabra_it\",\n                                \"ambiguous_names::Abra.abra_it\"],\n                               [\"ambiguous_names::main\",\n                                \"ambiguous_names::Cadabra.cadabra_it\"],\n                               [\"ambiguous_names::(global)\",\n                                \"ambiguous_names::main\"],\n                               [\"ambiguous_names::Abra.(constructor)\",\n                                \"ambiguous_names::Abra.abra_it\"]],\n            \"expected_nodes\": [\"ambiguous_names::Cadabra.cadabra_it\",\n                               \"ambiguous_names::main\",\n                               \"ambiguous_names::(global)\",\n                               \"ambiguous_names::Abra.abra_it\",\n                               \"ambiguous_names::Abra.(constructor)\"]\n        },\n        {\n            \"test_name\": \"ambiguous_names\",\n            \"directory\": \"ambiguous_names\",\n            \"kwargs\": {\"no_trimming\": True},\n            \"expected_edges\": [[\"ambiguous_names::Cadabra.cadabra_it\",\n                                \"ambiguous_names::Abra.abra_it\"],\n                               [\"ambiguous_names::main\",\n                                \"ambiguous_names::Cadabra.cadabra_it\"],\n                               [\"ambiguous_names::(global)\",\n                                \"ambiguous_names::main\"],\n                               [\"ambiguous_names::Abra.(constructor)\",\n                                \"ambiguous_names::Abra.abra_it\"]],\n            \"expected_nodes\": [\"ambiguous_names::Cadabra.cadabra_it\",\n                               \"ambiguous_names::main\",\n                               \"ambiguous_names::(global)\",\n                               \"ambiguous_names::Abra.abra_it\",\n                               \"ambiguous_names::Abra.(constructor)\",\n                               \"ambiguous_names::Abra.magic\",\n                               \"ambiguous_names::Cadabra.magic\",\n                               ]\n        },\n        {\n            \"test_name\": \"ambiguous_names exclude_namespaces\",\n            \"directory\": \"ambiguous_names\",\n            \"comment\": \"Also tests an important thing. .magic is in two classes in the first test. This eliminates a class and magic is resolved.\",\n            \"kwargs\": {\"exclude_namespaces\": ['Abra']},\n            \"expected_edges\": [[\"ambiguous_names::main\",\n                                \"ambiguous_names::Cadabra.cadabra_it\"],\n                               [\"ambiguous_names::(global)\",\n                                \"ambiguous_names::main\"],\n                               [\"ambiguous_names::main\",\n                                \"ambiguous_names::Cadabra.magic\"]],\n            \"expected_nodes\": [\"ambiguous_names::Cadabra.cadabra_it\",\n                               \"ambiguous_names::Cadabra.magic\",\n                               \"ambiguous_names::main\",\n                               \"ambiguous_names::(global)\"]\n        },\n        {\n            \"test_name\": \"weird_assignments\",\n            \"directory\": \"weird_assignments\",\n            \"expected_edges\": [[\"weird_assignments::(global)\",\n                                \"weird_assignments::get_ab\"]],\n            \"expected_nodes\": [\"weird_assignments::(global)\", \"weird_assignments::get_ab\"]\n        },\n        {\n            \"test_name\": \"complex_ownership\",\n            \"directory\": \"complex_ownership\",\n            \"expected_edges\": [[\"complex_ownership::(global)\",\n                                \"complex_ownership::DEF.toABC\"],\n                               [\"complex_ownership::DEF.toABC\",\n                                \"complex_ownership::ABC.(constructor)\"],\n                               [\"complex_ownership::(global)\",\n                                \"complex_ownership::ABC.apply\"],\n                               [\"complex_ownership::GHI.doit2\",\n                                \"complex_ownership::ABC.apply\"],\n                               [\"complex_ownership::(global)\",\n                                \"complex_ownership::GHI.doit2\"],\n                               [\"complex_ownership::(global)\",\n                                \"complex_ownership::ABC.doit\"],\n                               [\"complex_ownership::(global)\",\n                                \"complex_ownership::GHI.doit3\"]],\n            \"expected_nodes\": [\"complex_ownership::(global)\",\n                               \"complex_ownership::ABC.apply\",\n                               \"complex_ownership::ABC.doit\",\n                               \"complex_ownership::ABC.(constructor)\",\n                               \"complex_ownership::DEF.toABC\",\n                               \"complex_ownership::GHI.doit2\",\n                               \"complex_ownership::GHI.doit3\"]\n        },\n        {\n            \"test_name\": \"two_file_imports\",\n            \"directory\": \"two_file_imports\",\n            \"expected_edges\": [[\"importer::outer\", \"imported::myClass.(constructor)\"],\n                               [\"importer::outer\", \"imported::inner\"],\n                               [\"importer::(global)\", \"importer::outer\"],\n                               [\"imported::myClass.(constructor)\",\n                                \"imported::myClass.doit\"],\n                               [\"imported::myClass.doit\", \"imported::myClass.doit2\"]],\n            \"expected_nodes\": [\"imported::myClass.doit2\",\n                               \"imported::myClass.(constructor)\",\n                               \"imported::myClass.doit\",\n                               \"imported::inner\",\n                               \"importer::(global)\",\n                               \"importer::outer\"]\n        },\n        {\n            \"test_name\": \"globals\",\n            \"comment\": \"ensure that nested functions behave correctly\",\n            \"directory\": \"globals\",\n            \"expected_edges\": [[\"globals::a\", \"globals::b\"],\n                               [\"globals::a\", \"globals::c\"],\n                               [\"globals::c\", \"globals::d\"]],\n            \"expected_nodes\": [\"globals::d\", \"globals::a\", \"globals::c\", \"globals::b\"]\n        },\n        {\n            \"test_name\": \"scope confusion\",\n            \"comment\": \"Be sure that 'this' is used correctly\",\n            \"directory\": \"scoping\",\n            \"expected_edges\": [[\"scoping::(global)\", \"scoping::MyClass.a\"],\n                               [\"scoping::MyClass.a\", \"scoping::MyClass.scope_confusion\"],\n                               [\"scoping::(global)\", \"scoping::MyClass.b\"],\n                               [\"scoping::MyClass.b\", \"scoping::scope_confusion\"]],\n            \"expected_nodes\": [\"scoping::MyClass.scope_confusion\",\n                               \"scoping::MyClass.b\",\n                               \"scoping::scope_confusion\",\n                               \"scoping::MyClass.a\",\n                               \"scoping::(global)\"]\n        },\n        {\n            \"test_name\": \"chained\",\n            \"directory\": \"chained\",\n            \"expected_edges\": [[\"chained::(global)\", \"chained::Chain.(constructor)\"],\n                               [\"chained::(global)\", \"chained::Chain.mul\"],\n                               [\"chained::(global)\", \"chained::Chain.add\"],\n                               [\"chained::(global)\", \"chained::Chain.sub\"]],\n            \"expected_nodes\": [\"chained::Chain.sub\",\n                               \"chained::(global)\",\n                               \"chained::Chain.(constructor)\",\n                               \"chained::Chain.add\",\n                               \"chained::Chain.mul\"]\n        },\n        {\n            \"test_name\": \"inheritance\",\n            \"directory\": \"inheritance\",\n            \"expected_edges\": [[\"inheritance::(global)\",\n                                \"inheritance::ScaleDemo.(constructor)\"],\n                               [\"inheritance::(global)\", \"inheritance::pentaNum\"],\n                               [\"inheritance::ScaleDemo.(constructor)\",\n                                \"inheritance::MajorScales.majorNum\"]],\n            \"expected_nodes\": [\"inheritance::MajorScales.majorNum\",\n                               \"inheritance::ScaleDemo.(constructor)\",\n                               \"inheritance::pentaNum\",\n                               \"inheritance::(global)\"]\n        },\n        {\n            \"test_name\": \"inheritance_attr\",\n            \"directory\": \"inheritance_attr\",\n            \"comment\": \"This is rare but it happened while testing chart.js. Extends was done on an attr.\",\n            \"expected_edges\": [[\"inheritance_attr::(global)\",\n                                \"inheritance_attr::ClsB.meow\"]],\n            \"expected_nodes\": [\"inheritance_attr::(global)\",\n                               \"inheritance_attr::ClsB.meow\"]\n        },\n        {\n            \"test_name\": \"bad_parse\",\n            \"directory\": \"bad_parse\",\n            \"comments\": \"One file is bad. Test bad parse line\",\n            \"kwargs\": {\"skip_parse_errors\": True},\n            \"expected_edges\": [[\"file_a_good::(global)\", \"file_a_good::a\"]],\n            \"expected_nodes\": [\"file_a_good::(global)\", \"file_a_good::a\"]\n        },\n        {\n            \"test_name\": \"class_in_function\",\n            \"directory\": \"class_in_function\",\n            \"comments\": \"when a function defines a class within it\",\n            \"expected_edges\": [[\"class_in_function::(global)\",\n                                \"class_in_function::rectangleClassFactory\"]],\n            \"expected_nodes\": [\"class_in_function::rectangleClassFactory\",\n                               \"class_in_function::(global)\"]\n        },\n        {\n            \"test_name\": \"ternary_new\",\n            \"directory\": \"ternary_new\",\n            \"comments\": \"Ignore the name. This is for complex multi-layered object instantiation.\",\n            \"expected_edges\": [[\"ternary_new::Cadabra.init\", \"ternary_new::Cadabra.init\"],\n                               [\"ternary_new::Abra.init\", \"ternary_new::Abra.init\"],\n                               [\"ternary_new::(global)\", \"ternary_new::ClassMap.fact\"]],\n            \"expected_nodes\": [\"ternary_new::Cadabra.init\",\n                               \"ternary_new::ClassMap.fact\",\n                               \"ternary_new::(global)\",\n                               \"ternary_new::Abra.init\"]\n        },\n        {\n            \"test_name\": \"moment.js\",\n            \"directory\": \"moment\",\n            \"expected_edges\": [[\"moment::getWeeksInWeekYear\", \"moment::weeksInYear\"],\n                               [\"moment::configFromRFC2822\", \"moment::preprocessRFC2822\"],\n                               [\"moment::isNumberOrStringArray\", \"moment::isString\"],\n                               [\"moment::prepareConfig\", \"moment::isDate\"],\n                               [\"moment::handleStrictParse$1\", \"moment::createUTC\"],\n                               [\"moment::addSubtract\", \"moment::setMonth\"],\n                               [\"moment::makeGetSet\", \"moment::set$1\"],\n                               [\"moment::dayOfYearFromWeeks\", \"moment::firstWeekOffset\"],\n                               [\"moment::createDuration\", \"moment::Duration\"],\n                               [\"moment::prepareConfig\", \"moment::Moment\"],\n                               [\"moment::isNumberOrStringArray\", \"moment::isNumber\"],\n                               [\"moment::configFromInput\", \"moment::configFromString\"],\n                               [\"moment::createAdder\", \"moment::addSubtract\"],\n                               [\"moment::pastFuture\", \"moment::isFunction\"],\n                               [\"moment::configFromArray\", \"moment::daysInYear\"],\n                               [\"moment::isSame\", \"moment::normalizeUnits\"],\n                               [\"moment::getParsingFlags\", \"moment::defaultParsingFlags\"],\n                               [\"moment::makeFormatFunction\",\n                                \"moment::removeFormattingTokens\"],\n                               [\"moment::relativeTime$1\", \"moment::createDuration\"],\n                               [\"moment::defineLocale\", \"moment::deprecateSimple\"],\n                               [\"moment::localeMonthsParse\", \"moment::createUTC\"],\n                               [\"moment::getPrioritizedUnits\", \"moment::hasOwnProp\"],\n                               [\"moment::(global)\", \"moment::setHookCallback\"],\n                               [\"moment::deprecate\", \"moment::hasOwnProp\"],\n                               [\"moment::listWeekdaysImpl\", \"moment::isNumber\"],\n                               [\"moment::parseMs\", \"moment::toInt\"],\n                               [\"moment::createLocalOrUTC\", \"moment::isArray\"],\n                               [\"moment::relativeTime\", \"moment::isFunction\"],\n                               [\"moment::(global)\", \"moment::toInt\"],\n                               [\"moment::weekdaysShortRegex\", \"moment::hasOwnProp\"],\n                               [\"moment::isSame\", \"moment::isMoment\"],\n                               [\"moment::formatMoment\", \"moment::expandFormat\"],\n                               [\"moment::getSetWeekYearHelper\", \"moment::weekOfYear\"],\n                               [\"moment::max\", \"moment::pickBy\"],\n                               [\"moment::createDuration\", \"moment::momentsDifference\"],\n                               [\"moment::startOf\", \"moment::normalizeUnits\"],\n                               [\"moment::prepareConfig\", \"moment::isArray\"],\n                               [\"moment::bubble\", \"moment::monthsToDays\"],\n                               [\"moment::configFromInput\", \"moment::configFromObject\"],\n                               [\"moment::createLocal\", \"moment::createLocalOrUTC\"],\n                               [\"moment::defineLocale\", \"moment::mergeConfigs\"],\n                               [\"moment::addSubtract$1\", \"moment::createDuration\"],\n                               [\"moment::configFromStringAndArray\", \"moment::extend\"],\n                               [\"moment::parsingFlags\", \"moment::extend\"],\n                               [\"moment::as\", \"moment::daysToMonths\"],\n                               [\"moment::normalizeObjectUnits\", \"moment::normalizeUnits\"],\n                               [\"moment::weekdaysMinRegex\", \"moment::hasOwnProp\"],\n                               [\"moment::mergeConfigs\", \"moment::isObject\"],\n                               [\"moment::localeEras\", \"moment::getLocale\"],\n                               [\"moment::checkOverflow\", \"moment::getParsingFlags\"],\n                               [\"moment::configFromStringAndFormat\",\n                                \"moment::getParseRegexForToken\"],\n                               [\"moment::listMonthsShort\", \"moment::listMonthsImpl\"],\n                               [\"moment::extractFromRFC2822Strings\",\n                                \"moment::untruncateYear\"],\n                               [\"moment::(global)\", \"moment::addWeekParseToken\"],\n                               [\"moment::getSetWeekYearHelper\", \"moment::weeksInYear\"],\n                               [\"moment::configFromObject\", \"moment::configFromArray\"],\n                               [\"moment::isBetween\", \"moment::createLocal\"],\n                               [\"moment::addTimeToArrayFromToken\", \"moment::hasOwnProp\"],\n                               [\"moment::configFromStringAndArray\", \"moment::copyConfig\"],\n                               [\"moment::valueOf$1\", \"moment::toInt\"],\n                               [\"moment::to\", \"moment::createLocal\"],\n                               [\"moment::getSetMonth\", \"moment::setMonth\"],\n                               [\"moment::getSetGlobalLocale\", \"moment::getLocale\"],\n                               [\"moment::(global)\", \"moment::offset\"],\n                               [\"moment::copyConfig\", \"moment::getParsingFlags\"],\n                               [\"moment::offset\", \"moment::zeroFill\"],\n                               [\"moment::isCalendarSpec\", \"moment::isObject\"],\n                               [\"moment::isSame\", \"moment::createLocal\"],\n                               [\"moment::addSubtract\", \"moment::set$1\"],\n                               [\"moment::listWeekdaysImpl\", \"moment::getLocale\"],\n                               [\"moment::isDaylightSavingTimeShifted\",\n                                \"moment::prepareConfig\"],\n                               [\"moment::diff\", \"moment::absFloor\"],\n                               [\"moment::(global)\", \"moment::zeroFill\"],\n                               [\"moment::createDuration\", \"moment::toInt\"],\n                               [\"moment::setOffsetToLocal\", \"moment::getDateOffset\"],\n                               [\"moment::subtract$1\", \"moment::addSubtract$1\"],\n                               [\"moment::toISOString\", \"moment::isFunction\"],\n                               [\"moment::normalizeObjectUnits\", \"moment::hasOwnProp\"],\n                               [\"moment::mergeConfigs\", \"moment::extend\"],\n                               [\"moment::prepareConfig\",\n                                \"moment::configFromStringAndArray\"],\n                               [\"moment::locale\", \"moment::getLocale\"],\n                               [\"moment::listMonthsImpl\", \"moment::get$1\"],\n                               [\"moment::updateLocale\", \"moment::loadLocale\"],\n                               [\"moment::configFromObject\", \"moment::map\"],\n                               [\"moment::meridiem\", \"moment::addFormatToken\"],\n                               [\"moment::calendar$1\", \"moment::createLocal\"],\n                               [\"moment::configFromArray\", \"moment::defaults\"],\n                               [\"moment::toISOString$1\", \"moment::sign\"],\n                               [\"moment::configFromStringAndFormat\",\n                                \"moment::getParsingFlags\"],\n                               [\"moment::configFromRFC2822\", \"moment::getParsingFlags\"],\n                               [\"moment::getIsLeapYear\", \"moment::isLeapYear\"],\n                               [\"moment::isMomentInputObject\", \"moment::hasOwnProp\"],\n                               [\"moment::stringSet\", \"moment::normalizeObjectUnits\"],\n                               [\"moment::cloneWithOffset\", \"moment::createLocal\"],\n                               [\"moment::isValid$2\", \"moment::isValid\"],\n                               [\"moment::(global)\", \"moment::hasOwnProp\"],\n                               [\"moment::(global)\", \"moment::makeGetSet\"],\n                               [\"moment::configFromInput\", \"moment::configFromArray\"],\n                               [\"moment::configFromRFC2822\",\n                                \"moment::extractFromRFC2822Strings\"],\n                               [\"moment::(global)\", \"moment::getSetGlobalLocale\"],\n                               [\"moment::getSetISOWeek\", \"moment::weekOfYear\"],\n                               [\"moment::isNumberOrStringArray\", \"moment::isArray\"],\n                               [\"moment::monthsShortRegex\", \"moment::hasOwnProp\"],\n                               [\"moment::set\", \"moment::hasOwnProp\"],\n                               [\"moment::from\", \"moment::createLocal\"],\n                               [\"moment::deprecate\", \"moment::warn\"],\n                               [\"moment::configFromArray\", \"moment::currentDateArray\"],\n                               [\"moment::addSubtract\", \"moment::absRound\"],\n                               [\"moment::configFromInput\", \"moment::isNumber\"],\n                               [\"moment::(global)\", \"moment::getParsingFlags\"],\n                               [\"moment::defineLocale\", \"moment::loadLocale\"],\n                               [\"moment::getSetOffset\", \"moment::getDateOffset\"],\n                               [\"moment::Duration\", \"moment::normalizeObjectUnits\"],\n                               [\"moment::createInvalid\", \"moment::extend\"],\n                               [\"moment::getSetISODayOfWeek\", \"moment::parseIsoWeekday\"],\n                               [\"moment::configFromStringAndArray\",\n                                \"moment::getParsingFlags\"],\n                               [\"moment::parsingFlags\", \"moment::getParsingFlags\"],\n                               [\"moment::endOf\", \"moment::normalizeUnits\"],\n                               [\"moment::erasNameRegex\", \"moment::hasOwnProp\"],\n                               [\"moment::diff\", \"moment::monthDiff\"],\n                               [\"moment::getSetOffset\", \"moment::offsetFromString\"],\n                               [\"moment::dayOfYearFromWeekInfo\", \"moment::createLocal\"],\n                               [\"moment::calendar\", \"moment::isFunction\"],\n                               [\"moment::configFromRFC2822\", \"moment::checkWeekday\"],\n                               [\"moment::getSetOffset\", \"moment::createDuration\"],\n                               [\"moment::configFromString\", \"moment::configFromISO\"],\n                               [\"moment::getWeeksInYear\", \"moment::weeksInYear\"],\n                               [\"moment::set\", \"moment::isFunction\"],\n                               [\"moment::(global)\", \"moment::createInvalid\"],\n                               [\"moment::daysInMonth\", \"moment::isLeapYear\"],\n                               [\"moment::stringSet\", \"moment::isFunction\"],\n                               [\"moment::getISOWeeksInYear\", \"moment::weeksInYear\"],\n                               [\"moment::dayOfYearFromWeekInfo\",\n                                \"moment::dayOfYearFromWeeks\"],\n                               [\"moment::isAfter\", \"moment::normalizeUnits\"],\n                               [\"moment::calendar$1\", \"moment::isMomentInput\"],\n                               [\"moment::getLocale\", \"moment::chooseLocale\"],\n                               [\"moment::toNow\", \"moment::createLocal\"],\n                               [\"moment::prepareConfig\", \"moment::createInvalid\"],\n                               [\"moment::weekdaysRegex\", \"moment::hasOwnProp\"],\n                               [\"moment::loadLocale\", \"moment::getSetGlobalLocale\"],\n                               [\"moment::pickBy\", \"moment::createLocal\"],\n                               [\"moment::fromNow\", \"moment::createLocal\"],\n                               [\"moment::setWeekAll\", \"moment::dayOfYearFromWeeks\"],\n                               [\"moment::configFromStringAndFormat\",\n                                \"moment::addTimeToArrayFromToken\"],\n                               [\"moment::isAfter\", \"moment::isMoment\"],\n                               [\"moment::formatMoment\", \"moment::makeFormatFunction\"],\n                               [\"moment::createAdder\", \"moment::deprecateSimple\"],\n                               [\"moment::updateLocale\", \"moment::mergeConfigs\"],\n                               [\"moment::stringSet\", \"moment::getPrioritizedUnits\"],\n                               [\"moment::localeWeek\", \"moment::weekOfYear\"],\n                               [\"moment::as\", \"moment::monthsToDays\"],\n                               [\"moment::setMonth\", \"moment::isNumber\"],\n                               [\"moment::isMomentInput\", \"moment::isMomentInputObject\"],\n                               [\"moment::chooseLocale\", \"moment::commonPrefix\"],\n                               [\"moment::monthsRegex\", \"moment::hasOwnProp\"],\n                               [\"moment::computeWeekdaysParse\", \"moment::createUTC\"],\n                               [\"moment::configFromInput\", \"moment::isDate\"],\n                               [\"moment::prepareConfig\", \"moment::configFromInput\"],\n                               [\"moment::localeMonthsShort\", \"moment::isArray\"],\n                               [\"moment::get$1\", \"moment::getLocale\"],\n                               [\"moment::as\", \"moment::normalizeUnits\"],\n                               [\"moment::isMomentInputObject\", \"moment::isObjectEmpty\"],\n                               [\"moment::to\", \"moment::createDuration\"],\n                               [\"moment::configFromInput\", \"moment::isUndefined\"],\n                               [\"moment::dayOfYearFromWeeks\", \"moment::daysInYear\"],\n                               [\"moment::listMonthsImpl\", \"moment::isNumber\"],\n                               [\"moment::getLocale\", \"moment::loadLocale\"],\n                               [\"moment::configFromStringAndFormat\",\n                                \"moment::configFromRFC2822\"],\n                               [\"moment::isMomentInput\", \"moment::isMoment\"],\n                               [\"moment::isDaylightSavingTimeShifted\",\n                                \"moment::createLocal\"],\n                               [\"moment::erasAbbrRegex\", \"moment::hasOwnProp\"],\n                               [\"moment::computeMonthsParse\", \"moment::createUTC\"],\n                               [\"moment::stringGet\", \"moment::isFunction\"],\n                               [\"moment::isObjectEmpty\", \"moment::hasOwnProp\"],\n                               [\"moment::listMonths\", \"moment::listMonthsImpl\"],\n                               [\"moment::compareArrays\", \"moment::toInt\"],\n                               [\"moment::createUnix\", \"moment::createLocal\"],\n                               [\"moment::addFormatToken\", \"moment::zeroFill\"],\n                               [\"moment::isAfter\", \"moment::createLocal\"],\n                               [\"moment::createInvalid$1\", \"moment::createDuration\"],\n                               [\"moment::createAdder\", \"moment::createDuration\"],\n                               [\"moment::isDaylightSavingTimeShifted\",\n                                \"moment::isUndefined\"],\n                               [\"moment::configFromRFC2822\", \"moment::calculateOffset\"],\n                               [\"moment::pickBy\", \"moment::isArray\"],\n                               [\"moment::localeWeekdaysMin\", \"moment::shiftWeekdays\"],\n                               [\"moment::configFromInput\", \"moment::isObject\"],\n                               [\"moment::getLocale\", \"moment::isArray\"],\n                               [\"moment::createDuration\", \"moment::absRound\"],\n                               [\"moment::configFromInput\", \"moment::isArray\"],\n                               [\"moment::deprecateSimple\", \"moment::warn\"],\n                               [\"moment::extend\", \"moment::hasOwnProp\"],\n                               [\"moment::(global)\", \"moment::addParseToken\"],\n                               [\"moment::(global)\", \"moment::makeAs\"],\n                               [\"moment::from\", \"moment::createDuration\"],\n                               [\"moment::addParseToken\", \"moment::isNumber\"],\n                               [\"moment::chooseLocale\", \"moment::normalizeLocale\"],\n                               [\"moment::Moment\", \"moment::copyConfig\"],\n                               [\"moment::localeMonths\", \"moment::isArray\"],\n                               [\"moment::clone$1\", \"moment::createDuration\"],\n                               [\"moment::getSetGlobalLocale\", \"moment::isUndefined\"],\n                               [\"moment::stringSet\", \"moment::normalizeUnits\"],\n                               [\"moment::isBefore\", \"moment::isMoment\"],\n                               [\"moment::makeFormatFunction\", \"moment::isFunction\"],\n                               [\"moment::defineLocale\", \"moment::getSetGlobalLocale\"],\n                               [\"moment::min\", \"moment::pickBy\"],\n                               [\"moment::createUTC\", \"moment::createLocalOrUTC\"],\n                               [\"moment::add$1\", \"moment::addSubtract$1\"],\n                               [\"moment::get$1\", \"moment::createUTC\"],\n                               [\"moment::weeksInYear\", \"moment::daysInYear\"],\n                               [\"moment::configFromObject\",\n                                \"moment::normalizeObjectUnits\"],\n                               [\"moment::dayOfYearFromWeekInfo\", \"moment::weekOfYear\"],\n                               [\"moment::prepareConfig\", \"moment::isMoment\"],\n                               [\"moment::offsetFromString\", \"moment::toInt\"],\n                               [\"moment::calendar$1\", \"moment::isFunction\"],\n                               [\"moment::(global)\", \"moment::createAdder\"],\n                               [\"moment::getParseRegexForToken\", \"moment::hasOwnProp\"],\n                               [\"moment::cloneWithOffset\", \"moment::isDate\"],\n                               [\"moment::configFromArray\", \"moment::getParsingFlags\"],\n                               [\"moment::localeWeekdays\", \"moment::isArray\"],\n                               [\"moment::dayOfYearFromWeekInfo\", \"moment::defaults\"],\n                               [\"moment::configFromStringAndArray\",\n                                \"moment::configFromStringAndFormat\"],\n                               [\"moment::getEraYear\", \"moment::hooks\"],\n                               [\"moment::setOffsetToParsedOffset\",\n                                \"moment::offsetFromString\"],\n                               [\"moment::bubble\", \"moment::absFloor\"],\n                               [\"moment::createInvalid\", \"moment::createUTC\"],\n                               [\"moment::addSubtract\", \"moment::get\"],\n                               [\"moment::weekOfYear\", \"moment::weeksInYear\"],\n                               [\"moment::prepareConfig\",\n                                \"moment::configFromStringAndFormat\"],\n                               [\"moment::(global)\", \"moment::addWeekYearFormatToken\"],\n                               [\"moment::setMonth\", \"moment::toInt\"],\n                               [\"moment::configFromInput\", \"moment::map\"],\n                               [\"moment::offset\", \"moment::addFormatToken\"],\n                               [\"moment::(global)\", \"moment::deprecate\"],\n                               [\"moment::listWeekdays\", \"moment::listWeekdaysImpl\"],\n                               [\"moment::endOf\", \"moment::mod$1\"],\n                               [\"moment::isValid\", \"moment::getParsingFlags\"],\n                               [\"moment::isMomentInput\", \"moment::isNumberOrStringArray\"],\n                               [\"moment::defineLocale\", \"moment::Locale\"],\n                               [\"moment::localeWeekdays\", \"moment::shiftWeekdays\"],\n                               [\"moment::calendar$1\", \"moment::isCalendarSpec\"],\n                               [\"moment::pastFuture\", \"moment::format\"],\n                               [\"moment::configFromStringAndArray\", \"moment::isValid\"],\n                               [\"moment::isDaylightSavingTimeShifted\",\n                                \"moment::copyConfig\"],\n                               [\"moment::mergeConfigs\", \"moment::hasOwnProp\"],\n                               [\"moment::setMonth\", \"moment::daysInMonth\"],\n                               [\"moment::stringGet\", \"moment::normalizeUnits\"],\n                               [\"moment::humanize\", \"moment::relativeTime$1\"],\n                               [\"moment::toISOString\", \"moment::formatMoment\"],\n                               [\"moment::createFromConfig\", \"moment::prepareConfig\"],\n                               [\"moment::listWeekdaysMin\", \"moment::listWeekdaysImpl\"],\n                               [\"moment::addWeekYearFormatToken\",\n                                \"moment::addFormatToken\"],\n                               [\"moment::prepareConfig\", \"moment::isValid\"],\n                               [\"moment::handleStrictParse\", \"moment::createUTC\"],\n                               [\"moment::createDuration\", \"moment::hasOwnProp\"],\n                               [\"moment::isCalendarSpec\", \"moment::hasOwnProp\"],\n                               [\"moment::erasNarrowRegex\", \"moment::hasOwnProp\"],\n                               [\"moment::localeEras\", \"moment::hooks\"],\n                               [\"moment::weekOfYear\", \"moment::firstWeekOffset\"],\n                               [\"moment::Duration\", \"moment::getLocale\"],\n                               [\"moment::isMomentInput\", \"moment::isString\"],\n                               [\"moment::getSetGlobalLocale\", \"moment::defineLocale\"],\n                               [\"moment::createDuration\", \"moment::parseIso\"],\n                               [\"moment::configFromStringAndFormat\",\n                                \"moment::meridiemFixWrap\"],\n                               [\"moment::diff\", \"moment::normalizeUnits\"],\n                               [\"moment::configFromStringAndFormat\",\n                                \"moment::checkOverflow\"],\n                               [\"moment::daysInMonth\", \"moment::mod\"],\n                               [\"moment::createFromConfig\", \"moment::checkOverflow\"],\n                               [\"moment::get$2\", \"moment::normalizeUnits\"],\n                               [\"moment::(global)\", \"moment::addRegexToken\"],\n                               [\"moment::isMomentInputObject\", \"moment::isObject\"],\n                               [\"moment::isBetween\", \"moment::isMoment\"],\n                               [\"moment::createDuration\", \"moment::createLocal\"],\n                               [\"moment::(global)\", \"moment::addUnitAlias\"],\n                               [\"moment::createLocalOrUTC\", \"moment::isObjectEmpty\"],\n                               [\"moment::isMomentInput\", \"moment::isNumber\"],\n                               [\"moment::addParseToken\", \"moment::toInt\"],\n                               [\"moment::startOf\", \"moment::mod$1\"],\n                               [\"moment::makeGetSet\", \"moment::get\"],\n                               [\"moment::createLocalOrUTC\", \"moment::isObject\"],\n                               [\"moment::to\", \"moment::isMoment\"],\n                               [\"moment::checkWeekday\", \"moment::getParsingFlags\"],\n                               [\"moment::defineLocale\", \"moment::defineLocale\"],\n                               [\"moment::localeErasConvertYear\", \"moment::hooks\"],\n                               [\"moment::getSetMonth\", \"moment::get\"],\n                               [\"moment::createInvalid\", \"moment::getParsingFlags\"],\n                               [\"moment::momentsDifference\", \"moment::cloneWithOffset\"],\n                               [\"moment::configFromISO\",\n                                \"moment::configFromStringAndFormat\"],\n                               [\"moment::configFromStringAndFormat\",\n                                \"moment::configFromArray\"],\n                               [\"moment::updateLocale\", \"moment::getSetGlobalLocale\"],\n                               [\"moment::unescapeFormat\", \"moment::regexEscape\"],\n                               [\"moment::configFromISO\", \"moment::getParsingFlags\"],\n                               [\"moment::configFromStringAndFormat\",\n                                \"moment::configFromISO\"],\n                               [\"moment::getDaysInMonth\", \"moment::daysInMonth\"],\n                               [\"moment::daysInYear\", \"moment::isLeapYear\"],\n                               [\"moment::diff\", \"moment::cloneWithOffset\"],\n                               [\"moment::configFromArray\",\n                                \"moment::dayOfYearFromWeekInfo\"],\n                               [\"moment::prepareConfig\", \"moment::checkOverflow\"],\n                               [\"moment::addRegexToken\", \"moment::isFunction\"],\n                               [\"moment::isBefore\", \"moment::normalizeUnits\"],\n                               [\"moment::bubble\", \"moment::absCeil\"],\n                               [\"moment::isDurationValid\", \"moment::toInt\"],\n                               [\"moment::computeWeekdaysParse\", \"moment::regexEscape\"],\n                               [\"moment::momentsDifference\",\n                                \"moment::positiveMomentsDifference\"],\n                               [\"moment::cloneWithOffset\", \"moment::isMoment\"],\n                               [\"moment::getParseRegexForToken\", \"moment::unescapeFormat\"],\n                               [\"moment::configFromArray\", \"moment::createUTCDate\"],\n                               [\"moment::toISOString$1\", \"moment::absFloor\"],\n                               [\"moment::isDaylightSavingTimeShifted\",\n                                \"moment::compareArrays\"],\n                               [\"moment::monthDiff\", \"moment::monthDiff\"],\n                               [\"moment::from\", \"moment::isMoment\"],\n                               [\"moment::isCalendarSpec\", \"moment::isObjectEmpty\"],\n                               [\"moment::bubble\", \"moment::daysToMonths\"],\n                               [\"moment::chooseLocale\", \"moment::loadLocale\"],\n                               [\"moment::weeksInYear\", \"moment::firstWeekOffset\"],\n                               [\"moment::listWeekdaysImpl\", \"moment::get$1\"],\n                               [\"moment::calendar$1\", \"moment::cloneWithOffset\"],\n                               [\"moment::isMomentInput\", \"moment::isDate\"],\n                               [\"moment::(global)\", \"moment::offsetFromString\"],\n                               [\"moment::addWeekParseToken\", \"moment::addParseToken\"],\n                               [\"moment::getISOWeeksInISOWeekYear\", \"moment::weeksInYear\"],\n                               [\"moment::invalidAt\", \"moment::getParsingFlags\"],\n                               [\"moment::computeMonthsParse\", \"moment::regexEscape\"],\n                               [\"moment::localeWeekdaysShort\", \"moment::shiftWeekdays\"],\n                               [\"moment::getSetOffset\", \"moment::addSubtract\"],\n                               [\"moment::firstWeekOffset\", \"moment::createUTCDate\"],\n                               [\"moment::weeks\", \"moment::absFloor\"],\n                               [\"moment::updateLocale\", \"moment::Locale\"],\n                               [\"moment::copyConfig\", \"moment::isUndefined\"],\n                               [\"moment::createDuration\", \"moment::isDuration\"],\n                               [\"moment::hasAlignedHourOffset\", \"moment::createLocal\"],\n                               [\"moment::clone\", \"moment::Moment\"],\n                               [\"moment::set$1\", \"moment::toInt\"],\n                               [\"moment::getSetDayOfWeek\", \"moment::parseWeekday\"],\n                               [\"moment::setWeekAll\", \"moment::createUTCDate\"],\n                               [\"moment::(global)\", \"moment::meridiem\"],\n                               [\"moment::isDaylightSavingTimeShifted\",\n                                \"moment::createUTC\"],\n                               [\"moment::set$1\", \"moment::isLeapYear\"],\n                               [\"moment::configFromStringAndFormat\",\n                                \"moment::expandFormat\"],\n                               [\"moment::set$1\", \"moment::daysInMonth\"],\n                               [\"moment::format\", \"moment::formatMoment\"],\n                               [\"moment::localeWeekdaysParse\", \"moment::createUTC\"],\n                               [\"moment::deprecate\", \"moment::extend\"],\n                               [\"moment::computeErasParse\", \"moment::regexEscape\"],\n                               [\"moment::(global)\", \"moment::makeGetter\"],\n                               [\"moment::isBefore\", \"moment::createLocal\"],\n                               [\"moment::configFromString\", \"moment::configFromRFC2822\"],\n                               [\"moment::dayOfYearFromWeekInfo\",\n                                \"moment::getParsingFlags\"],\n                               [\"moment::dayOfYearFromWeekInfo\", \"moment::weeksInYear\"],\n                               [\"moment::(global)\", \"moment::addFormatToken\"],\n                               [\"moment::createDuration\", \"moment::isNumber\"],\n                               [\"moment::createLocalOrUTC\", \"moment::createFromConfig\"],\n                               [\"moment::(global)\", \"moment::addUnitPriority\"],\n                               [\"moment::createFromConfig\", \"moment::Moment\"],\n                               [\"moment::checkOverflow\", \"moment::daysInMonth\"],\n                               [\"moment::listWeekdaysShort\", \"moment::listWeekdaysImpl\"],\n                               [\"moment::Duration\", \"moment::isDurationValid\"],\n                               [\"moment::toInt\", \"moment::absFloor\"],\n                               [\"moment::isDurationValid\", \"moment::hasOwnProp\"],\n                               [\"moment::prepareConfig\", \"moment::getLocale\"]],\n            \"expected_nodes\": [\"moment::weeksInYear\",\n                               \"moment::toISOString$1\",\n                               \"moment::createFromConfig\",\n                               \"moment::mod$1\",\n                               \"moment::checkOverflow\",\n                               \"moment::configFromISO\",\n                               \"moment::getSetISODayOfWeek\",\n                               \"moment::regexEscape\",\n                               \"moment::isCalendarSpec\",\n                               \"moment::from\",\n                               \"moment::setOffsetToLocal\",\n                               \"moment::setHookCallback\",\n                               \"moment::deprecateSimple\",\n                               \"moment::checkWeekday\",\n                               \"moment::Locale\",\n                               \"moment::daysToMonths\",\n                               \"moment::getIsLeapYear\",\n                               \"moment::isFunction\",\n                               \"moment::defineLocale\",\n                               \"moment::loadLocale\",\n                               \"moment::configFromObject\",\n                               \"moment::listWeekdaysShort\",\n                               \"moment::addFormatToken\",\n                               \"moment::getEraYear\",\n                               \"moment::startOf\",\n                               \"moment::makeGetSet\",\n                               \"moment::fromNow\",\n                               \"moment::currentDateArray\",\n                               \"moment::makeAs\",\n                               \"moment::stringGet\",\n                               \"moment::createAdder\",\n                               \"moment::copyConfig\",\n                               \"moment::hasAlignedHourOffset\",\n                               \"moment::getSetDayOfWeek\",\n                               \"moment::isValid\",\n                               \"moment::isMomentInputObject\",\n                               \"moment::localeWeek\",\n                               \"moment::getWeeksInYear\",\n                               \"moment::getDaysInMonth\",\n                               \"moment::sign\",\n                               \"moment::dayOfYearFromWeeks\",\n                               \"moment::addSubtract$1\",\n                               \"moment::getLocale\",\n                               \"moment::setMonth\",\n                               \"moment::addTimeToArrayFromToken\",\n                               \"moment::getWeeksInWeekYear\",\n                               \"moment::configFromStringAndFormat\",\n                               \"moment::meridiem\",\n                               \"moment::handleStrictParse$1\",\n                               \"moment::set\",\n                               \"moment::parseIso\",\n                               \"moment::chooseLocale\",\n                               \"moment::daysInYear\",\n                               \"moment::localeErasConvertYear\",\n                               \"moment::createInvalid\",\n                               \"moment::isBefore\",\n                               \"moment::erasNameRegex\",\n                               \"moment::handleStrictParse\",\n                               \"moment::subtract$1\",\n                               \"moment::defaultParsingFlags\",\n                               \"moment::meridiemFixWrap\",\n                               \"moment::parseMs\",\n                               \"moment::isNumber\",\n                               \"moment::createInvalid$1\",\n                               \"moment::createUnix\",\n                               \"moment::isSame\",\n                               \"moment::relativeTime$1\",\n                               \"moment::addUnitAlias\",\n                               \"moment::createDuration\",\n                               \"moment::expandFormat\",\n                               \"moment::deprecate\",\n                               \"moment::to\",\n                               \"moment::offset\",\n                               \"moment::getParsingFlags\",\n                               \"moment::absRound\",\n                               \"moment::firstWeekOffset\",\n                               \"moment::monthsToDays\",\n                               \"moment::isAfter\",\n                               \"moment::localeMonths\",\n                               \"moment::toInt\",\n                               \"moment::normalizeLocale\",\n                               \"moment::isUndefined\",\n                               \"moment::weeks\",\n                               \"moment::createLocal\",\n                               \"moment::addRegexToken\",\n                               \"moment::add$1\",\n                               \"moment::makeGetter\",\n                               \"moment::createUTCDate\",\n                               \"moment::computeWeekdaysParse\",\n                               \"moment::configFromStringAndArray\",\n                               \"moment::absCeil\",\n                               \"moment::normalizeUnits\",\n                               \"moment::max\",\n                               \"moment::pickBy\",\n                               \"moment::endOf\",\n                               \"moment::configFromRFC2822\",\n                               \"moment::isDaylightSavingTimeShifted\",\n                               \"moment::bubble\",\n                               \"moment::createUTC\",\n                               \"moment::getPrioritizedUnits\",\n                               \"moment::isObjectEmpty\",\n                               \"moment::isObject\",\n                               \"moment::zeroFill\",\n                               \"moment::addParseToken\",\n                               \"moment::calendar\",\n                               \"moment::momentsDifference\",\n                               \"moment::unescapeFormat\",\n                               \"moment::monthsRegex\",\n                               \"moment::compareArrays\",\n                               \"moment::setOffsetToParsedOffset\",\n                               \"moment::prepareConfig\",\n                               \"moment::toISOString\",\n                               \"moment::isValid$2\",\n                               \"moment::weekOfYear\",\n                               \"moment::format\",\n                               \"moment::localeWeekdays\",\n                               \"moment::getISOWeeksInISOWeekYear\",\n                               \"moment::isMoment\",\n                               \"moment::hooks\",\n                               \"moment::shiftWeekdays\",\n                               \"moment::(global)\",\n                               \"moment::preprocessRFC2822\",\n                               \"moment::clone$1\",\n                               \"moment::isNumberOrStringArray\",\n                               \"moment::warn\",\n                               \"moment::weekdaysRegex\",\n                               \"moment::formatMoment\",\n                               \"moment::getSetISOWeek\",\n                               \"moment::weekdaysMinRegex\",\n                               \"moment::listMonths\",\n                               \"moment::isDuration\",\n                               \"moment::extractFromRFC2822Strings\",\n                               \"moment::weekdaysShortRegex\",\n                               \"moment::configFromArray\",\n                               \"moment::untruncateYear\",\n                               \"moment::min\",\n                               \"moment::Duration\",\n                               \"moment::getParseRegexForToken\",\n                               \"moment::erasNarrowRegex\",\n                               \"moment::removeFormattingTokens\",\n                               \"moment::updateLocale\",\n                               \"moment::listWeekdays\",\n                               \"moment::mergeConfigs\",\n                               \"moment::isString\",\n                               \"moment::getSetOffset\",\n                               \"moment::hasOwnProp\",\n                               \"moment::configFromString\",\n                               \"moment::isDate\",\n                               \"moment::parsingFlags\",\n                               \"moment::calculateOffset\",\n                               \"moment::stringSet\",\n                               \"moment::listMonthsImpl\",\n                               \"moment::dayOfYearFromWeekInfo\",\n                               \"moment::isDurationValid\",\n                               \"moment::positiveMomentsDifference\",\n                               \"moment::clone\",\n                               \"moment::map\",\n                               \"moment::get$1\",\n                               \"moment::makeFormatFunction\",\n                               \"moment::erasAbbrRegex\",\n                               \"moment::computeMonthsParse\",\n                               \"moment::daysInMonth\",\n                               \"moment::listWeekdaysImpl\",\n                               \"moment::computeErasParse\",\n                               \"moment::getSetWeekYearHelper\",\n                               \"moment::pastFuture\",\n                               \"moment::isBetween\",\n                               \"moment::listWeekdaysMin\",\n                               \"moment::normalizeObjectUnits\",\n                               \"moment::isMomentInput\",\n                               \"moment::getSetMonth\",\n                               \"moment::parseWeekday\",\n                               \"moment::offsetFromString\",\n                               \"moment::addWeekParseToken\",\n                               \"moment::set$1\",\n                               \"moment::localeMonthsShort\",\n                               \"moment::addUnitPriority\",\n                               \"moment::localeWeekdaysMin\",\n                               \"moment::createLocalOrUTC\",\n                               \"moment::commonPrefix\",\n                               \"moment::getSetGlobalLocale\",\n                               \"moment::valueOf$1\",\n                               \"moment::localeMonthsParse\",\n                               \"moment::addWeekYearFormatToken\",\n                               \"moment::as\",\n                               \"moment::mod\",\n                               \"moment::get\",\n                               \"moment::configFromInput\",\n                               \"moment::humanize\",\n                               \"moment::addSubtract\",\n                               \"moment::listMonthsShort\",\n                               \"moment::absFloor\",\n                               \"moment::relativeTime\",\n                               \"moment::monthDiff\",\n                               \"moment::getISOWeeksInYear\",\n                               \"moment::isArray\",\n                               \"moment::setWeekAll\",\n                               \"moment::monthsShortRegex\",\n                               \"moment::Moment\",\n                               \"moment::extend\",\n                               \"moment::locale\",\n                               \"moment::cloneWithOffset\",\n                               \"moment::localeWeekdaysParse\",\n                               \"moment::toNow\",\n                               \"moment::localeEras\",\n                               \"moment::localeWeekdaysShort\",\n                               \"moment::get$2\",\n                               \"moment::parseIsoWeekday\",\n                               \"moment::diff\",\n                               \"moment::getDateOffset\",\n                               \"moment::calendar$1\",\n                               \"moment::invalidAt\",\n                               \"moment::defaults\",\n                               \"moment::isLeapYear\"]\n        },\n        {\n            \"test_name\": \"moment_depth_1\",\n            \"directory\": \"moment\",\n            \"kwargs\": {\"target_function\": \"createDuration\",\n                       \"upstream_depth\": \"1\",\n                       \"downstream_depth\": \"1\"},\n            \"expected_edges\": [[\"moment::clone$1\", \"moment::createDuration\"],\n                               [\"moment::createInvalid$1\", \"moment::createDuration\"],\n                               [\"moment::createDuration\", \"moment::isDuration\"],\n                               [\"moment::relativeTime$1\", \"moment::createDuration\"],\n                               [\"moment::createDuration\", \"moment::Duration\"],\n                               [\"moment::createDuration\", \"moment::isNumber\"],\n                               [\"moment::createDuration\", \"moment::absRound\"],\n                               [\"moment::createAdder\", \"moment::createDuration\"],\n                               [\"moment::createDuration\", \"moment::hasOwnProp\"],\n                               [\"moment::addSubtract$1\", \"moment::createDuration\"],\n                               [\"moment::createDuration\", \"moment::parseIso\"],\n                               [\"moment::from\", \"moment::createLocal\"],\n                               [\"moment::to\", \"moment::createLocal\"],\n                               [\"moment::createDuration\", \"moment::momentsDifference\"],\n                               [\"moment::to\", \"moment::createDuration\"],\n                               [\"moment::from\", \"moment::createDuration\"],\n                               [\"moment::getSetOffset\", \"moment::createDuration\"],\n                               [\"moment::createDuration\", \"moment::toInt\"],\n                               [\"moment::createDuration\", \"moment::createLocal\"]],\n            \"expected_nodes\": [\"moment::toInt\",\n                               \"moment::Duration\",\n                               \"moment::createAdder\",\n                               \"moment::createDuration\",\n                               \"moment::absRound\",\n                               \"moment::from\",\n                               \"moment::addSubtract$1\",\n                               \"moment::isDuration\",\n                               \"moment::isNumber\",\n                               \"moment::hasOwnProp\",\n                               \"moment::createLocal\",\n                               \"moment::parseIso\",\n                               \"moment::getSetOffset\",\n                               \"moment::relativeTime$1\",\n                               \"moment::createInvalid$1\",\n                               \"moment::clone$1\",\n                               \"moment::to\",\n                               \"moment::momentsDifference\"]\n        },\n        {\n            \"test_name\": \"moment_only_down\",\n            \"directory\": \"moment\",\n            \"kwargs\": {\"target_function\": \"createDuration\", \"downstream_depth\": \"1\"},\n            \"expected_edges\": [[\"moment::createDuration\", \"moment::hasOwnProp\"],\n                               [\"moment::createDuration\", \"moment::isDuration\"],\n                               [\"moment::createDuration\", \"moment::momentsDifference\"],\n                               [\"moment::createDuration\", \"moment::absRound\"],\n                               [\"moment::createDuration\", \"moment::isNumber\"],\n                               [\"moment::createDuration\", \"moment::parseIso\"],\n                               [\"moment::createDuration\", \"moment::toInt\"],\n                               [\"moment::createDuration\", \"moment::Duration\"],\n                               [\"moment::createDuration\", \"moment::createLocal\"]],\n            \"expected_nodes\": [\"moment::parseIso\",\n                               \"moment::createDuration\",\n                               \"moment::isNumber\",\n                               \"moment::hasOwnProp\",\n                               \"moment::toInt\",\n                               \"moment::isDuration\",\n                               \"moment::createLocal\",\n                               \"moment::momentsDifference\",\n                               \"moment::absRound\",\n                               \"moment::Duration\"]\n        },\n        {\n            \"test_name\": \"moment_only_up\",\n            \"directory\": \"moment\",\n            \"kwargs\": {\"target_function\": \"createDuration\", \"upstream_depth\": \"1\"},\n            \"expected_edges\": [[\"moment::createInvalid$1\", \"moment::createDuration\"],\n                               [\"moment::to\", \"moment::createDuration\"],\n                               [\"moment::from\", \"moment::createDuration\"],\n                               [\"moment::createAdder\", \"moment::createDuration\"],\n                               [\"moment::getSetOffset\", \"moment::createDuration\"],\n                               [\"moment::clone$1\", \"moment::createDuration\"],\n                               [\"moment::addSubtract$1\", \"moment::createDuration\"],\n                               [\"moment::relativeTime$1\", \"moment::createDuration\"]],\n            \"expected_nodes\": [\"moment::relativeTime$1\",\n                               \"moment::createDuration\",\n                               \"moment::createInvalid$1\",\n                               \"moment::to\",\n                               \"moment::createAdder\",\n                               \"moment::from\",\n                               \"moment::getSetOffset\",\n                               \"moment::clone$1\",\n                               \"moment::addSubtract$1\"]\n        },\n        {\n            \"test_name\": \"moment_double_depth\",\n            \"directory\": \"moment\",\n            \"kwargs\": {\"target_function\": \"createDuration\",\n                       \"upstream_depth\": \"2\",\n                       \"downstream_depth\": \"2\"},\n            \"expected_edges\": [[\"moment::(global)\", \"moment::toInt\"],\n                               [\"moment::isDurationValid\", \"moment::hasOwnProp\"],\n                               [\"moment::to\", \"moment::createLocal\"],\n                               [\"moment::cloneWithOffset\", \"moment::createLocal\"],\n                               [\"moment::createDuration\", \"moment::absRound\"],\n                               [\"moment::createLocal\", \"moment::createLocalOrUTC\"],\n                               [\"moment::isDurationValid\", \"moment::toInt\"],\n                               [\"moment::clone$1\", \"moment::createDuration\"],\n                               [\"moment::momentsDifference\", \"moment::cloneWithOffset\"],\n                               [\"moment::subtract$1\", \"moment::addSubtract$1\"],\n                               [\"moment::momentsDifference\",\n                                \"moment::positiveMomentsDifference\"],\n                               [\"moment::normalizeObjectUnits\", \"moment::hasOwnProp\"],\n                               [\"moment::createDuration\", \"moment::createLocal\"],\n                               [\"moment::from\", \"moment::createDuration\"],\n                               [\"moment::add$1\", \"moment::addSubtract$1\"],\n                               [\"moment::Duration\", \"moment::isDurationValid\"],\n                               [\"moment::relativeTime$1\", \"moment::createDuration\"],\n                               [\"moment::createDuration\", \"moment::Duration\"],\n                               [\"moment::getSetOffset\", \"moment::createDuration\"],\n                               [\"moment::humanize\", \"moment::relativeTime$1\"],\n                               [\"moment::Duration\", \"moment::getLocale\"],\n                               [\"moment::addSubtract$1\", \"moment::createDuration\"],\n                               [\"moment::to\", \"moment::createDuration\"],\n                               [\"moment::Duration\", \"moment::normalizeObjectUnits\"],\n                               [\"moment::toInt\", \"moment::absFloor\"],\n                               [\"moment::createDuration\", \"moment::hasOwnProp\"],\n                               [\"moment::from\", \"moment::createLocal\"],\n                               [\"moment::createAdder\", \"moment::createDuration\"],\n                               [\"moment::(global)\", \"moment::hasOwnProp\"],\n                               [\"moment::createDuration\", \"moment::isDuration\"],\n                               [\"moment::(global)\", \"moment::createAdder\"],\n                               [\"moment::createDuration\", \"moment::momentsDifference\"],\n                               [\"moment::createDuration\", \"moment::toInt\"],\n                               [\"moment::createDuration\", \"moment::isNumber\"],\n                               [\"moment::createDuration\", \"moment::parseIso\"],\n                               [\"moment::createInvalid$1\", \"moment::createDuration\"]],\n            \"expected_nodes\": [\"moment::normalizeObjectUnits\",\n                               \"moment::cloneWithOffset\",\n                               \"moment::createInvalid$1\",\n                               \"moment::to\",\n                               \"moment::isDuration\",\n                               \"moment::hasOwnProp\",\n                               \"moment::isNumber\",\n                               \"moment::momentsDifference\",\n                               \"moment::createLocalOrUTC\",\n                               \"moment::(global)\",\n                               \"moment::absFloor\",\n                               \"moment::relativeTime$1\",\n                               \"moment::Duration\",\n                               \"moment::from\",\n                               \"moment::absRound\",\n                               \"moment::addSubtract$1\",\n                               \"moment::getSetOffset\",\n                               \"moment::isDurationValid\",\n                               \"moment::positiveMomentsDifference\",\n                               \"moment::createDuration\",\n                               \"moment::getLocale\",\n                               \"moment::parseIso\",\n                               \"moment::add$1\",\n                               \"moment::createAdder\",\n                               \"moment::clone$1\",\n                               \"moment::createLocal\",\n                               \"moment::toInt\",\n                               \"moment::subtract$1\",\n                               \"moment::humanize\"]\n        }\n    ],\n    'mjs': [\n        {\n            \"test_name\": \"two_file_imports_es6\",\n            \"directory\": \"two_file_imports_es6\",\n            \"comment\": \"mjs files should be detected and this separately tests es6 imports\",\n            \"kwargs\": {'source_type': 'module'},\n            \"expected_edges\": [[\"imported_es6::myClass.(constructor)\",\n                                \"imported_es6::myClass.doit\"],\n                               [\"importer_es6::outer\", \"imported_es6::inner\"],\n                               [\"imported_es6::myClass.doit\",\n                                \"imported_es6::myClass.doit2\"],\n                               [\"importer_es6::outer\",\n                                \"imported_es6::myClass.(constructor)\"],\n                               [\"importer_es6::(global)\", \"importer_es6::outer\"]],\n            \"expected_nodes\": [\"imported_es6::myClass.doit\",\n                               \"imported_es6::inner\",\n                               \"imported_es6::myClass.doit2\",\n                               \"imported_es6::myClass.(constructor)\",\n                               \"importer_es6::(global)\",\n                               \"importer_es6::outer\"]\n        },\n    ],\n    'rb': [\n        {\n            \"test_name\": \"simple_a\",\n            \"directory\": \"simple_a\",\n            \"expected_edges\": [[\"simple_a::func_a\", \"simple_a::func_b\"],\n                               [\"simple_a::(global)\", \"simple_a::func_a\"]],\n            \"expected_nodes\": [\"simple_a::(global)\",\n                               \"simple_a::func_a\",\n                               \"simple_a::func_b\"]\n        },\n        {\n            \"test_name\": \"simple_b\",\n            \"directory\": \"simple_b\",\n            \"expected_edges\": [[\"simple_b::b\", \"simple_b::a\"],\n                               [\"simple_b::a\", \"simple_b::b\"],\n                               [\"simple_b::Cls.d\", \"simple_b::a\"],\n                               [\"simple_b::(global)\", \"simple_b::Cls.initialize\"],\n                               [\"simple_b::(global)\", \"simple_b::Cls.d\"]],\n            \"expected_nodes\": [\"simple_b::(global)\",\n                               \"simple_b::a\",\n                               \"simple_b::b\",\n                               \"simple_b::Cls.d\",\n                               \"simple_b::Cls.initialize\"]\n        },\n        {\n            \"test_name\": \"two_file_simple\",\n            \"directory\": \"two_file_simple\",\n            \"expected_edges\": [[\"file_a::(global)\", \"file_a::abra\"],\n                               [\"file_a::abra\", \"file_b::babra\"]],\n            \"expected_nodes\": [\"file_a::(global)\", \"file_a::abra\", \"file_b::babra\"]\n        },\n        {\n            \"test_name\": \"resolve_correct_class\",\n            \"directory\": \"resolve_correct_class\",\n            \"expected_edges\": [[\"rcc::Beta.func_1\", \"rcc::Alpha.func_2\"],\n                               [\"rcc::Alpha.func_1\", \"rcc::Beta.func_2\"],\n                               [\"rcc::Alpha.func_1\", \"rcc::Alpha.func_1\"]],\n            \"expected_nodes\": [\"rcc::Beta.func_1\",\n                               \"rcc::Alpha.func_1\",\n                               \"rcc::Alpha.func_2\",\n                               \"rcc::Beta.func_2\"]\n        },\n        {\n            \"test_name\": \"ambiguous_resolution\",\n            \"directory\": \"ambiguous_resolution\",\n            \"expected_edges\": [[\"ambiguous_resolution::Cadabra.cadabra_it\",\n                                \"ambiguous_resolution::Abra.abra_it\"],\n                               [\"ambiguous_resolution::(global)\",\n                                \"ambiguous_resolution::main\"],\n                               [\"ambiguous_resolution::main\",\n                                \"ambiguous_resolution::Cadabra.cadabra_it\"]],\n            \"expected_nodes\": [\"ambiguous_resolution::Abra.abra_it\",\n                               \"ambiguous_resolution::main\",\n                               \"ambiguous_resolution::(global)\",\n                               \"ambiguous_resolution::Cadabra.cadabra_it\"]\n\n        },\n        {\n            \"test_name\": \"instance_methods\",\n            \"directory\": \"instance_methods\",\n            \"expected_edges\": [[\"instance_methods::Abra.nested2\",\n                                \"instance_methods::Abra.main\"],\n                               [\"instance_methods::(global)\",\n                                \"instance_methods::Abra.nested2\"],\n                               [\"instance_methods::Abra.nested\",\n                                \"instance_methods::Abra.main\"],\n                               [\"instance_methods::(global)\",\n                                \"instance_methods::Abra.nested\"],\n                               [\"instance_methods::(global)\",\n                                \"instance_methods::Abra.main2\"],\n                               [\"instance_methods::Abra.main\",\n                                \"instance_methods::Abra.nested\"],\n                               [\"instance_methods::(global)\",\n                                \"instance_methods::Abra.main\"]],\n            \"expected_nodes\": [\"instance_methods::Abra.nested2\",\n                               \"instance_methods::Abra.main2\",\n                               \"instance_methods::Abra.nested\",\n                               \"instance_methods::Abra.main\",\n                               \"instance_methods::(global)\"]\n\n        },\n        {\n            \"test_name\": \"chains\",\n            \"directory\": \"chains\",\n            \"expected_edges\": [[\"chains::a\", \"chains::b\"],\n                               [\"chains::(global)\", \"chains::c\"],\n                               [\"chains::b\", \"chains::a\"],\n                               [\"chains::c\", \"chains::Cls.initialize\"],\n                               [\"chains::c\", \"chains::Cls.b\"],\n                               [\"chains::(global)\", \"chains::b\"],\n                               [\"chains::(global)\", \"chains::a\"],\n                               [\"chains::c\", \"chains::Cls.a\"]],\n            \"expected_nodes\": [\"chains::a\",\n                               \"chains::Cls.initialize\",\n                               \"chains::Cls.a\",\n                               \"chains::c\",\n                               \"chains::(global)\",\n                               \"chains::Cls.b\",\n                               \"chains::b\"]\n\n        },\n        {\n            \"test_name\": \"modules\",\n            \"directory\": \"modules\",\n            \"expected_edges\": [[\"modules::(global)\", \"modules::ScaleDemo.initialize\"],\n                               [\"modules::(global)\", \"modules::majorNum\"],\n                               [\"modules::ScaleDemoLimited.initialize\",\n                                \"modules::MajorScales.majorNum\"],\n                               [\"modules::ScaleDemo.initialize\",\n                                \"modules::MajorScales.majorNum\"],\n                               [\"modules::ScaleDemo.initialize\",\n                                \"modules::PentatonicScales.pentaNum\"]],\n            \"expected_nodes\": [\"modules::PentatonicScales.pentaNum\",\n                               \"modules::ScaleDemo.initialize\",\n                               \"modules::(global)\",\n                               \"modules::MajorScales.majorNum\",\n                               \"modules::ScaleDemoLimited.initialize\",\n                               \"modules::majorNum\"]\n        },\n        {\n            \"test_name\": \"doublecolon\",\n            \"directory\": \"doublecolon\",\n            \"expected_edges\": [[\"doublecolon::Class1.func_a\",\n                                \"doublecolon::Class2.func_b\"],\n                               [\"doublecolon::Class1.func_b\",\n                                \"doublecolon::Class1.func_a\"],\n                               [\"doublecolon::Class2.func_b\",\n                                \"doublecolon::Class2.func_a\"],\n                               [\"doublecolon::Class2.func_a\",\n                                \"doublecolon::Class1.func_b\"],\n                               [\"doublecolon::(global)\", \"doublecolon::Class2.func_b\"]],\n            \"expected_nodes\": [\"doublecolon::Class2.func_a\",\n                               \"doublecolon::(global)\",\n                               \"doublecolon::Class1.func_b\",\n                               \"doublecolon::Class1.func_a\",\n                               \"doublecolon::Class2.func_b\"]\n        },\n        {\n            \"test_name\": \"weird_chains\",\n            \"directory\": \"weird_chains\",\n            \"expected_edges\": [[\"weird_chains::(global)\", \"weird_chains::DivByTwo.result\"],\n                               [\"weird_chains::(global)\", \"weird_chains::DivByTwo.-\"],\n                               [\"weird_chains::(global)\",\n                                \"weird_chains::DivByTwo.initialize\"],\n                               [\"weird_chains::(global)\", \"weird_chains::DivByTwo.*\"],\n                               [\"weird_chains::(global)\", \"weird_chains::DivByTwo.+\"]],\n            \"expected_nodes\": [\"weird_chains::DivByTwo.+\",\n                               \"weird_chains::DivByTwo.*\",\n                               \"weird_chains::DivByTwo.-\",\n                               \"weird_chains::DivByTwo.result\",\n                               \"weird_chains::DivByTwo.initialize\",\n                               \"weird_chains::(global)\"]\n        },\n        {\n            \"test_name\": \"onelinefile\",\n            \"directory\": \"onelinefile\",\n            \"comment\": \"Including this because the ruby ast treats single line stuff different from multiline. Also there is a lambda\",\n            \"expected_edges\": [],\n            \"expected_nodes\": []\n        },\n        {\n            \"test_name\": \"nested\",\n            \"directory\": \"nested\",\n            \"expected_edges\": [[\"nested::Nested.initialize\", \"nested::Nested.func_1\"],\n                               [\"nested::(global)\", \"nested::Nested.func_2\"],\n                               [\"nested::(global)\", \"nested::Mod.func_1\"],\n                               [\"nested::Nested.func_2\", \"nested::Mod.func_2\"],\n                               [\"nested::func_1\", \"nested::func_2\"],\n                               [\"nested::(global)\", \"nested::Nested.initialize\"],\n                               [\"nested::Nested.func_2\", \"nested::Nested.func_1\"],\n                               [\"nested::func_2\", \"nested::func_1\"],\n                               [\"nested::Nested.func_1\", \"nested::Mod.func_1\"]],\n            \"expected_nodes\": [\"nested::Mod.func_1\",\n                               \"nested::func_2\",\n                               \"nested::Nested.func_1\",\n                               \"nested::func_1\",\n                               \"nested::Nested.initialize\",\n                               \"nested::(global)\",\n                               \"nested::Mod.func_2\",\n                               \"nested::Nested.func_2\"]\n        },\n        {\n            \"test_name\": \"nested_classes\",\n            \"directory\": \"nested_classes\",\n            \"comment\": \"like the last nested test but with classes instead of modules\",\n            \"expected_edges\": [[\"nested_classes::(global)\", \"nested_classes::Mod.func_1\"],\n                               [\"nested_classes::Nested.func_1\",\n                                \"nested_classes::Mod.func_1\"],\n                               [\"nested_classes::Nested.func_2\",\n                                \"nested_classes::Mod.func_2\"],\n                               [\"nested_classes::(global)\",\n                                \"nested_classes::Nested.initialize\"],\n                               [\"nested_classes::func_1\", \"nested_classes::func_2\"],\n                               [\"nested_classes::Nested.func_2\",\n                                \"nested_classes::Nested.func_1\"],\n                               [\"nested_classes::func_2\", \"nested_classes::func_1\"],\n                               [\"nested_classes::Nested.initialize\",\n                                \"nested_classes::Nested.func_1\"],\n                               [\"nested_classes::(global)\",\n                                \"nested_classes::Nested.func_2\"]],\n            \"expected_nodes\": [\"nested_classes::Mod.func_1\",\n                               \"nested_classes::func_1\",\n                               \"nested_classes::func_2\",\n                               \"nested_classes::Nested.func_2\",\n                               \"nested_classes::Nested.initialize\",\n                               \"nested_classes::Mod.func_2\",\n                               \"nested_classes::(global)\",\n                               \"nested_classes::Nested.func_1\"]\n        },\n        {\n            \"test_name\": \"inheritance_2\",\n            \"directory\": \"inheritance_2\",\n            \"comment\": \"The '<' style of inheritance\",\n            \"expected_edges\": [[\"inheritance_2::Cat.meow\", \"inheritance_2::Animal.speak\"],\n                               [\"inheritance_2::(global)\", \"inheritance_2::Animal.speak\"],\n                               [\"inheritance_2::(global)\", \"inheritance_2::Cat.meow\"]],\n            \"expected_nodes\": [\"inheritance_2::Cat.meow\",\n                               \"inheritance_2::Animal.speak\",\n                               \"inheritance_2::(global)\"]\n        },\n        {\n            \"test_name\": \"split_modules\",\n            \"directory\": \"split_modules\",\n            \"expected_edges\": [[\"split_modules_a::(global)\",\n                                \"split_modules_b::MyClass.doit\"],\n                               [\"split_modules_b::MyClass.doit\",\n                                \"split_modules_a::Split.say_hi\"],\n                               [\"split_modules_a::(global)\",\n                                \"split_modules_b::MyClass.initialize\"],\n                               [\"split_modules_a::Split.say_hi\",\n                                \"split_modules_b::Split.say_bye\"]],\n            \"expected_nodes\": [\"split_modules_b::MyClass.doit\",\n                               \"split_modules_b::Split.say_bye\",\n                               \"split_modules_b::MyClass.initialize\",\n                               \"split_modules_a::(global)\",\n                               \"split_modules_a::Split.say_hi\"]\n        },\n        {\n            \"test_name\": \"public_suffix\",\n            \"directory\": \"public_suffix\",\n            \"expected_edges\": [[\"domain::Domain.to_s\", \"domain::Domain.name\"],\n                               [\"list::List.select\", \"list::List.size\"],\n                               [\"public_suffix::PublicSuffix.normalize\",\n                                \"list::List.empty?\"],\n                               [\"domain::Domain.subdomain\", \"domain::Domain.subdomain?\"],\n                               [\"rule::Exception.decompose\", \"rule::Exception.parts\"],\n                               [\"domain::Domain.name_to_labels\", \"domain::Domain.to_s\"],\n                               [\"list::List.select\", \"domain::Domain.to_s\"],\n                               [\"public_suffix::PublicSuffix.decompose\",\n                                \"domain::Domain.initialize\"],\n                               [\"rule::Normal.decompose\", \"domain::Domain.to_s\"],\n                               [\"list::List.clear\", \"list::List.clear\"],\n                               [\"list::List.empty?\", \"list::List.empty?\"],\n                               [\"list::List.select\", \"list::List.entry_to_rule\"],\n                               [\"rule::Base.initialize\", \"domain::Domain.to_s\"],\n                               [\"list::List.each\", \"list::List.entry_to_rule\"],\n                               [\"domain::Domain.domain\", \"domain::Domain.domain?\"],\n                               [\"list::List.find\", \"list::List.select\"],\n                               [\"public_suffix::PublicSuffix.parse\",\n                                \"public_suffix::PublicSuffix.decompose\"],\n                               [\"public_suffix::PublicSuffix.normalize\",\n                                \"domain::Domain.to_s\"],\n                               [\"public_suffix::PublicSuffix.valid?\",\n                                \"public_suffix::PublicSuffix.normalize\"],\n                               [\"rule::Wildcard.decompose\", \"domain::Domain.to_s\"],\n                               [\"rule::Rule.default\", \"rule::Rule.factory\"],\n                               [\"list::List.size\", \"list::List.size\"],\n                               [\"rule::Wildcard.decompose\", \"rule::Wildcard.parts\"],\n                               [\"rule::Base.match?\", \"list::List.empty?\"],\n                               [\"list::List.parse\", \"list::List.add\"],\n                               [\"public_suffix::PublicSuffix.decompose\",\n                                \"list::List.empty?\"],\n                               [\"public_suffix::PublicSuffix.valid?\", \"list::List.find\"],\n                               [\"public_suffix::PublicSuffix.domain\",\n                                \"public_suffix::PublicSuffix.parse\"],\n                               [\"rule::Exception.build\", \"domain::Domain.to_s\"],\n                               [\"list::List.each\", \"list::List.each\"],\n                               [\"public_suffix::PublicSuffix.parse\",\n                                \"public_suffix::PublicSuffix.normalize\"],\n                               [\"rule::Normal.decompose\", \"rule::Normal.parts\"],\n                               [\"rule::Exception.decompose\", \"domain::Domain.to_s\"],\n                               [\"rule::Wildcard.build\", \"domain::Domain.to_s\"],\n                               [\"list::List.parse\", \"list::List.empty?\"],\n                               [\"list::List.parse\", \"rule::Rule.factory\"],\n                               [\"public_suffix::PublicSuffix.parse\", \"list::List.find\"],\n                               [\"list::List.default\", \"list::List.parse\"],\n                               [\"rule::Rule.factory\", \"domain::Domain.to_s\"],\n                               [\"list::List.add\", \"list::List.rule_to_entry\"]],\n            \"expected_nodes\": [\"rule::Base.initialize\",\n                               \"list::List.empty?\",\n                               \"domain::Domain.initialize\",\n                               \"domain::Domain.name\",\n                               \"rule::Normal.parts\",\n                               \"list::List.clear\",\n                               \"list::List.select\",\n                               \"list::List.default\",\n                               \"rule::Wildcard.decompose\",\n                               \"list::List.rule_to_entry\",\n                               \"rule::Normal.decompose\",\n                               \"public_suffix::PublicSuffix.domain\",\n                               \"list::List.parse\",\n                               \"list::List.add\",\n                               \"domain::Domain.name_to_labels\",\n                               \"list::List.entry_to_rule\",\n                               \"list::List.each\",\n                               \"rule::Wildcard.parts\",\n                               \"public_suffix::PublicSuffix.valid?\",\n                               \"rule::Base.match?\",\n                               \"rule::Exception.decompose\",\n                               \"rule::Rule.default\",\n                               \"domain::Domain.domain\",\n                               \"rule::Exception.build\",\n                               \"domain::Domain.subdomain\",\n                               \"public_suffix::PublicSuffix.parse\",\n                               \"list::List.find\",\n                               \"list::List.size\",\n                               \"domain::Domain.to_s\",\n                               \"domain::Domain.subdomain?\",\n                               \"domain::Domain.domain?\",\n                               \"rule::Rule.factory\",\n                               \"public_suffix::PublicSuffix.decompose\",\n                               \"rule::Exception.parts\",\n                               \"rule::Wildcard.build\",\n                               \"public_suffix::PublicSuffix.normalize\"]\n        }\n    ],\n    'php': [\n        {\n            \"test_name\": \"simple_a\",\n            \"directory\": \"simple_a\",\n            \"expected_edges\": [[\"simple_a::func_a\", \"simple_a::func_b\"],\n                               [\"simple_a::(global)\", \"simple_a::func_a\"]],\n            \"expected_nodes\": [\"simple_a::func_a\",\n                               \"simple_a::func_b\",\n                               \"simple_a::(global)\"]\n        },\n        {\n            \"test_name\": \"simple_b\",\n            \"directory\": \"simple_b\",\n            \"expected_edges\": [[\"simple_b::(global)\", \"simple_b::C.d\"],\n                               [\"simple_b::a\", \"simple_b::b\"],\n                               [\"simple_b::C.d\", \"simple_b::a\"],\n                               [\"simple_b::b\", \"simple_b::a\"]],\n            \"expected_nodes\": [\"simple_b::b\",\n                               \"simple_b::(global)\",\n                               \"simple_b::a\",\n                               \"simple_b::C.d\"]\n        },\n        {\n            \"test_name\": \"two_file_simple\",\n            \"directory\": \"two_file_simple\",\n            \"expected_edges\": [[\"file_a::(global)\", \"file_a::a\"],\n                               [\"file_a::a\", \"file_b::b\"]],\n            \"expected_nodes\": [\"file_a::a\", \"file_b::b\", \"file_a::(global)\"]\n        },\n        {\n            \"test_name\": \"resolve_correct_class\",\n            \"directory\": \"resolve_correct_class\",\n            \"expected_edges\": [[\"rcc::Alpha.func_1\", \"rcc::Alpha.func_1\"],\n                               [\"rcc::Alpha.func_1\", \"rcc::Beta.func_2\"],\n                               [\"rcc::Beta.func_1\", \"rcc::Alpha.func_2\"]],\n            \"expected_nodes\": [\"rcc::Beta.func_1\",\n                               \"rcc::Beta.func_2\",\n                               \"rcc::Alpha.func_2\",\n                               \"rcc::Alpha.func_1\"]\n        },\n        {\n            \"test_name\": \"ambiguous_resolution\",\n            \"directory\": \"ambiguous_resolution\",\n            \"expected_edges\": [[\"ambiguous_resolution::main\",\n                                \"ambiguous_resolution::Cadabra.cadabra_it\"],\n                               [\"ambiguous_resolution::Cadabra.cadabra_it\",\n                                \"ambiguous_resolution::Abra.abra_it\"],\n                               [\"ambiguous_resolution::(global)\",\n                                \"ambiguous_resolution::main\"]],\n            \"expected_nodes\": [\"ambiguous_resolution::main\",\n                               \"ambiguous_resolution::(global)\",\n                               \"ambiguous_resolution::Abra.abra_it\",\n                               \"ambiguous_resolution::Cadabra.cadabra_it\"]\n        },\n        {\n            \"test_name\": \"chains\",\n            \"directory\": \"chains\",\n            \"expected_edges\": [[\"chains::c\", \"chains::Cls.__construct\"],\n                               [\"chains::a\", \"chains::b\"],\n                               [\"chains::b\", \"chains::a\"],\n                               [\"chains::c\", \"chains::Cls.a\"],\n                               [\"chains::c\", \"chains::Cls.b\"],\n                               [\"chains::(global)\", \"chains::c\"],\n                               [\"chains::(global)\", \"chains::b\"],\n                               [\"chains::(global)\", \"chains::a\"]],\n            \"expected_nodes\": [\"chains::c\",\n                               \"chains::Cls.b\",\n                               \"chains::(global)\",\n                               \"chains::a\",\n                               \"chains::Cls.__construct\",\n                               \"chains::b\",\n                               \"chains::Cls.a\"]\n        },\n        {\n            \"test_name\": \"publicprivateprotected\",\n            \"directory\": \"publicprivateprotected\",\n            \"comment\": \"Just ensuring that access modifiers don't confuse code2flow\",\n            \"expected_edges\": [[\"publicprivateprotected::set_color_weight\",\n                                \"publicprivateprotected::Fruit.set_weight\"],\n                               [\"publicprivateprotected::(global)\",\n                                \"publicprivateprotected::set_color_weight\"],\n                               [\"publicprivateprotected::(global)\",\n                                \"publicprivateprotected::Fruit.set_name\"],\n                               [\"publicprivateprotected::set_color_weight\",\n                                \"publicprivateprotected::Fruit.set_color\"]],\n            \"expected_nodes\": [\"publicprivateprotected::Fruit.set_color\",\n                               \"publicprivateprotected::set_color_weight\",\n                               \"publicprivateprotected::Fruit.set_weight\",\n                               \"publicprivateprotected::Fruit.set_name\",\n                               \"publicprivateprotected::(global)\"]\n        },\n        {\n            \"test_name\": \"inheritance\",\n            \"directory\": \"inheritance\",\n            \"expected_edges\": [[\"inheritance::Strawberry.message\",\n                                \"inheritance::Fruit.getColor\"],\n                               [\"inheritance::(global)\",\n                                \"inheritance::Strawberry.message\"],\n                               [\"inheritance::(global)\", \"inheritance::Fruit.intro\"]],\n            \"expected_nodes\": [\"inheritance::Fruit.intro\",\n                               \"inheritance::(global)\",\n                               \"inheritance::Fruit.getColor\",\n                               \"inheritance::Strawberry.message\"]\n        },\n        {\n            \"test_name\": \"inheritance2\",\n            \"directory\": \"inheritance2\",\n            \"expected_edges\": [[\"inheritance2::(global)\", \"inheritance2::Audi.intro\"],\n                               [\"inheritance2::(global)\", \"inheritance2::Volvo.intro\"],\n                               [\"inheritance2::(global)\", \"inheritance2::Audi.makeSound\"],\n                               [\"inheritance2::(global)\", \"inheritance2::Citroen.intro\"]],\n            \"expected_nodes\": [\"inheritance2::Audi.makeSound\",\n                               \"inheritance2::Citroen.intro\",\n                               \"inheritance2::Audi.intro\",\n                               \"inheritance2::Volvo.intro\",\n                               \"inheritance2::(global)\"]\n        },\n        {\n            \"test_name\": \"traits\",\n            \"directory\": \"traits\",\n            \"expected_edges\": [[\"traits::welcome2\", \"traits::message1.msg1\"],\n                               [\"traits::welcome1\", \"traits::Welcome.__construct\"],\n                               [\"traits::welcome2\", \"traits::message2.msg2\"],\n                               [\"traits::welcome1\", \"traits::message1.msg1\"]],\n            \"expected_nodes\": [\"traits::message1.msg1\",\n                               \"traits::Welcome.__construct\",\n                               \"traits::message2.msg2\",\n                               \"traits::welcome2\",\n                               \"traits::welcome1\"]\n        },\n        {\n            \"test_name\": \"static\",\n            \"directory\": \"static\",\n            \"expected_edges\": [[\"static::(global)\", \"static::Greeting.__construct\"],\n                               [\"static::(global)\", \"static::Greeting.welcome\"],\n                               [\"static::Greeting.__construct\",\n                                \"static::Greeting.say_name\"],\n                               [\"static::Greeting.__construct\",\n                                \"static::Greeting.welcome\"]],\n            \"expected_nodes\": [\"static::Greeting.__construct\",\n                               \"static::(global)\",\n                               \"static::Greeting.welcome\",\n                               \"static::Greeting.say_name\"]\n        },\n        {\n            \"test_name\": \"anon\",\n            \"directory\": \"anon\",\n            \"comment\": \"skip closure methods\",\n            \"expected_edges\": [[\"anonymous_function::(global)\", \"anonymous_function::a\"]],\n            \"expected_nodes\": [\"anonymous_function::(global)\", \"anonymous_function::a\"]\n        },\n        {\n            \"test_name\": \"anon2\",\n            \"directory\": \"anon2\",\n            \"expected_edges\": [[\"anonymous_function2::(Closure)\",\n                                \"anonymous_function2::func_a\"]],\n            \"expected_nodes\": [\"anonymous_function2::(Closure)\",\n                               \"anonymous_function2::func_a\"]\n        },\n        {\n            \"test_name\": \"branch\",\n            \"directory\": \"branch\",\n            \"comment\": \"just a simple sub-block\",\n            \"expected_edges\": [[\"branch::(global)\", \"branch::a\"]],\n            \"expected_nodes\": [\"branch::a\", \"branch::(global)\"]\n        },\n        {\n            \"test_name\": \"factory\",\n            \"directory\": \"factory\",\n            \"expected_edges\": [[\"factory::(Closure)\", \"currency::Currency.__construct\"],\n                               [\"factory::(Closure)\", \"currency::Currency.getCode\"],\n                               [\"factory::(Closure)\", \"currency::Money.contains\"]],\n            \"expected_nodes\": [\"currency::Currency.getCode\",\n                               \"currency::Money.contains\",\n                               \"factory::(Closure)\",\n                               \"currency::Currency.__construct\"]\n        },\n        {\n            \"test_name\": \"nested\",\n            \"directory\": \"nested\",\n            \"expected_edges\": [[\"nested::(global)\", \"nested::outer\"],\n                               [\"nested::(global)\", \"nested::inner\"]],\n            \"expected_nodes\": [\"nested::outer\", \"nested::inner\", \"nested::(global)\"]\n        },\n        {\n            \"test_name\": \"namespace_a\",\n            \"directory\": \"namespace_a\",\n            \"expected_edges\": [[\"namespace_a::NS.(global)\",\n                                \"namespace_a::Namespaced_cls.__construct\"],\n                               [\"namespace_a::NS.(global)\",\n                                \"namespace_a::NS.namespaced_func\"],\n                               [\"namespace_a::NS.(global)\",\n                                \"namespace_a::Namespaced_cls.instance_method\"]],\n            \"expected_nodes\": [\"namespace_a::NS.namespaced_func\",\n                               \"namespace_a::Namespaced_cls.__construct\",\n                               \"namespace_a::Namespaced_cls.instance_method\",\n                               \"namespace_a::NS.(global)\"]\n        },\n        {\n            \"test_name\": \"namespace_b\",\n            \"directory\": \"namespace_b\",\n            \"expected_edges\": [[\"namespace_b1::(global)\", \"namespace_b2::Cat.says\"],\n                               [\"namespace_b1::(global)\", \"namespace_b2::Animal.meows\"],\n                               [\"namespace_b1::(global)\", \"namespace_b2::Cat.meows\"]],\n            \"expected_nodes\": [\"namespace_b2::Cat.says\",\n                               \"namespace_b1::(global)\",\n                               \"namespace_b2::Animal.meows\",\n                               \"namespace_b2::Cat.meows\"]\n        },\n        {\n            \"test_name\": \"namespace_c\",\n            \"directory\": \"namespace_c\",\n            \"expected_edges\": [[\"namespace_c1::(global)\",\n                                \"namespace_c2::Outer.Inner.speak\"],\n                               [\"namespace_c1::(global)\", \"namespace_c2::Cat.meow\"]],\n            \"expected_nodes\": [\"namespace_c2::Outer.Inner.speak\",\n                               \"namespace_c2::Cat.meow\",\n                               \"namespace_c1::(global)\"]\n        },\n        {\n            \"test_name\": \"nested_calls\",\n            \"directory\": \"nested_calls\",\n            \"test_name\": \"nested_calls\",\n            \"directory\": \"nested_calls\",\n            \"expected_edges\": [[\"nested_calls::(global)\", \"nested_calls::Cls.func\"],\n                               [\"nested_calls::x_\", \"nested_calls::Cls.func2\"],\n                               [\"nested_calls::Cls.func\", \"nested_calls::Cls.a\"],\n                               [\"nested_calls::Cls.func2\", \"nested_calls::z_\"],\n                               [\"nested_calls::Cls.func2\", \"nested_calls::y_\"],\n                               [\"nested_calls::Cls.func2\", \"nested_calls::x_\"],\n                               [\"nested_calls::Cls.func\", \"nested_calls::Cls.c\"],\n                               [\"nested_calls::Cls.func\", \"nested_calls::Cls.b\"],\n                               [\"nested_calls::(global)\", \"nested_calls::func2\"]],\n            \"expected_nodes\": [\"nested_calls::z_\",\n                               \"nested_calls::Cls.a\",\n                               \"nested_calls::x_\",\n                               \"nested_calls::Cls.c\",\n                               \"nested_calls::Cls.func2\",\n                               \"nested_calls::func2\",\n                               \"nested_calls::Cls.b\",\n                               \"nested_calls::(global)\",\n                               \"nested_calls::Cls.func\",\n                               \"nested_calls::y_\"]\n        },\n        {\n\n            \"test_name\": \"weird_assign\",\n            \"directory\": \"weird_assign\",\n            \"comment\": \"Not a complicated test but an unusual usecase\",\n            \"expected_edges\": [[\"weird_assign::c\", \"weird_assign::b\"],\n                               [\"weird_assign::c\", \"weird_assign::a\"]],\n            \"expected_nodes\": [\"weird_assign::c\", \"weird_assign::a\", \"weird_assign::b\"]\n        },\n        {\n            \"test_name\": \"money\",\n            \"directory\": \"money\",\n            \"expected_edges\": [[\"GmpCalculator::GmpCalculator.round\",\n                                \"Number::Number.isCurrentEven\"],\n                               [\"BcMathCalculator::BcMathCalculator.round\",\n                                \"Number::Number.isHalf\"],\n                               [\"Money::Money.greaterThan\", \"Money::Money.compare\"],\n                               [\"IntlLocalizedDecimalFormatter::IntlLocalizedDecimalFormatter.format\",\n                                \"Money::Money.getAmount\"],\n                               [\"Number::Number.fromNumber\", \"Number::Number.__construct\"],\n                               [\"IntlLocalizedDecimalParser::IntlLocalizedDecimalParser.parse\",\n                                \"Money::Money.__construct\"],\n                               [\"GmpCalculator::GmpCalculator.divide\",\n                                \"InvalidArgumentException::InvalidArgumentException.divisionByZero\"],\n                               [\"GmpCalculator::GmpCalculator.round\",\n                                \"Number::Number.isInteger\"],\n                               [\"CurrencyPair::CurrencyPair.createFromIso\",\n                                \"Currency::Currency.__construct\"],\n                               [\"BitcoinMoneyFormatter::BitcoinMoneyFormatter.format\",\n                                \"Number::Number.roundMoneyValue\"],\n                               [\"GmpCalculator::GmpCalculator.divide\",\n                                \"Number::Number.getFractionalPart\"],\n                               [\"IndirectExchange::IndirectExchange.getConversions\",\n                                \"UnresolvableCurrencyPairException::UnresolvableCurrencyPairException.createFromCurrencies\"],\n                               [\"GmpCalculator::GmpCalculator.round\",\n                                \"Number::Number.fromString\"],\n                               [\"BitcoinMoneyFormatter::BitcoinMoneyFormatter.format\",\n                                \"Money::Money.getCurrency\"],\n                               [\"DecimalMoneyParser::DecimalMoneyParser.parse\",\n                                \"Money::Money.__construct\"],\n                               [\"BcMathCalculator::BcMathCalculator.round\",\n                                \"Number::Number.getIntegerRoundingMultiplier\"],\n                               [\"GmpCalculator::GmpCalculator.compare\",\n                                \"Number::Number.fromString\"],\n                               [\"ReversedCurrenciesExchange::ReversedCurrenciesExchange.quote\",\n                                \"CurrencyPair::CurrencyPair.getConversionRatio\"],\n                               [\"Converter::Converter.convertAgainstCurrencyPair\",\n                                \"Money::Money.getCurrency\"],\n                               [\"BitcoinMoneyFormatter::BitcoinMoneyFormatter.format\",\n                                \"Currency::Currency.getCode\"],\n                               [\"FixedExchange::FixedExchange.quote\",\n                                \"UnresolvableCurrencyPairException::UnresolvableCurrencyPairException.createFromCurrencies\"],\n                               [\"Money::Money.allocate\", \"Money::Money.__construct\"],\n                               [\"IntlMoneyParser::IntlMoneyParser.parse\",\n                                \"Number::Number.roundMoneyValue\"],\n                               [\"CurrencyList::CurrencyList.contains\",\n                                \"Currency::Currency.getCode\"],\n                               [\"Money::Money.min\", \"Money::Money.lessThan\"],\n                               [\"Number::Number.fromNumber\", \"Number::Number.fromString\"],\n                               [\"Converter::Converter.convertAgainstCurrencyPair\",\n                                \"Currency::Currency.getCode\"],\n                               [\"Money::Money.absolute\", \"Money::Money.__construct\"],\n                               [\"GmpCalculator::GmpCalculator.share\",\n                                \"GmpCalculator::GmpCalculator.multiply\"],\n                               [\"ISOCurrencies::ISOCurrencies.subunitFor\",\n                                \"ISOCurrencies::ISOCurrencies.contains\"],\n                               [\"GmpCalculator::GmpCalculator.divide\",\n                                \"Number::Number.isDecimal\"],\n                               [\"SwapExchange::SwapExchange.quote\",\n                                \"CurrencyPair::CurrencyPair.__construct\"],\n                               [\"GmpCalculator::GmpCalculator.roundDigit\",\n                                \"Number::Number.getIntegerPart\"],\n                               [\"IntlMoneyFormatter::IntlMoneyFormatter.format\",\n                                \"Money::Money.getCurrency\"],\n                               [\"ISOCurrencies::ISOCurrencies.subunitFor\",\n                                \"Currency::Currency.getCode\"],\n                               [\"GmpCalculator::GmpCalculator.floor\",\n                                \"Number::Number.getIntegerPart\"],\n                               [\"BcMathCalculator::BcMathCalculator.floor\",\n                                \"Number::Number.isInteger\"],\n                               [\"IntlLocalizedDecimalParser::IntlLocalizedDecimalParser.parse\",\n                                \"Number::Number.roundMoneyValue\"],\n                               [\"Money::Money.divide\", \"Money::Money.__construct\"],\n                               [\"Money::Money.max\", \"Money::Money.greaterThan\"],\n                               [\"GmpCalculator::GmpCalculator.share\",\n                                \"GmpCalculator::GmpCalculator.floor\"],\n                               [\"Converter::Converter.convertAgainstCurrencyPair\",\n                                \"CurrencyPair::CurrencyPair.getCounterCurrency\"],\n                               [\"BitcoinMoneyParser::BitcoinMoneyParser.parse\",\n                                \"Currency::Currency.__construct\"],\n                               [\"BitcoinMoneyFormatter::BitcoinMoneyFormatter.format\",\n                                \"Money::Money.getAmount\"],\n                               [\"BcMathCalculator::BcMathCalculator.floor\",\n                                \"Number::Number.fromString\"],\n                               [\"DecimalMoneyParser::DecimalMoneyParser.parse\",\n                                \"Number::Number.roundMoneyValue\"],\n                               [\"Converter::Converter.convertAgainstCurrencyPair\",\n                                \"Number::Number.fromString\"],\n                               [\"Comparator::Comparator.__construct\",\n                                \"IntlMoneyFormatter::IntlMoneyFormatter.__construct\"],\n                               [\"Number::Number.base10\", \"Number::Number.__construct\"],\n                               [\"GmpCalculator::GmpCalculator.round\",\n                                \"Number::Number.getIntegerPart\"],\n                               [\"BitcoinCurrencies::BitcoinCurrencies.contains\",\n                                \"Currency::Currency.getCode\"],\n                               [\"GmpCalculator::GmpCalculator.roundDigit\",\n                                \"Number::Number.getIntegerRoundingMultiplier\"],\n                               [\"GmpCalculator::GmpCalculator.compare\",\n                                \"Number::Number.getIntegerPart\"],\n                               [\"DecimalMoneyFormatter::DecimalMoneyFormatter.format\",\n                                \"Money::Money.getCurrency\"],\n                               [\"ISOCurrencies::ISOCurrencies.subunitFor\",\n                                \"ISOCurrencies::ISOCurrencies.getCurrencies\"],\n                               [\"GmpCalculator::GmpCalculator.roundDigit\",\n                                \"Number::Number.isCloserToNext\"],\n                               [\"GmpCalculator::GmpCalculator.compare\",\n                                \"Number::Number.isDecimal\"],\n                               [\"ISOCurrencies::ISOCurrencies.(Closure)\",\n                                \"Currency::Currency.__construct\"],\n                               [\"GmpCalculator::GmpCalculator.round\",\n                                \"Number::Number.isHalf\"],\n                               [\"ISOCurrencies::ISOCurrencies.contains\",\n                                \"Currency::Currency.getCode\"],\n                               [\"ReversedCurrenciesExchange::ReversedCurrenciesExchange.quote\",\n                                \"Money::Money.getCalculator\"],\n                               [\"GmpCalculator::GmpCalculator.ceil\",\n                                \"Number::Number.isInteger\"],\n                               [\"Number::Number.fromFloat\", \"Number::Number.fromString\"],\n                               [\"Money::Money.roundToUnit\", \"Money::Money.__construct\"],\n                               [\"Money::Money.negative\", \"Money::Money.__construct\"],\n                               [\"GmpCalculator::GmpCalculator.round\",\n                                \"GmpCalculator::GmpCalculator.roundDigit\"],\n                               [\"UnresolvableCurrencyPairException::UnresolvableCurrencyPairException.createFromCurrencies\",\n                                \"Currency::Currency.getCode\"],\n                               [\"Money::Money.multiply\", \"Money::Money.__construct\"],\n                               [\"FixedExchange::FixedExchange.quote\",\n                                \"CurrencyPair::CurrencyPair.__construct\"],\n                               [\"Converter::Converter.convert\",\n                                \"Money::Money.getCurrency\"],\n                               [\"GmpCalculator::GmpCalculator.roundDigit\",\n                                \"GmpCalculator::GmpCalculator.add\"],\n                               [\"IndirectExchange::IndirectExchange.getConversions\",\n                                \"Currency::Currency.getCode\"],\n                               [\"GmpCalculator::GmpCalculator.ceil\",\n                                \"Number::Number.fromString\"],\n                               [\"Money::Money.lessThan\", \"Money::Money.compare\"],\n                               [\"GmpCalculator::GmpCalculator.floor\",\n                                \"GmpCalculator::GmpCalculator.add\"],\n                               [\"Money::Money.allocateTo\", \"Money::Money.allocate\"],\n                               [\"CurrencyList::CurrencyList.(Closure)\",\n                                \"Currency::Currency.__construct\"],\n                               [\"Converter::Converter.convertAndReturnWithCurrencyPair\",\n                                \"Money::Money.getCurrency\"],\n                               [\"GmpCalculator::GmpCalculator.round\",\n                                \"Number::Number.getIntegerRoundingMultiplier\"],\n                               [\"BcMathCalculator::BcMathCalculator.roundDigit\",\n                                \"Number::Number.getIntegerRoundingMultiplier\"],\n                               [\"CurrencyList::CurrencyList.subunitFor\",\n                                \"Currency::Currency.getCode\"],\n                               [\"BcMathCalculator::BcMathCalculator.round\",\n                                \"Number::Number.isInteger\"],\n                               [\"ISOCurrencies::ISOCurrencies.numericCodeFor\",\n                                \"ISOCurrencies::ISOCurrencies.contains\"],\n                               [\"Number::Number.__construct\",\n                                \"Number::Number.parseIntegerPart\"],\n                               [\"ISOCurrencies::ISOCurrencies.numericCodeFor\",\n                                \"Currency::Currency.getCode\"],\n                               [\"BcMathCalculator::BcMathCalculator.roundDigit\",\n                                \"Number::Number.isCloserToNext\"],\n                               [\"BcMathCalculator::BcMathCalculator.share\",\n                                \"BcMathCalculator::BcMathCalculator.floor\"],\n                               [\"GmpCalculator::GmpCalculator.multiply\",\n                                \"Number::Number.fromString\"],\n                               [\"FixedExchange::FixedExchange.quote\",\n                                \"Currency::Currency.getCode\"],\n                               [\"GmpCalculator::GmpCalculator.share\",\n                                \"GmpCalculator::GmpCalculator.divide\"],\n                               [\"BcMathCalculator::BcMathCalculator.mod\",\n                                \"InvalidArgumentException::InvalidArgumentException.moduloByZero\"],\n                               [\"Money::Money.subtract\", \"Money::Money.__construct\"],\n                               [\"IntlMoneyFormatter::IntlMoneyFormatter.format\",\n                                \"Currency::Currency.getCode\"],\n                               [\"Converter::Converter.convertAndReturnWithCurrencyPair\",\n                                \"Converter::Converter.convertAgainstCurrencyPair\"],\n                               [\"GmpCalculator::GmpCalculator.compare\",\n                                \"Number::Number.getFractionalPart\"],\n                               [\"IntlMoneyParser::IntlMoneyParser.parse\",\n                                \"Currency::Currency.__construct\"],\n                               [\"ISOCurrencies::ISOCurrencies.contains\",\n                                \"ISOCurrencies::ISOCurrencies.getCurrencies\"],\n                               [\"IndirectExchange::IndirectExchange.quote\",\n                                \"IndirectExchange::IndirectExchange.getConversions\"],\n                               [\"GmpCalculator::GmpCalculator.divide\",\n                                \"GmpCalculator::GmpCalculator.compare\"],\n                               [\"ISOCurrencies::ISOCurrencies.getIterator\",\n                                \"ISOCurrencies::ISOCurrencies.getCurrencies\"],\n                               [\"Converter::Converter.convertAgainstCurrencyPair\",\n                                \"Money::Money.getAmount\"],\n                               [\"GmpCalculator::GmpCalculator.round\",\n                                \"GmpCalculator::GmpCalculator.add\"],\n                               [\"GmpCalculator::GmpCalculator.mod\",\n                                \"GmpCalculator::GmpCalculator.compare\"],\n                               [\"Money::Money.equals\", \"Money::Money.compare\"],\n                               [\"GmpCalculator::GmpCalculator.divide\",\n                                \"Number::Number.__construct\"],\n                               [\"IndirectExchange::IndirectExchange.(Closure)\",\n                                \"CurrencyPair::CurrencyPair.getConversionRatio\"],\n                               [\"Money::Money.__construct\", \"Number::Number.isInteger\"],\n                               [\"IntlMoneyFormatter::IntlMoneyFormatter.format\",\n                                \"Money::Money.getAmount\"],\n                               [\"Converter::Converter.convertAgainstCurrencyPair\",\n                                \"CurrencyPair::CurrencyPair.getConversionRatio\"],\n                               [\"ISOCurrencies::ISOCurrencies.numericCodeFor\",\n                                \"ISOCurrencies::ISOCurrencies.getCurrencies\"],\n                               [\"CurrencyList::CurrencyList.subunitFor\",\n                                \"CurrencyList::CurrencyList.contains\"],\n                               [\"ExchangerExchange::ExchangerExchange.quote\",\n                                \"UnresolvableCurrencyPairException::UnresolvableCurrencyPairException.createFromCurrencies\"],\n                               [\"Converter::Converter.convertAgainstCurrencyPair\",\n                                \"Number::Number.base10\"],\n                               [\"IntlLocalizedDecimalFormatter::IntlLocalizedDecimalFormatter.format\",\n                                \"Money::Money.getCurrency\"],\n                               [\"Money::Money.__construct\", \"Number::Number.fromString\"],\n                               [\"Number::Number.__construct\",\n                                \"Number::Number.parseFractionalPart\"],\n                               [\"GmpCalculator::GmpCalculator.ceil\",\n                                \"Number::Number.getIntegerPart\"],\n                               [\"Money::Money.add\", \"Money::Money.__construct\"],\n                               [\"GmpCalculator::GmpCalculator.mod\",\n                                \"GmpCalculator::GmpCalculator.absolute\"],\n                               [\"BcMathCalculator::BcMathCalculator.round\",\n                                \"Number::Number.isCurrentEven\"],\n                               [\"GmpCalculator::GmpCalculator.multiply\",\n                                \"Number::Number.getIntegerPart\"],\n                               [\"IndirectExchange::IndirectExchange.getConversions\",\n                                \"IndirectExchangeQueuedItem::IndirectExchangeQueuedItem.__construct\"],\n                               [\"GmpCalculator::GmpCalculator.mod\",\n                                \"InvalidArgumentException::InvalidArgumentException.moduloByZero\"],\n                               [\"GmpCalculator::GmpCalculator.divide\",\n                                \"Number::Number.fromString\"],\n                               [\"GmpCalculator::GmpCalculator.multiply\",\n                                \"Number::Number.isDecimal\"],\n                               [\"MoneyFactory::MoneyFactory.__callStatic\",\n                                \"Currency::Currency.__construct\"],\n                               [\"BitcoinMoneyParser::BitcoinMoneyParser.parse\",\n                                \"Money::Money.__construct\"],\n                               [\"GmpCalculator::GmpCalculator.mod\",\n                                \"Number::Number.fromString\"],\n                               [\"Money::Money.divide\", \"Money::Money.round\"],\n                               [\"BcMathCalculator::BcMathCalculator.divide\",\n                                \"InvalidArgumentException::InvalidArgumentException.divisionByZero\"],\n                               [\"DecimalMoneyFormatter::DecimalMoneyFormatter.format\",\n                                \"Money::Money.getAmount\"],\n                               [\"BitcoinCurrencies::BitcoinCurrencies.subunitFor\",\n                                \"Currency::Currency.getCode\"],\n                               [\"Number::Number.fromString\", \"Number::Number.__construct\"],\n                               [\"BcMathCalculator::BcMathCalculator.ceil\",\n                                \"Number::Number.isInteger\"],\n                               [\"Converter::Converter.convert\",\n                                \"Converter::Converter.convertAgainstCurrencyPair\"],\n                               [\"IndirectExchange::IndirectExchange.getConversions\",\n                                \"IndirectExchange::IndirectExchange.reconstructConversionChain\"],\n                               [\"Comparator::Comparator.__construct\",\n                                \"AggregateCurrencies::AggregateCurrencies.__construct\"],\n                               [\"BcMathCalculator::BcMathCalculator.round\",\n                                \"Number::Number.fromString\"],\n                               [\"BcMathCalculator::BcMathCalculator.round\",\n                                \"BcMathCalculator::BcMathCalculator.roundDigit\"],\n                               [\"BcMathCalculator::BcMathCalculator.ceil\",\n                                \"Number::Number.fromString\"],\n                               [\"IndirectExchange::IndirectExchange.reconstructConversionChain\",\n                                \"Currency::Currency.getCode\"],\n                               [\"Money::Money.lessThanOrEqual\", \"Money::Money.compare\"],\n                               [\"IndirectExchange::IndirectExchange.quote\",\n                                \"CurrencyPair::CurrencyPair.__construct\"],\n                               [\"IndirectExchange::IndirectExchange.(Closure)\",\n                                \"Money::Money.getCalculator\"],\n                               [\"Money::Money.greaterThanOrEqual\", \"Money::Money.compare\"],\n                               [\"Money::Money.__construct\",\n                                \"Number::Number.getIntegerPart\"],\n                               [\"GmpCalculator::GmpCalculator.multiply\",\n                                \"Number::Number.getFractionalPart\"],\n                               [\"AggregateMoneyFormatter::AggregateMoneyFormatter.format\",\n                                \"Money::Money.getCurrency\"],\n                               [\"GmpCalculator::GmpCalculator.ceil\",\n                                \"GmpCalculator::GmpCalculator.add\"],\n                               [\"ExchangerExchange::ExchangerExchange.quote\",\n                                \"CurrencyPair::CurrencyPair.__construct\"],\n                               [\"ReversedCurrenciesExchange::ReversedCurrenciesExchange.quote\",\n                                \"CurrencyPair::CurrencyPair.__construct\"],\n                               [\"AggregateMoneyFormatter::AggregateMoneyFormatter.format\",\n                                \"Currency::Currency.getCode\"],\n                               [\"GmpCalculator::GmpCalculator.divide\",\n                                \"Number::Number.getIntegerPart\"],\n                               [\"MoneyFactory::MoneyFactory.__callStatic\",\n                                \"Money::Money.__construct\"],\n                               [\"Money::Money.multiply\", \"Money::Money.round\"],\n                               [\"SwapExchange::SwapExchange.quote\",\n                                \"UnresolvableCurrencyPairException::UnresolvableCurrencyPairException.createFromCurrencies\"],\n                               [\"Money::Money.mod\", \"Money::Money.__construct\"],\n                               [\"Converter::Converter.convertAgainstCurrencyPair\",\n                                \"Money::Money.__construct\"],\n                               [\"GmpCalculator::GmpCalculator.floor\",\n                                \"Number::Number.isInteger\"],\n                               [\"ExchangerExchange::ExchangerExchange.quote\",\n                                \"Currency::Currency.getCode\"],\n                               [\"BitcoinCurrencies::BitcoinCurrencies.getIterator\",\n                                \"Currency::Currency.__construct\"],\n                               [\"Converter::Converter.convertAgainstCurrencyPair\",\n                                \"CurrencyPair::CurrencyPair.getBaseCurrency\"],\n                               [\"CurrencyPair::CurrencyPair.createFromIso\",\n                                \"CurrencyPair::CurrencyPair.__construct\"],\n                               [\"ISOCurrencies::ISOCurrencies.getCurrencies\",\n                                \"ISOCurrencies::ISOCurrencies.loadCurrencies\"],\n                               [\"Money::Money.ratioOf\", \"Money::Money.isZero\"],\n                               [\"IntlMoneyParser::IntlMoneyParser.parse\",\n                                \"Money::Money.__construct\"],\n                               [\"GmpCalculator::GmpCalculator.floor\",\n                                \"Number::Number.fromString\"]],\n            \"expected_nodes\": [\"GmpCalculator::GmpCalculator.add\",\n                               \"FixedExchange::FixedExchange.quote\",\n                               \"Number::Number.roundMoneyValue\",\n                               \"CurrencyPair::CurrencyPair.getBaseCurrency\",\n                               \"ISOCurrencies::ISOCurrencies.loadCurrencies\",\n                               \"Number::Number.isCloserToNext\",\n                               \"GmpCalculator::GmpCalculator.roundDigit\",\n                               \"Money::Money.greaterThan\",\n                               \"Money::Money.subtract\",\n                               \"Money::Money.lessThanOrEqual\",\n                               \"CurrencyList::CurrencyList.subunitFor\",\n                               \"BcMathCalculator::BcMathCalculator.ceil\",\n                               \"Money::Money.equals\",\n                               \"Number::Number.isHalf\",\n                               \"Comparator::Comparator.__construct\",\n                               \"IndirectExchange::IndirectExchange.reconstructConversionChain\",\n                               \"Number::Number.__construct\",\n                               \"GmpCalculator::GmpCalculator.share\",\n                               \"Number::Number.getIntegerRoundingMultiplier\",\n                               \"Number::Number.isDecimal\",\n                               \"InvalidArgumentException::InvalidArgumentException.moduloByZero\",\n                               \"Money::Money.mod\",\n                               \"Converter::Converter.convertAgainstCurrencyPair\",\n                               \"BitcoinCurrencies::BitcoinCurrencies.getIterator\",\n                               \"GmpCalculator::GmpCalculator.compare\",\n                               \"IntlLocalizedDecimalParser::IntlLocalizedDecimalParser.parse\",\n                               \"Money::Money.multiply\",\n                               \"Money::Money.divide\",\n                               \"Money::Money.isZero\",\n                               \"Number::Number.fromNumber\",\n                               \"BcMathCalculator::BcMathCalculator.roundDigit\",\n                               \"IntlMoneyFormatter::IntlMoneyFormatter.format\",\n                               \"BcMathCalculator::BcMathCalculator.mod\",\n                               \"ISOCurrencies::ISOCurrencies.(Closure)\",\n                               \"BitcoinCurrencies::BitcoinCurrencies.contains\",\n                               \"Money::Money.getCurrency\",\n                               \"IndirectExchange::IndirectExchange.(Closure)\",\n                               \"Money::Money.__construct\",\n                               \"Number::Number.fromFloat\",\n                               \"Money::Money.allocate\",\n                               \"BitcoinMoneyFormatter::BitcoinMoneyFormatter.format\",\n                               \"Money::Money.lessThan\",\n                               \"ISOCurrencies::ISOCurrencies.contains\",\n                               \"Money::Money.add\",\n                               \"ReversedCurrenciesExchange::ReversedCurrenciesExchange.quote\",\n                               \"Number::Number.parseFractionalPart\",\n                               \"Currency::Currency.__construct\",\n                               \"ISOCurrencies::ISOCurrencies.subunitFor\",\n                               \"Money::Money.ratioOf\",\n                               \"Number::Number.isInteger\",\n                               \"SwapExchange::SwapExchange.quote\",\n                               \"BitcoinMoneyParser::BitcoinMoneyParser.parse\",\n                               \"IndirectExchange::IndirectExchange.quote\",\n                               \"Number::Number.base10\",\n                               \"CurrencyPair::CurrencyPair.createFromIso\",\n                               \"Number::Number.isCurrentEven\",\n                               \"DecimalMoneyParser::DecimalMoneyParser.parse\",\n                               \"GmpCalculator::GmpCalculator.divide\",\n                               \"ISOCurrencies::ISOCurrencies.numericCodeFor\",\n                               \"AggregateCurrencies::AggregateCurrencies.__construct\",\n                               \"GmpCalculator::GmpCalculator.floor\",\n                               \"Money::Money.negative\",\n                               \"Number::Number.getFractionalPart\",\n                               \"Money::Money.roundToUnit\",\n                               \"CurrencyList::CurrencyList.(Closure)\",\n                               \"ISOCurrencies::ISOCurrencies.getIterator\",\n                               \"IndirectExchange::IndirectExchange.getConversions\",\n                               \"InvalidArgumentException::InvalidArgumentException.divisionByZero\",\n                               \"Money::Money.compare\",\n                               \"CurrencyPair::CurrencyPair.__construct\",\n                               \"AggregateMoneyFormatter::AggregateMoneyFormatter.format\",\n                               \"DecimalMoneyFormatter::DecimalMoneyFormatter.format\",\n                               \"BcMathCalculator::BcMathCalculator.floor\",\n                               \"Money::Money.getCalculator\",\n                               \"BcMathCalculator::BcMathCalculator.divide\",\n                               \"IndirectExchangeQueuedItem::IndirectExchangeQueuedItem.__construct\",\n                               \"MoneyFactory::MoneyFactory.__callStatic\",\n                               \"Money::Money.greaterThanOrEqual\",\n                               \"Money::Money.min\",\n                               \"Converter::Converter.convert\",\n                               \"ISOCurrencies::ISOCurrencies.getCurrencies\",\n                               \"GmpCalculator::GmpCalculator.absolute\",\n                               \"Money::Money.absolute\",\n                               \"IntlMoneyParser::IntlMoneyParser.parse\",\n                               \"Number::Number.fromString\",\n                               \"Money::Money.max\",\n                               \"GmpCalculator::GmpCalculator.mod\",\n                               \"UnresolvableCurrencyPairException::UnresolvableCurrencyPairException.createFromCurrencies\",\n                               \"CurrencyPair::CurrencyPair.getConversionRatio\",\n                               \"Money::Money.getAmount\",\n                               \"IntlMoneyFormatter::IntlMoneyFormatter.__construct\",\n                               \"GmpCalculator::GmpCalculator.ceil\",\n                               \"ExchangerExchange::ExchangerExchange.quote\",\n                               \"Money::Money.round\",\n                               \"GmpCalculator::GmpCalculator.multiply\",\n                               \"GmpCalculator::GmpCalculator.round\",\n                               \"BcMathCalculator::BcMathCalculator.share\",\n                               \"IntlLocalizedDecimalFormatter::IntlLocalizedDecimalFormatter.format\",\n                               \"Converter::Converter.convertAndReturnWithCurrencyPair\",\n                               \"BcMathCalculator::BcMathCalculator.round\",\n                               \"CurrencyList::CurrencyList.contains\",\n                               \"Number::Number.parseIntegerPart\",\n                               \"Currency::Currency.getCode\",\n                               \"CurrencyPair::CurrencyPair.getCounterCurrency\",\n                               \"Money::Money.allocateTo\",\n                               \"BitcoinCurrencies::BitcoinCurrencies.subunitFor\",\n                               \"Number::Number.getIntegerPart\"]\n        }\n    ]\n}\n"
  }
]