[
  {
    "path": ".github/workflows/main.yml",
    "content": "name: CI\non: [push]\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Begin CI...\n        uses: actions/checkout@v2\n\n      - name: Use Node 12\n        uses: actions/setup-node@v1\n        with:\n          node-version: 12.x\n\n      - name: Use cached node_modules\n        uses: actions/cache@v1\n        with:\n          path: node_modules\n          key: nodeModules-${{ hashFiles('**/yarn.lock') }}\n          restore-keys: |\n            nodeModules-\n\n      - name: Install dependencies\n        run: yarn install --frozen-lockfile\n        env:\n          CI: true\n\n      - name: Build\n        run: yarn build\n        env:\n          CI: true\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nlib\n*.log\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.formatOnSave\": true,\n  \"typescript.tsdk\": \"node_modules\\\\typescript\\\\lib\"\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Kasper Mikiewicz\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">Typescript Expect Plugin</h1>\n<p align=\"center\">\n  <img  alt=\"npm\" src=\"https://img.shields.io/npm/v/typescript-expect-plugin?color=blue\" />\n  <img  alt=\"mit license\" src=\"https://img.shields.io/npm/l/typescript-expect-plugin?color=blue\" />\n  <img\n    alt=\"CI\"\n    src=\"https://github.com/Idered/typescript-expect-plugin/workflows/CI/badge.svg?event=push\"\n  />\n  <a\n    href=\"https://gitter.im/typescript-expect-plugin/community?utm_source=badge&amp;utm_medium=badge&amp;utm_campaign=pr-badge\"\n    ><img\n      alt=\"Gitter\"\n      src=\"https://badges.gitter.im/typescript-expect-plugin/community.svg\"\n  /></a>\n  <a href=\"https://twitter.com/intent/follow/?screen_name=Idered\">\n    <img alt=\"twitter\" src=\"https://img.shields.io/twitter/follow/Idered?style=social\" />\n  </a>\n</p>\n<p align=\"center\">Be lazy, write simple tests in comments.</p>\n\n<p align=\"center\">\n  <img src=\"https://i.imgur.com/AhQK9Pl.gif\" />\n</p>\n\n## Editor support\n\n✅ VS Code - flawlessly works in `Problems` panel.\n\n⏹ Sublime Text - could not get it to work but it might be possible.\n\n❔ Atom - not tested.\n\n⛔ `tsc` - plugins are disabled during build. It should work with webpack ts loader.\n\n## Quick start\n\n```sh\nnpm install typescript-expect-plugin\n```\n\n1.  Add plugin to `tsconfig.json`:\n```ts\n{\n  \"compilerOptions\": {\n    \"plugins\": [{ \"name\": \"typescript-expect-plugin\" }]\n  },\n}\n```\n\n2. Change VS Code typescript to workspace version:\n\n![](https://i.imgur.com/kK9BlMi.gif)\n\n## Usage\n\n## WARNING\n\n> ⚠Tests are executed after each file change - not save. Be careful if you're going to test functions that remove or change files in your local system\n\n---\n\nThis plugin adds support for `@expect` JSDoc tag. It has the following usage pattern:\n\n```tsx\n/**\n * @expect [PARAMS] CONDITION CONDITION_PARAMETER\n */\n```\n\n- `[PARAMS]` - for example `[2, 4]` will spread two arguments to tested function.\n- `CONDITION` - check function from jest expect library. Use `ctrl+space` to see autocomplete suggestions.\n- `CONDITION_PARAMETER` - argument passed to `CONDITION` function.\n\n## Examples\n\n```tsx\n/**\n * @expect [2, 4] toBe 6\n * @expect [2, 2] toBeGreaterThan 3\n * @expect [2, 2] toBeLessThan 3\n * @expect [2, 22] toEqual 24\n */\nexport function sum(a: number, b: number) {\n  return a + b;\n}\n\n/**\n * @expect [[2, 4, 8], 4] toBeTruthy\n * @expect [[2, 4, 8], 12] toBeFalsy\n */\nexport function has(haystack: any[], needle: any) {\n  return haystack.includes(needle);\n}\n\n/**\n * @expect [[2, 8], [9, 12]] toEqual [2, 8, 9, 12]\n */\nexport function join(arr1: any[], arr2: any[]) {\n  return [...arr1, ...arr2];\n}\n\n/**\n * @expect [{\"firstName\": \"John\"}, \"lastName\", \"Doe\"] toHaveProperty \"lastName\", \"Doe Doe\"\n */\nexport function withProp(obj: Record<string, any>, key: string, value: any) {\n  return {...obj, [key]: value}\n}\n```\n\n> ### Test objects\n![](https://i.imgur.com/ZplL1PV.gif)\n\n> ### Test arrays\n\n![](https://i.imgur.com/epox4Pu.gif)\n\n## Author\n\nHey there, I'm Kasper. If you wish to get notified about more cool typescript or react projects/tips you can follow me on twitter.\n\n<a href=\"https://twitter.com/intent/follow/?screen_name=Idered\">\n  <img alt=\"twitter\" src=\"https://img.shields.io/twitter/follow/Idered?style=social\" />\n</a>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"typescript-expect-plugin\",\n  \"description\": \"Be lazy, write simple tests in comments\",\n  \"version\": \"0.3.1\",\n  \"main\": \"lib/index.js\",\n  \"author\": \"Kasper Mikiewicz\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"dev\": \"tsc --watch\",\n    \"prepare\": \"tsc\"\n  },\n  \"files\": [\n    \"lib\"\n  ],\n  \"keywords\": [\n    \"typescript\",\n    \"typescript-plugin\",\n    \"test\",\n    \"jest\",\n    \"expect\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/idered/typescript-expect-plugin.git\"\n  },\n  \"homepage\": \"https://github.com/idered/typescript-expect-plugin#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/idered/typescript-expect-plugin/issues\"\n  },\n  \"engines\": {\n    \"node\": \">=10\"\n  },\n  \"dependencies\": {\n    \"byots\": \"^4.0.0-dev.20200523.13.57\",\n    \"expect\": \"^26.0.1\",\n    \"read-pkg-up\": \"^7.0.1\",\n    \"tmp\": \"^0.2.1\",\n    \"ts-node\": \"^8.10.1\"\n  },\n  \"peerDependencies\": {\n    \"typescript\": \">=3.7.0\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^14.0.1\",\n    \"@types/tmp\": \"^0.2.0\",\n    \"typescript\": \"^3.9.2\"\n  }\n}\n"
  },
  {
    "path": "src/adapter.ts",
    "content": "import ts, { ScriptElementKind } from \"typescript\";\nimport bts from \"byots\";\nimport { register } from \"ts-node\";\nimport visit from \"./visit\";\nimport { MessageBag } from \"./message-bag\";\nimport {\n  EXPECT_KEYWORDS,\n  TS_LANGSERVICE_EXPECT_DIAGNOSTIC_ERROR_CODE,\n} from \"./consts\";\n\nregister({\n  compilerOptions: {\n    target: \"ESNext\",\n  },\n});\n\nexport type ESLintAdapterOptions = {\n  logger: (msg: string) => void;\n  getSourceFile: (fileName: string) => ts.SourceFile | undefined;\n  getProgram?: () => ts.Program;\n};\n\nexport class Adapter {\n  private readonly logger: (msg: string) => void;\n  private readonly getSourceFile: (\n    fileName: string\n  ) => ts.SourceFile | undefined;\n  private messageBag: MessageBag;\n\n  public constructor({ logger, getSourceFile }: ESLintAdapterOptions) {\n    this.logger = logger;\n    this.getSourceFile = getSourceFile;\n    this.messageBag = new MessageBag();\n  }\n\n  public getSemanticDiagnostics(\n    delegate: ts.LanguageService[\"getSemanticDiagnostics\"],\n    fileName: string\n  ): ReturnType<ts.LanguageService[\"getSemanticDiagnostics\"]> {\n    const original = delegate(fileName);\n\n    try {\n      this.messageBag.clear();\n      const sourceFile = this.getSourceFile(fileName);\n      if (!sourceFile) return original;\n      visit(sourceFile, this.messageBag);\n      const diagnostics = original.length\n        ? []\n        : this.transformErrorsToDiagnostics(sourceFile);\n      return [...original, ...diagnostics];\n    } catch (error) {\n      this.logger(error.message ? error.message : \"unknown error\");\n      return original;\n    }\n  }\n\n  public getCompletionsAtPosition(\n    delegate: ts.LanguageService[\"getCompletionsAtPosition\"],\n    fileName: string,\n    position: number,\n    options: ts.GetCompletionsAtPositionOptions\n  ): ReturnType<ts.LanguageService[\"getCompletionsAtPosition\"]> {\n    let original = delegate(fileName, position, options);\n    const source = (this.getSourceFile(fileName) as unknown) as bts.SourceFile;\n    const token = bts.getTokenAtPosition(source, position);\n\n    if (bts.isJSDocTag(token) && original) {\n      original.entries = [\n        ...original.entries,\n        {\n          kind: ScriptElementKind.keyword,\n          kindModifiers: \"\",\n          name: \"expect\",\n          sortText: \"0\",\n        },\n      ];\n    }\n\n    if (bts.isInComment(source, position) && bts.isJSDoc(token)) {\n      if (!original) {\n        original = {\n          entries: [],\n          isGlobalCompletion: false,\n          isMemberCompletion: false,\n          isNewIdentifierLocation: false,\n        };\n      }\n\n      const tag = token.tags?.find(\n        (item) =>\n          item.end + item.comment?.length + 1 >= position &&\n          item.pos <= position\n      );\n      const isExpectTag =\n        tag && tag.comment && tag.tagName.escapedText === \"expect\";\n\n      if (isExpectTag) {\n        const hasNotKeyword = tag.comment?.includes(\"not\");\n        const fnKeyword =\n          EXPECT_KEYWORDS.find((keyword) => tag.comment?.includes(keyword)) ||\n          \"\";\n\n        const keywordPosition =\n          tag.end + tag.comment.indexOf(fnKeyword) + fnKeyword.length;\n\n        original.entries = [\n          ...original.entries,\n          ...[\n            ...(hasNotKeyword || fnKeyword ? [] : [\"not\"]),\n            ...(fnKeyword && keywordPosition !== position\n              ? []\n              : EXPECT_KEYWORDS),\n          ].map((name) => ({\n            kind: ScriptElementKind.functionElement,\n            name,\n            kindModifiers: \"\",\n            sortText: \"0\",\n          })),\n        ];\n      }\n    }\n\n    return original;\n  }\n\n  public getQuickInfoAtPosition(\n    delegate: ts.LanguageService[\"getQuickInfoAtPosition\"],\n    fileName: string,\n    position: number\n  ): ReturnType<ts.LanguageService[\"getQuickInfoAtPosition\"]> {\n    const original = delegate(fileName, position);\n    // Remove expect tags when user hover function name\n    if (original) {\n      original.tags = original.tags?.filter((item) => item.name !== \"expect\");\n    }\n    return original;\n  }\n\n  public getCompletionEntryDetails(\n    delegate: ts.LanguageService[\"getCompletionEntryDetails\"],\n    fileName: string,\n    position: number,\n    entryName: string,\n    formatOptions: ts.FormatCodeOptions | ts.FormatCodeSettings,\n    source: string,\n    preferences: ts.UserPreferences\n  ): ReturnType<ts.LanguageService[\"getCompletionEntryDetails\"]> {\n    const original = delegate(\n      fileName,\n      position,\n      entryName,\n      formatOptions,\n      source,\n      preferences\n    );\n    // Remove expect tags for autocomplete popup\n    if (original) {\n      original.tags = original.tags?.filter((item) => item.name !== \"expect\");\n    }\n    return original;\n  }\n\n  public getSignatureHelpItems(\n    delegate: ts.LanguageService[\"getSignatureHelpItems\"],\n    fileName: string,\n    position: number,\n    options: ts.SignatureHelpItemsOptions\n  ): ReturnType<ts.LanguageService[\"getSignatureHelpItems\"]> {\n    const original = delegate(fileName, position, options);\n    // Remove expect tags for autocomplete popup\n    if (original) {\n      original.items = original.items.map((item) => ({\n        ...item,\n        // Remove expect tags from signature tooltip\n        tags: item.tags?.filter((item) => item.name !== \"expect\"),\n      }));\n    }\n    return original;\n  }\n\n  private transformErrorsToDiagnostics(\n    sourceFile: ts.SourceFile\n  ): ts.Diagnostic[] {\n    return this.messageBag.messages.map((item) => ({\n      category: ts.DiagnosticCategory.Error,\n      file: sourceFile,\n      messageText: item.content,\n      start: item.pos,\n      length: item.end - item.pos - 1,\n      code: TS_LANGSERVICE_EXPECT_DIAGNOSTIC_ERROR_CODE,\n    }));\n  }\n}\n"
  },
  {
    "path": "src/consts.ts",
    "content": "export const TS_LANGSERVICE_EXPECT_DIAGNOSTIC_ERROR_CODE = 50555;\nexport const EXPECT_KEYWORDS = [\n  \"toBeDefined\",\n  \"toBe\",\n  \"toBeCloseTo\",\n  \"toBeFalsy\",\n  \"toBeGreaterThan\",\n  \"toBeGreaterThanOrEqual\",\n  \"toBeLessThan\",\n  \"toBeLessThanOrEqual\",\n  \"toBeNaN\",\n  \"toBeNull\",\n  \"toBeTruthy\",\n  \"toBeUndefined\",\n  \"toContain\",\n  \"toContainEqual\",\n  \"toEqual\",\n  \"toHaveLength\",\n  \"toHaveProperty\",\n  \"toMatchObject\",\n  \"toStrictEqual\",\n  \"toThrow\",\n];\n"
  },
  {
    "path": "src/get-expect-tag.ts",
    "content": "import ts from \"typescript\";\n\nexport function isJSDocExpectTag(tag: ts.JSDocTag): tag is ts.JSDocTag {\n  return tag.tagName.escapedText === \"expect\";\n}\n\nexport function getJSDocExpectTags(node: ts.Node): readonly ts.JSDocTag[] {\n  return ts.getAllJSDocTags(node, isJSDocExpectTag);\n}\n"
  },
  {
    "path": "src/get-temporary-file.ts",
    "content": "import ts from \"typescript\";\nimport { writeFileSync } from \"fs\";\nimport tmp from \"tmp\";\n\ntmp.setGracefulCleanup();\n\nfunction getTemporaryFile(node: ts.FunctionDeclaration) {\n  const sourceFile = node.getSourceFile();\n  const tempFile = tmp.fileSync({ postfix: \".ts\" });\n  const filepath = tempFile.name.split(\".\").slice(0, -1).join(\".\");\n  writeFileSync(tempFile.name, sourceFile.getFullText());\n  const fileModule = require(filepath);\n\n  return fileModule;\n}\n\nexport default getTemporaryFile;\n"
  },
  {
    "path": "src/index.ts",
    "content": "import { pluginModuleFactory } from \"./plugin-module-factory\";\n\nexport = pluginModuleFactory;\n"
  },
  {
    "path": "src/language-service-proxy-builder.ts",
    "content": "import ts from \"typescript/lib/tsserverlibrary\";\n\nexport type LanguageServiceMethodWrapper<K extends keyof ts.LanguageService> = (\n  delegate: ts.LanguageService[K],\n  info?: ts.server.PluginCreateInfo\n) => ts.LanguageService[K];\n\nexport class LanguageServiceProxyBuilder {\n  private readonly wrappers: any[] = [];\n  private readonly info: ts.server.PluginCreateInfo;\n\n  public constructor(info: ts.server.PluginCreateInfo) {\n    this.info = info;\n  }\n\n  public wrap<\n    K extends keyof ts.LanguageService,\n    Q extends LanguageServiceMethodWrapper<K>\n  >(name: K, wrapper: Q) {\n    this.wrappers.push({ name, wrapper });\n    return this;\n  }\n\n  public build(): ts.LanguageService {\n    const ret = this.info.languageService;\n\n    this.wrappers.forEach(({ name, wrapper }) => {\n      (ret as any)[name] = wrapper(\n        this.info.languageService[name as keyof ts.LanguageService],\n        this.info\n      );\n    });\n    return ret;\n  }\n}\n"
  },
  {
    "path": "src/message-bag.ts",
    "content": "export type Message = {\n  pos: number;\n  end: number;\n  content: string;\n};\n\nexport class MessageBag {\n  messages: Message[] = [];\n\n  clear() {\n    this.messages = [];\n  }\n\n  add(message: Message) {\n    this.messages = [...this.messages, message];\n  }\n}\n"
  },
  {
    "path": "src/parse-comment.ts",
    "content": "import { Matchers } from \"expect\";\n\nconst exp = /^(\\[.+\\])\\s{1}((not\\.?)?(?:\\w+))[\\s{1}]?(.+)?/;\n\nfunction parseComment(comment: string) {\n  if (!comment) return;\n  const match = comment.match(exp);\n  if (!match) return;\n  const [, params, method, , result] = match;\n  const matcher = (method as unknown) as keyof Pick<Matchers<any>, \"toEqual\">;\n  const isUndefined = result === undefined || result === \"undefined\";\n\n  return {\n    params: JSON.parse(params),\n    matcher,\n    result: isUndefined ? [undefined] : JSON.parse(`[${result}]`),\n  };\n}\n\nexport default parseComment;\n"
  },
  {
    "path": "src/plugin-module-factory.ts",
    "content": "import typescript from \"typescript/lib/tsserverlibrary\";\nimport { LanguageServiceProxyBuilder } from \"./language-service-proxy-builder\";\nimport { Adapter } from \"./adapter\";\n\n// TODO: Use provided typescript\nconst create = (ts: typeof typescript) => (\n  info: ts.server.PluginCreateInfo\n): ts.LanguageService => {\n  const { languageService, project } = info;\n  const logger = (msg: string) =>\n    project.projectService.logger.info(`[typescript-jest-service] ${msg}`);\n  const getProgram = () => {\n    const program = languageService.getProgram();\n    if (!program) throw new Error();\n    return program;\n  };\n  const adapter = new Adapter({\n    logger,\n    getSourceFile(fileName: string) {\n      return getProgram().getSourceFile(fileName);\n    },\n  });\n  const proxy = new LanguageServiceProxyBuilder(info)\n    .wrap(\"getSemanticDiagnostics\", (delegate) =>\n      adapter.getSemanticDiagnostics.bind(adapter, delegate)\n    )\n    .wrap(\"getQuickInfoAtPosition\", (delegate) =>\n      adapter.getQuickInfoAtPosition.bind(adapter, delegate)\n    )\n    .wrap(\"getCompletionEntryDetails\", (delegate) =>\n      adapter.getCompletionEntryDetails.bind(adapter, delegate)\n    )\n    .wrap(\"getSignatureHelpItems\", (delegate) =>\n      adapter.getSignatureHelpItems.bind(adapter, delegate)\n    )\n    .wrap(\"getCompletionsAtPosition\", (delegate) =>\n      adapter.getCompletionsAtPosition.bind(adapter, delegate)\n    )\n    .build();\n  return proxy;\n};\n\ntype FactoryProps = {\n  typescript: typeof typescript;\n};\n\nexport const pluginModuleFactory: typescript.server.PluginModuleFactory = ({\n  typescript,\n}: FactoryProps) => ({\n  create: create(typescript),\n});\n"
  },
  {
    "path": "src/visit.ts",
    "content": "import ts from \"typescript\";\nimport expect from \"expect\";\nimport parseComment from \"./parse-comment\";\nimport getTemporaryFile from \"./get-temporary-file\";\nimport { getJSDocExpectTags } from \"./get-expect-tag\";\nimport { MessageBag } from \"./message-bag\";\n\nexport default (node: ts.Node, messageBag: MessageBag) => {\n  let defaultExport: ts.Identifier;\n  let namedExports: ts.Identifier[] = [];\n\n  try {\n    // Get default export\n    node.forEachChild((node) => {\n      if (ts.isExportAssignment(node) && ts.isIdentifier(node.expression)) {\n        defaultExport = node.expression;\n      }\n    });\n\n    // Get named exports\n    node.forEachChild((node) => {\n      if (ts.isExportDeclaration(node)) {\n        node.exportClause.forEachChild((item) => {\n          if (ts.isExportSpecifier(item)) {\n            namedExports = [...namedExports, item.name];\n          }\n        });\n      }\n    });\n\n    node.forEachChild((node) => {\n      const isTopLevel = ts.isSourceFile(node.parent);\n\n      if (ts.isFunctionDeclaration(node) && isTopLevel) {\n        if (hasExportModifier(node) || hasNamedExport(namedExports, node)) {\n          executeTest(node, messageBag);\n        }\n        if (isDefaultExport(defaultExport, node)) {\n          executeTest(node, messageBag, {\n            defaultExport: true,\n          });\n        }\n      }\n    });\n  } catch (err) {}\n};\n\nfunction hasNamedExport(\n  namedExports: ts.Identifier[],\n  node: ts.FunctionDeclaration\n) {\n  return namedExports.find(\n    (item) => item.escapedText === node.name.escapedText\n  );\n}\n\nfunction isDefaultExport(\n  defaultExport: ts.Identifier,\n  node: ts.FunctionDeclaration\n) {\n  return defaultExport && node.name.escapedText === defaultExport.escapedText;\n}\n\nfunction hasExportModifier(node: ts.FunctionDeclaration) {\n  return node.modifiers?.some(\n    (item) => item.kind === ts.SyntaxKind.ExportKeyword\n  );\n}\n\nfunction executeTest(\n  node: ts.FunctionDeclaration,\n  messageBag: MessageBag,\n  options: {\n    defaultExport?: boolean;\n  } = {}\n) {\n  const expectTags = getJSDocExpectTags(node);\n  try {\n    var fileModule = getTemporaryFile(node);\n  } catch (err) {\n    if (err.diagnosticText) {\n      const [, pos, end] = err.diagnosticText.match(/\\((\\d+),(\\d+)\\)/);\n      messageBag.add({\n        pos,\n        end,\n        content: err.diagnosticText,\n      });\n    }\n  }\n\n  if (!fileModule) {\n    return;\n  }\n\n  for (const tag of expectTags) {\n    try {\n      const comment = parseComment(tag.comment);\n      if (!comment) continue;\n      const { matcher, params, result } = comment;\n      const functionName = options.defaultExport ? \"default\" : node.name.text;\n      const call = matcher\n        .split(\".\")\n        .reduce(\n          (prev, current) => prev[current],\n          expect(fileModule[functionName](...params))\n        );\n      if (typeof call === \"function\") {\n        call(...result);\n      }\n    } catch (err) {\n      messageBag.add({\n        pos: tag.pos,\n        end: tag.end,\n        content: err.message.replace(/\\n\\s*\\n/g, \"\\n\"),\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2015\",\n    \"outDir\": \"lib\",\n    \"module\": \"CommonJS\",\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true\n  }\n}\n"
  }
]