├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .npmignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── azure-pipelines.yml ├── bin ├── analyze-trace ├── print-trace-types └── simplify-trace-types ├── package-lock.json ├── package.json └── src ├── analyze-trace-dir.ts ├── analyze-trace-file.ts ├── analyze-trace-options.ts ├── analyze-trace-utilities.ts ├── count-import-expressions.ts ├── get-type-tree.ts ├── normalize-positions.ts ├── parse-trace-file.ts ├── print-trace-analysis-json.ts ├── print-trace-analysis-text.ts ├── print-types.ts ├── simplify-type.ts ├── simplify-types-file.ts ├── trivia-state-machine.ts └── tsconfig.json /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to npm 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | registry-url: 'https://registry.npmjs.org' 17 | 18 | # Ensure everything is set up right 19 | - run: 'npm ci' 20 | - run: 'npm run build' 21 | 22 | - uses: orta/npm-should-deploy-action@main 23 | id: check 24 | 25 | - run: 'npm publish' 26 | if: ${{ steps.check.outputs.deploy == 'true' }} 27 | env: 28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.map 3 | *.d.ts 4 | node_modules 5 | *.tsbuildinfo 6 | *.tgz -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | package-lock.json 3 | CODE_OF_CONDUCT.md 4 | CONTRIBUTING.md 5 | *.map 6 | .github 7 | azure-pipelines.yml 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | ## Building 16 | 17 | To build, run `npm ci` to download the required packages and `npm run build` to compile. 18 | 19 | ## Deployment 20 | 21 | To publish a new version of this package, change the version in `package.json` and merge the corresponding PR. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @typescript/analyze-trace 2 | 3 | [`@typescript/analyze-trace`](https://github.com/microsoft/typescript-analyze-trace) is tool for analyzing the output of `tsc --generateTrace` in a fast and digestible way (rather than more involved diagnostics [here](https://github.com/microsoft/TypeScript/wiki/Performance-Tracing)). 4 | 5 | Note: The goal is to identify clear-cut hot-spots and provide enough context to extract a small repro. 6 | The repro can then be used as the basis of a bug report or a starting point for manual code inspection or profiling. 7 | 8 | ## Usage 9 | 10 | The short version is to run these commands: 11 | 12 | ```sh 13 | tsc -p path/to/tsconfig.json --generateTrace traceDir 14 | npm install --no-save @typescript/analyze-trace 15 | npx analyze-trace traceDir 16 | ``` 17 | 18 | Each of these commands do the following: 19 | 20 | 1. Building your project with `--generateTrace` targeting a specific directory (e.g.`tsc -p path/to/tsconfig.json --generateTrace traceDir`) will create a new `traceDir` directory with paired trace and types files. 21 | Note that running with `-b`/`--build` mode works as well. 22 | 2. Installing `@typescript/analyze-trace` makes its various commands available in your project. 23 | 3. Running `npx analyze-trace traceDir` outputs a sorted list of compilation hot-spots - places where TypeScript is taking a high amount of time. 24 | 25 | The analyzer tries to refer back to files from your project to provide better output, and uses relative paths to do so. 26 | If your project changed since running `tsc --generateTrace`, or you moved your trace output directory, then the tool's results may be misleading. 27 | For best results, re-run `--generateTrace` when files and dependencies are updated, and ensure the trace output is always in the same relative location with respect to the input project. 28 | 29 | You can run `npx analyze-trace --help` to find out about other options including: 30 | 31 | Option | Default | Description 32 | --------------------------|---------|------------------------------------------------------------------------------- 33 | `--skipMillis [number]` | `100` | Suppress events that take less than the specified number of milliseconds. Reduce this value to see more output (maybe on faster machines), and increase it to reduce clutter. 34 | `--forceMillis [number]` | `500` | Report all un-skipped events that take longer than the specified number of milliseconds. Reduce it to reveal more potential hot-spots that the built-in heuristic will not flag. Note that `forceMillis` is always lower-bounded by `skipMillis`. 35 | `--color [boolean]` | `true` | Color the output to make it easier to read. Turn this off when redirecting output to a file. 36 | `--expandTypes [boolean]` | `true` | Expand the names of types when printing them. Turn this off when types are too verbose. 37 | `--json [boolean]` | `false` | *Experimental and unstable*: Produce JSON output for programmatic consumption. 38 | 39 | For a simplified view of a `types.json` file (useful when investigating an individual trace), you can run `npx simplify-trace-types traceDir/types.json output.txt`. 40 | Note that the resulting file is for human consumption and should not be passed to the analyzer (i.e. don't clobber the original). 41 | 42 | ### Interpreting Results 43 | 44 | The `analyze-trace` output will try to highlight the most expensive portions of a compilation that it was able to measure (a.k.a. "hot spots"). 45 | Each hot spot may have a breakdown of other contributing hot spots. 46 | 47 | #### Hot Spots 48 | 49 | `analyze-trace` will also try to point out when multiple versions of the same npm package were loaded and type-checked. 50 | 51 | Output may look like the following: 52 | 53 | ``` 54 | Hot Spots 55 | ├─ Check file /some/sample/project/node_modules/typescript/lib/lib.dom.d.ts (899ms) 56 | ├─ Check file /some/sample/project/node_modules/@types/lodash/common/common.d.ts (530ms) 57 | │ └─ Compare types 50638 and 50640 (511ms) 58 | │ └─ Compare types 50643 and 50642 (511ms) 59 | │ └─ Compare types 50648 and 50644 (511ms) 60 | │ └─ Determine variance of type 50492 (511ms) 61 | │ └─ Compare types 50652 and 50651 (501ms) 62 | | └─ ... 63 | ├─ Check file /some/sample/project/node_modules/@types/babel__traverse/index.d.ts (511ms) 64 | └─ Check file /some/sample/project/node_modules/@types/react/index.d.ts (507ms) 65 | ``` 66 | 67 | Each step here is annotated with check times (e.g. checking two types in lodash took over 500ms). 68 | 69 | Some common messages include the following: 70 | 71 | Message | Explanation 72 | --------|------------ 73 | "Compare types 1234 and 5678" | TypeScript had to check whether two types with internal IDs `1234` and `5678` were related. 74 | "Determine variance of type 1234" | TypeScript had to check whether a `Foo` was compatible with a `Foo`. Instead of calculating all the members of `Foo` and `Foo` and comparing them, calculating variance allows TypeScript to know whether in such cases, it can just relate `T` to `U`, or `U` to `T`. Variance calculation requires a few up-front comparisons to possibly save all future ones. 75 | "Emit declaration file" | Generating the declaration file for the current file took a while. 76 | "Consider adding `import "./some/import/path"` which is used in 1234 places" | TypeScript's `--declaration` emit needed to generate 1234 imports for `./some/import/path`. Consider directly importing this path so the type-checker can avoid emitting the same path over and over in the declaration file. Also consider using [explicit type annotations](https://github.com/microsoft/TypeScript/wiki/Performance#using-type-annotations) so the type-checker can avoid time calculating the best path to a module and how to best display the type at all. 77 | 78 | Other messages correspond roughly to specific functions in the compiler, but are *typically* self-explanatory. 79 | 80 | File names will be the first indicators of where to look. 81 | Often, type IDs are used in place of more precise (but often verbose) type names, regardless of whether `--expandTypes` is on. 82 | The `types.json` file will provide a way to look these up. 83 | 84 | #### Duplicate Packages 85 | 86 | `analyze-trace` will point out instances of duplicate packages in `node_modules`. 87 | These can be caused by multiple projects in a mono-repo that use different versions of the same package, or possibly from dependencies in `node_modules` that all specify different versions a library. 88 | 89 | Duplicate packages may or may not be expected, but loading up multiple copies of a library can have negative effects on a build. 90 | For one, they add more time to TypeScript's parsing, binding, and possibly checking stages. 91 | Beyond that, duplicate copies of the same types may end up being passed around and compared to each other. 92 | Because these types don't share the same root identities, fewer optimizations can be made around them. 93 | 94 | ### Acting on Results 95 | 96 | #### Hot Spots 97 | 98 | Once you've found the "culprit" code that's making your build slow, try to create a minimal version of this code to isolate the issue and experiment. 99 | In some cases you can try to rewrite or simplify your code, and [our team has a few suggestions for common issues here](https://github.com/microsoft/TypeScript/wiki/Performance#writing-easy-to-compile-code). 100 | If culprit code occurs in a library, it may be worth filing an issue with that library or sending a pull request to provide simplifications. 101 | 102 | If you believe you have a minimal isolated reproduction of the issue that might be worth optimizing in TypeScript itself, [you are encouraged to file an issue](https://github.com/microsoft/TypeScript/issues/new/choose). 103 | 104 | #### Duplicate Packages 105 | 106 | Updating projects within your monorepo to share the same dependencies may be one way to fix this issue. 107 | Updating your dependencies may be another, though it won't always be the case that the most up-to-date versions of your dependencies list their dependencies in a compatible way. 108 | If libraries you consume cannot be updated to list compatible dependency ranges, consider using [`overrides` in `package.json` for npm](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#overrides) or [for pnpm](https://pnpm.io/package_json#pnpmoverrides), or [`resolutions` in `package.json` for Yarn](https://classic.yarnpkg.com/lang/en/docs/selective-version-resolutions/). 109 | 110 | #### Iterating on Results 111 | 112 | You may want to tweak the `--skipMillis` and `--forceMillis` options to uncover hot spots that `analyze-trace` may not reveal. 113 | 114 | You may also want to try [visualizing a performance trace](https://github.com/microsoft/TypeScript/wiki/Performance-Tracing)) for a more detailed view. 115 | Iterating between the `analyze-trace` tool and an interactive visualizer might be a helpful workflow. 116 | 117 | 118 | Reading up further on [the TypeScript compiler's performance diagnostics page](https://github.com/microsoft/TypeScript/wiki/Performance) may provide ideas and options for your team as well. 119 | 120 | ## Trademarks 121 | 122 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 123 | trademarks or logos is subject to and must follow 124 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 125 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 126 | Any use of third-party trademarks or logos are subject to those third-party's policies. 127 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | ## Microsoft Support Policy 10 | 11 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 12 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | pr: 2 | - main 3 | 4 | pool: 5 | name: VSEngSS-MicroBuild2019-1ES 6 | 7 | variables: 8 | TeamName: TypeScript 9 | 10 | steps: 11 | - task: NuGetToolInstaller@1 12 | inputs: 13 | versionSpec: '5.x' 14 | - task: CredScan@3 15 | - task: PoliCheck@2 16 | - task: AntiMalware@4 17 | - task: PublishSecurityAnalysisLogs@3 18 | - task: PostAnalysis@2 19 | 20 | - task: NodeTool@0 21 | inputs: 22 | versionSpec: '14.x' 23 | displayName: 'Install Node.js' 24 | - script: | 25 | npm ci 26 | npm run build 27 | displayName: 'npm install and build' 28 | 29 | - task: MicroBuildCleanup@1 30 | -------------------------------------------------------------------------------- /bin/analyze-trace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../dist/analyze-trace-dir.js') -------------------------------------------------------------------------------- /bin/print-trace-types: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../dist/print-types.js') -------------------------------------------------------------------------------- /bin/simplify-trace-types: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../dist/simplify-types-file.js') -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typescript/analyze-trace", 3 | "version": "0.10.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@typescript/analyze-trace", 9 | "version": "0.10.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "chalk": "^4.1.2", 13 | "exit": "^0.1.2", 14 | "jsonparse": "^1.3.1", 15 | "jsonstream-next": "^3.0.0", 16 | "p-limit": "^3.1.0", 17 | "split2": "^3.2.2", 18 | "treeify": "^1.1.0", 19 | "yargs": "^16.2.0" 20 | }, 21 | "bin": { 22 | "analyze-trace": "bin/analyze-trace", 23 | "print-trace-types": "bin/print-trace-types", 24 | "simplify-trace-types": "bin/simplify-trace-types" 25 | }, 26 | "devDependencies": { 27 | "@types/jsonstream-next": "^3.0.1", 28 | "@types/node": "^14.18.5", 29 | "@types/split2": "^3.2.1", 30 | "@types/treeify": "^1.0.0", 31 | "@types/yargs": "^16.0.4", 32 | "typescript": "^4.5.4" 33 | } 34 | }, 35 | "node_modules/@types/jsonstream-next": { 36 | "version": "3.0.1", 37 | "resolved": "https://registry.npmjs.org/@types/jsonstream-next/-/jsonstream-next-3.0.1.tgz", 38 | "integrity": "sha512-lTc4l4EJTYNrnUtDjV4E/6OS7bU4a6zS0hATBVjgnmFy/Hq9p59hTrIPhXkWL17WcJOw/YwIWVPp8Ah3D0kCew==", 39 | "dev": true, 40 | "dependencies": { 41 | "@types/node": "*" 42 | } 43 | }, 44 | "node_modules/@types/node": { 45 | "version": "14.18.5", 46 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.5.tgz", 47 | "integrity": "sha512-LMy+vDDcQR48EZdEx5wRX1q/sEl6NdGuHXPnfeL8ixkwCOSZ2qnIyIZmcCbdX0MeRqHhAcHmX+haCbrS8Run+A==", 48 | "dev": true 49 | }, 50 | "node_modules/@types/split2": { 51 | "version": "3.2.1", 52 | "resolved": "https://registry.npmjs.org/@types/split2/-/split2-3.2.1.tgz", 53 | "integrity": "sha512-7uz3yU+LooBq4yNOzlZD9PU9/1Eu0rTD1MjQ6apOVEoHsPrMUrFw7W8XrvWtesm2vK67SBK9AyJcOXtMpl9bgQ==", 54 | "dev": true, 55 | "dependencies": { 56 | "@types/node": "*" 57 | } 58 | }, 59 | "node_modules/@types/treeify": { 60 | "version": "1.0.0", 61 | "resolved": "https://registry.npmjs.org/@types/treeify/-/treeify-1.0.0.tgz", 62 | "integrity": "sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg==", 63 | "dev": true 64 | }, 65 | "node_modules/@types/yargs": { 66 | "version": "16.0.4", 67 | "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", 68 | "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", 69 | "dev": true, 70 | "dependencies": { 71 | "@types/yargs-parser": "*" 72 | } 73 | }, 74 | "node_modules/@types/yargs-parser": { 75 | "version": "20.2.1", 76 | "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", 77 | "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", 78 | "dev": true 79 | }, 80 | "node_modules/ansi-regex": { 81 | "version": "5.0.1", 82 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 83 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 84 | "engines": { 85 | "node": ">=8" 86 | } 87 | }, 88 | "node_modules/ansi-styles": { 89 | "version": "4.3.0", 90 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 91 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 92 | "dependencies": { 93 | "color-convert": "^2.0.1" 94 | }, 95 | "engines": { 96 | "node": ">=8" 97 | }, 98 | "funding": { 99 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 100 | } 101 | }, 102 | "node_modules/chalk": { 103 | "version": "4.1.2", 104 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 105 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 106 | "dependencies": { 107 | "ansi-styles": "^4.1.0", 108 | "supports-color": "^7.1.0" 109 | }, 110 | "engines": { 111 | "node": ">=10" 112 | }, 113 | "funding": { 114 | "url": "https://github.com/chalk/chalk?sponsor=1" 115 | } 116 | }, 117 | "node_modules/cliui": { 118 | "version": "7.0.4", 119 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 120 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 121 | "dependencies": { 122 | "string-width": "^4.2.0", 123 | "strip-ansi": "^6.0.0", 124 | "wrap-ansi": "^7.0.0" 125 | } 126 | }, 127 | "node_modules/color-convert": { 128 | "version": "2.0.1", 129 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 130 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 131 | "dependencies": { 132 | "color-name": "~1.1.4" 133 | }, 134 | "engines": { 135 | "node": ">=7.0.0" 136 | } 137 | }, 138 | "node_modules/color-name": { 139 | "version": "1.1.4", 140 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 141 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 142 | }, 143 | "node_modules/emoji-regex": { 144 | "version": "8.0.0", 145 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 146 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 147 | }, 148 | "node_modules/escalade": { 149 | "version": "3.1.1", 150 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 151 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 152 | "engines": { 153 | "node": ">=6" 154 | } 155 | }, 156 | "node_modules/exit": { 157 | "version": "0.1.2", 158 | "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", 159 | "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", 160 | "engines": { 161 | "node": ">= 0.8.0" 162 | } 163 | }, 164 | "node_modules/get-caller-file": { 165 | "version": "2.0.5", 166 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 167 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 168 | "engines": { 169 | "node": "6.* || 8.* || >= 10.*" 170 | } 171 | }, 172 | "node_modules/has-flag": { 173 | "version": "4.0.0", 174 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 175 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 176 | "engines": { 177 | "node": ">=8" 178 | } 179 | }, 180 | "node_modules/inherits": { 181 | "version": "2.0.4", 182 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 183 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 184 | }, 185 | "node_modules/is-fullwidth-code-point": { 186 | "version": "3.0.0", 187 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 188 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 189 | "engines": { 190 | "node": ">=8" 191 | } 192 | }, 193 | "node_modules/jsonparse": { 194 | "version": "1.3.1", 195 | "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", 196 | "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", 197 | "engines": [ 198 | "node >= 0.2.0" 199 | ] 200 | }, 201 | "node_modules/jsonstream-next": { 202 | "version": "3.0.0", 203 | "resolved": "https://registry.npmjs.org/jsonstream-next/-/jsonstream-next-3.0.0.tgz", 204 | "integrity": "sha512-aAi6oPhdt7BKyQn1SrIIGZBt0ukKuOUE1qV6kJ3GgioSOYzsRc8z9Hfr1BVmacA/jLe9nARfmgMGgn68BqIAgg==", 205 | "dependencies": { 206 | "jsonparse": "^1.2.0", 207 | "through2": "^4.0.2" 208 | }, 209 | "bin": { 210 | "jsonstream-next": "bin.js" 211 | }, 212 | "engines": { 213 | "node": ">=10" 214 | } 215 | }, 216 | "node_modules/p-limit": { 217 | "version": "3.1.0", 218 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 219 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 220 | "dependencies": { 221 | "yocto-queue": "^0.1.0" 222 | }, 223 | "engines": { 224 | "node": ">=10" 225 | }, 226 | "funding": { 227 | "url": "https://github.com/sponsors/sindresorhus" 228 | } 229 | }, 230 | "node_modules/readable-stream": { 231 | "version": "3.6.0", 232 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 233 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 234 | "dependencies": { 235 | "inherits": "^2.0.3", 236 | "string_decoder": "^1.1.1", 237 | "util-deprecate": "^1.0.1" 238 | }, 239 | "engines": { 240 | "node": ">= 6" 241 | } 242 | }, 243 | "node_modules/require-directory": { 244 | "version": "2.1.1", 245 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 246 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 247 | "engines": { 248 | "node": ">=0.10.0" 249 | } 250 | }, 251 | "node_modules/safe-buffer": { 252 | "version": "5.2.1", 253 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 254 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 255 | "funding": [ 256 | { 257 | "type": "github", 258 | "url": "https://github.com/sponsors/feross" 259 | }, 260 | { 261 | "type": "patreon", 262 | "url": "https://www.patreon.com/feross" 263 | }, 264 | { 265 | "type": "consulting", 266 | "url": "https://feross.org/support" 267 | } 268 | ] 269 | }, 270 | "node_modules/split2": { 271 | "version": "3.2.2", 272 | "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", 273 | "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", 274 | "dependencies": { 275 | "readable-stream": "^3.0.0" 276 | } 277 | }, 278 | "node_modules/string_decoder": { 279 | "version": "1.3.0", 280 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 281 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 282 | "dependencies": { 283 | "safe-buffer": "~5.2.0" 284 | } 285 | }, 286 | "node_modules/string-width": { 287 | "version": "4.2.3", 288 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 289 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 290 | "dependencies": { 291 | "emoji-regex": "^8.0.0", 292 | "is-fullwidth-code-point": "^3.0.0", 293 | "strip-ansi": "^6.0.1" 294 | }, 295 | "engines": { 296 | "node": ">=8" 297 | } 298 | }, 299 | "node_modules/strip-ansi": { 300 | "version": "6.0.1", 301 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 302 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 303 | "dependencies": { 304 | "ansi-regex": "^5.0.1" 305 | }, 306 | "engines": { 307 | "node": ">=8" 308 | } 309 | }, 310 | "node_modules/supports-color": { 311 | "version": "7.2.0", 312 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 313 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 314 | "dependencies": { 315 | "has-flag": "^4.0.0" 316 | }, 317 | "engines": { 318 | "node": ">=8" 319 | } 320 | }, 321 | "node_modules/through2": { 322 | "version": "4.0.2", 323 | "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", 324 | "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", 325 | "dependencies": { 326 | "readable-stream": "3" 327 | } 328 | }, 329 | "node_modules/treeify": { 330 | "version": "1.1.0", 331 | "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", 332 | "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", 333 | "engines": { 334 | "node": ">=0.6" 335 | } 336 | }, 337 | "node_modules/typescript": { 338 | "version": "4.5.4", 339 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", 340 | "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", 341 | "dev": true, 342 | "bin": { 343 | "tsc": "bin/tsc", 344 | "tsserver": "bin/tsserver" 345 | }, 346 | "engines": { 347 | "node": ">=4.2.0" 348 | } 349 | }, 350 | "node_modules/util-deprecate": { 351 | "version": "1.0.2", 352 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 353 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 354 | }, 355 | "node_modules/wrap-ansi": { 356 | "version": "7.0.0", 357 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 358 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 359 | "dependencies": { 360 | "ansi-styles": "^4.0.0", 361 | "string-width": "^4.1.0", 362 | "strip-ansi": "^6.0.0" 363 | }, 364 | "engines": { 365 | "node": ">=10" 366 | }, 367 | "funding": { 368 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 369 | } 370 | }, 371 | "node_modules/y18n": { 372 | "version": "5.0.8", 373 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 374 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 375 | "engines": { 376 | "node": ">=10" 377 | } 378 | }, 379 | "node_modules/yargs": { 380 | "version": "16.2.0", 381 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 382 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 383 | "dependencies": { 384 | "cliui": "^7.0.2", 385 | "escalade": "^3.1.1", 386 | "get-caller-file": "^2.0.5", 387 | "require-directory": "^2.1.1", 388 | "string-width": "^4.2.0", 389 | "y18n": "^5.0.5", 390 | "yargs-parser": "^20.2.2" 391 | }, 392 | "engines": { 393 | "node": ">=10" 394 | } 395 | }, 396 | "node_modules/yargs-parser": { 397 | "version": "20.2.9", 398 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", 399 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", 400 | "engines": { 401 | "node": ">=10" 402 | } 403 | }, 404 | "node_modules/yocto-queue": { 405 | "version": "0.1.0", 406 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 407 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 408 | "engines": { 409 | "node": ">=10" 410 | }, 411 | "funding": { 412 | "url": "https://github.com/sponsors/sindresorhus" 413 | } 414 | } 415 | }, 416 | "dependencies": { 417 | "@types/jsonstream-next": { 418 | "version": "3.0.1", 419 | "resolved": "https://registry.npmjs.org/@types/jsonstream-next/-/jsonstream-next-3.0.1.tgz", 420 | "integrity": "sha512-lTc4l4EJTYNrnUtDjV4E/6OS7bU4a6zS0hATBVjgnmFy/Hq9p59hTrIPhXkWL17WcJOw/YwIWVPp8Ah3D0kCew==", 421 | "dev": true, 422 | "requires": { 423 | "@types/node": "*" 424 | } 425 | }, 426 | "@types/node": { 427 | "version": "14.18.5", 428 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.5.tgz", 429 | "integrity": "sha512-LMy+vDDcQR48EZdEx5wRX1q/sEl6NdGuHXPnfeL8ixkwCOSZ2qnIyIZmcCbdX0MeRqHhAcHmX+haCbrS8Run+A==", 430 | "dev": true 431 | }, 432 | "@types/split2": { 433 | "version": "3.2.1", 434 | "resolved": "https://registry.npmjs.org/@types/split2/-/split2-3.2.1.tgz", 435 | "integrity": "sha512-7uz3yU+LooBq4yNOzlZD9PU9/1Eu0rTD1MjQ6apOVEoHsPrMUrFw7W8XrvWtesm2vK67SBK9AyJcOXtMpl9bgQ==", 436 | "dev": true, 437 | "requires": { 438 | "@types/node": "*" 439 | } 440 | }, 441 | "@types/treeify": { 442 | "version": "1.0.0", 443 | "resolved": "https://registry.npmjs.org/@types/treeify/-/treeify-1.0.0.tgz", 444 | "integrity": "sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg==", 445 | "dev": true 446 | }, 447 | "@types/yargs": { 448 | "version": "16.0.4", 449 | "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", 450 | "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", 451 | "dev": true, 452 | "requires": { 453 | "@types/yargs-parser": "*" 454 | } 455 | }, 456 | "@types/yargs-parser": { 457 | "version": "20.2.1", 458 | "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", 459 | "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", 460 | "dev": true 461 | }, 462 | "ansi-regex": { 463 | "version": "5.0.1", 464 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 465 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 466 | }, 467 | "ansi-styles": { 468 | "version": "4.3.0", 469 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 470 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 471 | "requires": { 472 | "color-convert": "^2.0.1" 473 | } 474 | }, 475 | "chalk": { 476 | "version": "4.1.2", 477 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 478 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 479 | "requires": { 480 | "ansi-styles": "^4.1.0", 481 | "supports-color": "^7.1.0" 482 | } 483 | }, 484 | "cliui": { 485 | "version": "7.0.4", 486 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 487 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 488 | "requires": { 489 | "string-width": "^4.2.0", 490 | "strip-ansi": "^6.0.0", 491 | "wrap-ansi": "^7.0.0" 492 | } 493 | }, 494 | "color-convert": { 495 | "version": "2.0.1", 496 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 497 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 498 | "requires": { 499 | "color-name": "~1.1.4" 500 | } 501 | }, 502 | "color-name": { 503 | "version": "1.1.4", 504 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 505 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 506 | }, 507 | "emoji-regex": { 508 | "version": "8.0.0", 509 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 510 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 511 | }, 512 | "escalade": { 513 | "version": "3.1.1", 514 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 515 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" 516 | }, 517 | "exit": { 518 | "version": "0.1.2", 519 | "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", 520 | "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==" 521 | }, 522 | "get-caller-file": { 523 | "version": "2.0.5", 524 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 525 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 526 | }, 527 | "has-flag": { 528 | "version": "4.0.0", 529 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 530 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 531 | }, 532 | "inherits": { 533 | "version": "2.0.4", 534 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 535 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 536 | }, 537 | "is-fullwidth-code-point": { 538 | "version": "3.0.0", 539 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 540 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 541 | }, 542 | "jsonparse": { 543 | "version": "1.3.1", 544 | "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", 545 | "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" 546 | }, 547 | "jsonstream-next": { 548 | "version": "3.0.0", 549 | "resolved": "https://registry.npmjs.org/jsonstream-next/-/jsonstream-next-3.0.0.tgz", 550 | "integrity": "sha512-aAi6oPhdt7BKyQn1SrIIGZBt0ukKuOUE1qV6kJ3GgioSOYzsRc8z9Hfr1BVmacA/jLe9nARfmgMGgn68BqIAgg==", 551 | "requires": { 552 | "jsonparse": "^1.2.0", 553 | "through2": "^4.0.2" 554 | } 555 | }, 556 | "p-limit": { 557 | "version": "3.1.0", 558 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 559 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 560 | "requires": { 561 | "yocto-queue": "^0.1.0" 562 | } 563 | }, 564 | "readable-stream": { 565 | "version": "3.6.0", 566 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 567 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 568 | "requires": { 569 | "inherits": "^2.0.3", 570 | "string_decoder": "^1.1.1", 571 | "util-deprecate": "^1.0.1" 572 | } 573 | }, 574 | "require-directory": { 575 | "version": "2.1.1", 576 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 577 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 578 | }, 579 | "safe-buffer": { 580 | "version": "5.2.1", 581 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 582 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 583 | }, 584 | "split2": { 585 | "version": "3.2.2", 586 | "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", 587 | "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", 588 | "requires": { 589 | "readable-stream": "^3.0.0" 590 | } 591 | }, 592 | "string_decoder": { 593 | "version": "1.3.0", 594 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 595 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 596 | "requires": { 597 | "safe-buffer": "~5.2.0" 598 | } 599 | }, 600 | "string-width": { 601 | "version": "4.2.3", 602 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 603 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 604 | "requires": { 605 | "emoji-regex": "^8.0.0", 606 | "is-fullwidth-code-point": "^3.0.0", 607 | "strip-ansi": "^6.0.1" 608 | } 609 | }, 610 | "strip-ansi": { 611 | "version": "6.0.1", 612 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 613 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 614 | "requires": { 615 | "ansi-regex": "^5.0.1" 616 | } 617 | }, 618 | "supports-color": { 619 | "version": "7.2.0", 620 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 621 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 622 | "requires": { 623 | "has-flag": "^4.0.0" 624 | } 625 | }, 626 | "through2": { 627 | "version": "4.0.2", 628 | "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", 629 | "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", 630 | "requires": { 631 | "readable-stream": "3" 632 | } 633 | }, 634 | "treeify": { 635 | "version": "1.1.0", 636 | "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", 637 | "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==" 638 | }, 639 | "typescript": { 640 | "version": "4.5.4", 641 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", 642 | "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", 643 | "dev": true 644 | }, 645 | "util-deprecate": { 646 | "version": "1.0.2", 647 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 648 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 649 | }, 650 | "wrap-ansi": { 651 | "version": "7.0.0", 652 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 653 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 654 | "requires": { 655 | "ansi-styles": "^4.0.0", 656 | "string-width": "^4.1.0", 657 | "strip-ansi": "^6.0.0" 658 | } 659 | }, 660 | "y18n": { 661 | "version": "5.0.8", 662 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 663 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" 664 | }, 665 | "yargs": { 666 | "version": "16.2.0", 667 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 668 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 669 | "requires": { 670 | "cliui": "^7.0.2", 671 | "escalade": "^3.1.1", 672 | "get-caller-file": "^2.0.5", 673 | "require-directory": "^2.1.1", 674 | "string-width": "^4.2.0", 675 | "y18n": "^5.0.5", 676 | "yargs-parser": "^20.2.2" 677 | } 678 | }, 679 | "yargs-parser": { 680 | "version": "20.2.9", 681 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", 682 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" 683 | }, 684 | "yocto-queue": { 685 | "version": "0.1.0", 686 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 687 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" 688 | } 689 | } 690 | } 691 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typescript/analyze-trace", 3 | "author": "Microsoft Corp.", 4 | "homepage": "https://github.com/microsoft/typescript-analyze-trace#readme", 5 | "version": "0.10.1", 6 | "license": "MIT", 7 | "description": "Analyze the output of tsc --generatetrace", 8 | "keywords": [ 9 | "TypeScript", 10 | "Microsoft", 11 | "trace", 12 | "generateTrace" 13 | ], 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/microsoft/typescript-analyze-trace/issues" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/microsoft/typescript-analyze-trace.git" 23 | }, 24 | "main": "./dist/analyze-trace-directory.js", 25 | "typings": "./dist/analyze-trace-directory.d.ts", 26 | "bin": { 27 | "simplify-trace-types": "./bin/simplify-trace-types", 28 | "print-trace-types": "./bin/print-trace-types", 29 | "analyze-trace": "./bin/analyze-trace" 30 | }, 31 | "scripts": { 32 | "build": "tsc -p src" 33 | }, 34 | "packageManager": "npm@8.19.4", 35 | "devDependencies": { 36 | "@types/jsonstream-next": "^3.0.1", 37 | "@types/node": "^14.18.5", 38 | "@types/split2": "^3.2.1", 39 | "@types/treeify": "^1.0.0", 40 | "@types/yargs": "^16.0.4", 41 | "typescript": "^4.5.4" 42 | }, 43 | "dependencies": { 44 | "chalk": "^4.1.2", 45 | "exit": "^0.1.2", 46 | "jsonparse": "^1.3.1", 47 | "jsonstream-next": "^3.0.0", 48 | "p-limit": "^3.1.0", 49 | "split2": "^3.2.2", 50 | "treeify": "^1.1.0", 51 | "yargs": "^16.2.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/analyze-trace-dir.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import cp = require("child_process"); 5 | import fs = require("fs"); 6 | import os = require("os"); 7 | import path = require("path"); 8 | 9 | import exit = require("exit"); 10 | import plimit = require("p-limit"); 11 | import yargs = require("yargs"); 12 | 13 | import { commandLineOptions, checkCommandLineOptions, pushCommandLineOptions } from "./analyze-trace-options"; 14 | 15 | const argv = yargs(process.argv.slice(2)) 16 | .command("$0 ", "Preprocess tracing type dumps", yargs => yargs 17 | .positional("traceDir", { type: "string", desc: "Directory of trace and types files", coerce: throwIfNotDirectory }) 18 | .options(commandLineOptions) 19 | .check(checkCommandLineOptions) 20 | .help("h").alias("h", "help") 21 | .epilog("Exits with code 0 if highlights were found, 1 if no highlights were found, and 2 if an error occurred") 22 | .strict()) 23 | .argv; 24 | 25 | // Try to leave one core free 26 | const limit = plimit(Math.max(1, os.cpus().length - 1)); 27 | 28 | const traceDir = argv.traceDir!; 29 | 30 | main() 31 | .then(code => exit(code)) 32 | .catch(err => { 33 | console.error(`Internal Error: ${err.message}`) 34 | exit(2); 35 | }); 36 | 37 | interface Project { 38 | configFilePath?: string; 39 | tracePath: string; 40 | typesPath: string; 41 | } 42 | 43 | interface ProjectResult { 44 | project: Project; 45 | stdout: string; 46 | stderr: string; 47 | exitCode: number | undefined; 48 | signal: NodeJS.Signals | undefined; 49 | } 50 | 51 | async function main(): Promise { 52 | let projects: undefined | Project[]; 53 | 54 | const legendPath = path.join(traceDir, "legend.json"); 55 | if (await isFile(legendPath)) { 56 | try { 57 | const legendText = await fs.promises.readFile(legendPath, { encoding: "utf-8" }); 58 | projects = JSON.parse(legendText); 59 | 60 | for (const project of projects!) { 61 | project.tracePath = path.resolve(traceDir, path.basename(project.tracePath)); 62 | project.typesPath = path.resolve(traceDir, path.basename(project.typesPath)); 63 | } 64 | } 65 | catch (e: any) { 66 | console.error(`Error reading legend file: ${e.message}`); 67 | } 68 | } 69 | 70 | if (!projects) { 71 | projects = []; 72 | 73 | for (const entry of await fs.promises.readdir(traceDir, { withFileTypes: true })) { 74 | if (!entry.isFile()) continue; 75 | 76 | const name = entry.name; 77 | const match = name.match(/^trace(.*\.json)$/); 78 | if (match) { 79 | projects.push({ 80 | tracePath: path.join(traceDir, name), 81 | typesPath: path.join(traceDir, `types${match[1]}`), 82 | }); 83 | } 84 | } 85 | } 86 | 87 | const results = await Promise.all(projects.map(p => limit(analyzeProject, p))); 88 | return argv.json 89 | ? await printResultsAsJson(results) 90 | : await printResultsAsText(results); 91 | } 92 | 93 | async function printResultsAsJson(results: readonly ProjectResult[]): Promise { 94 | let sawHighlights = false; 95 | const hadErrors: ProjectResult[] = []; 96 | const hadNoErrors: (ProjectResult & { highlights: object })[] = []; 97 | for (const result of results) { 98 | if (result.stderr || result.signal) { 99 | hadErrors.push(result); 100 | continue; 101 | } 102 | 103 | if (result.exitCode) { 104 | // 1 just indicates "no highlights" 105 | if (result.exitCode !== 1) { 106 | hadErrors.push(result); 107 | } 108 | continue; 109 | } 110 | 111 | try { 112 | hadNoErrors.push({ ...result, highlights: JSON.parse(result.stdout) }); 113 | sawHighlights = true; 114 | } 115 | catch { 116 | hadErrors.push({ ...result, stderr: "Failed to parse project result JSON" }); 117 | } 118 | } 119 | 120 | const json = { 121 | errors: hadErrors.length === 0 122 | ? undefined 123 | : hadErrors.map(result => ({ 124 | ...result, 125 | stdout: undefined, 126 | stderr: undefined, 127 | exitCode: result.exitCode || undefined, 128 | message: result.stderr, 129 | })), 130 | results: hadNoErrors.map(result => ({ 131 | project: result.project, 132 | highlights: result.highlights, 133 | })), 134 | }; 135 | 136 | console.log(JSON.stringify(json, undefined, 2)); 137 | 138 | return hadErrors.length > 0 139 | ? 2 140 | : sawHighlights 141 | ? 0 142 | : 1; 143 | } 144 | 145 | async function printResultsAsText(results: readonly ProjectResult[]): Promise { 146 | const hadHighlights: (ProjectResult & { score: number })[] = []; 147 | const hadErrors: ProjectResult[] = []; 148 | for (const result of results) { 149 | if (result.stderr || result.signal) { 150 | hadErrors.push(result); 151 | continue; 152 | } 153 | 154 | if (result.exitCode) { 155 | // 1 just indicates "no highlights" 156 | if (result.exitCode !== 1) { 157 | hadErrors.push(result); 158 | } 159 | continue; 160 | } 161 | 162 | // First will be the largest, so only need to match one 163 | const match = result.stdout.match(/\((\d+)[ ]*ms\)/); 164 | const score = match ? +match[1] : 0; // Treat all duplicates as tied for now 165 | hadHighlights.push({...result, score }); 166 | } 167 | 168 | let first = true; 169 | const projectCount = results.length; 170 | 171 | // Break ties with trace paths for determinism 172 | hadHighlights.sort((a, b) => b.score - a.score || a.project.tracePath.localeCompare(b.project.tracePath) ); // Descending 173 | for (const result of hadHighlights) { 174 | if (!first) console.log(); 175 | first = false; 176 | 177 | const project = result.project; 178 | if (projectCount > 1 || project.configFilePath) { 179 | console.log(`Analyzed ${getProjectDescription(project)}`); 180 | } 181 | console.log(result.stdout); 182 | } 183 | 184 | for (const errorResult of hadErrors) { 185 | if (!first) console.log(); 186 | first = false; 187 | 188 | const project = errorResult.project; 189 | console.log(`Error analyzing ${getProjectDescription(project)}`); 190 | if (errorResult.stderr) { 191 | console.log(errorResult.stderr); 192 | } 193 | else if (errorResult.exitCode) { 194 | console.log(`Exited with code ${errorResult.exitCode}`); 195 | } 196 | else if (errorResult.signal) { 197 | console.log(`Terminated with signal ${errorResult.signal}`); 198 | } 199 | } 200 | 201 | const interestingCount = hadHighlights.length + hadErrors.length; 202 | if (interestingCount < projectCount) { 203 | if (!first) console.log(); 204 | first = false; 205 | 206 | console.log(`Found nothing in ${projectCount - interestingCount}${interestingCount ? " other" : ""} project(s)`); 207 | } 208 | 209 | return hadErrors.length > 0 210 | ? 2 211 | : hadHighlights.length > 0 212 | ? 0 213 | : 1; 214 | } 215 | 216 | function getProjectDescription(project: Project) { 217 | return project.configFilePath 218 | ? `${project.configFilePath} (${path.basename(project.tracePath)})` 219 | : path.basename(project.tracePath); 220 | } 221 | 222 | async function analyzeProject(project: Project): Promise { 223 | const args = [ project.tracePath ]; 224 | if (await isFile(project.typesPath)) { 225 | args.push(project.typesPath); 226 | } 227 | pushCommandLineOptions(args, argv); 228 | 229 | return new Promise(resolve => { 230 | const child = cp.fork(path.join(__dirname, "analyze-trace-file"), args, { stdio: "pipe" }); 231 | 232 | let stdout = ""; 233 | let stderr = ""; 234 | 235 | child.stdout!.on("data", chunk => stdout += chunk); 236 | child.stderr!.on("data", chunk => stderr += chunk); 237 | 238 | child.on("exit", (code, signal) => { 239 | resolve({ 240 | project, 241 | stdout: stdout.trim(), 242 | stderr: stderr.trim(), 243 | exitCode: code ?? undefined, 244 | signal: signal ?? undefined, 245 | }); 246 | }); 247 | }); 248 | } 249 | 250 | function isFile(path: string): Promise { 251 | return fs.promises.stat(path).then(stats => stats.isFile()).catch(_ => false); 252 | } 253 | 254 | function throwIfNotDirectory(path: string): string { 255 | if (!fs.existsSync(path) || !fs.statSync(path)?.isDirectory()) { 256 | throw new Error(`${path} is not a directory`); 257 | } 258 | return path; 259 | } -------------------------------------------------------------------------------- /src/analyze-trace-file.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import fs = require("fs"); 5 | import exit = require("exit"); 6 | import yargs = require("yargs"); 7 | 8 | import { commandLineOptions, checkCommandLineOptions } from "./analyze-trace-options"; 9 | import { reportHighlights as reportText } from "./print-trace-analysis-text"; 10 | import { reportHighlights as reportJson } from "./print-trace-analysis-json"; 11 | 12 | const argv = yargs(process.argv.slice(2)) 13 | .command("$0 [typesPath]", "Preprocess tracing type dumps", yargs => yargs 14 | .positional("tracePath", { type: "string", desc: "Trace file to read", coerce: throwIfNotFile }) 15 | .positional("typesPath", { type: "string", desc: "Corresponding types file", coerce: throwIfNotFile }) 16 | .options(commandLineOptions) 17 | .check(checkCommandLineOptions) 18 | .help("h").alias("h", "help") 19 | .epilog("Exits with code 0 if highlights were found, 1 if no highlights were found, and 2 if an error occurred") 20 | .strict()) 21 | .argv; 22 | 23 | 24 | const tracePath = argv.tracePath!; 25 | const typesPath = argv.typesPath; 26 | 27 | const thresholdDuration = argv.forceMillis * 1000; // microseconds 28 | const minDuration = argv.skipMillis * 1000; // microseconds 29 | const minPercentage = 0.6; 30 | const importExpressionThreshold = 10; 31 | 32 | const reportHighlights = argv.json ? reportJson : reportText; 33 | 34 | reportHighlights(tracePath, argv.expandTypes ? typesPath : undefined, thresholdDuration, minDuration, minPercentage, importExpressionThreshold) 35 | .then(found => exit(found ? 0 : 1)) 36 | .catch(err => { 37 | console.error(`Internal Error: ${err.message}\n${err.stack}`) 38 | exit(2); 39 | }); 40 | 41 | function throwIfNotFile(path: string): string { 42 | if (!fs.existsSync(path) || !fs.statSync(path)?.isFile()) { 43 | throw new Error(`${path} is not a file`); 44 | } 45 | return path; 46 | } -------------------------------------------------------------------------------- /src/analyze-trace-options.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | export const commandLineOptions = { 5 | "forceMillis": { 6 | alias: ["forcemillis", "force-millis"], 7 | describe: "Events of at least this duration (in milliseconds) will reported unconditionally", 8 | type: "number", 9 | default: 500, 10 | }, 11 | "skipMillis": { 12 | alias: ["skipmillis", "skip-millis"], 13 | describe: "Events of less than this duration (in milliseconds) will suppressed unconditionally", 14 | type: "number", 15 | default: 100, 16 | }, 17 | "expandTypes": { 18 | alias: ["expandtypes", "expand-types"], 19 | describe: "Expand types when printing", 20 | type: "boolean", 21 | default: true, 22 | }, 23 | "color": { 24 | describe: "Color the output to make it easier to read", 25 | type: "boolean", 26 | default: true, 27 | }, 28 | "json": { 29 | describe: "Produce JSON output for programmatic consumption (EXPERIMENTAL)", 30 | type: "boolean", 31 | default: false, 32 | }, 33 | } as const; 34 | 35 | // Replicating the type inference in yargs would be excessive 36 | type Argv = { 37 | forceMillis: number, 38 | skipMillis: number, 39 | expandTypes: boolean, 40 | color: boolean, 41 | json: boolean, 42 | }; 43 | 44 | export function checkCommandLineOptions(argv: Argv): true { 45 | if (argv.forceMillis < argv.skipMillis) { 46 | throw new Error("forceMillis cannot be less than skipMillis") 47 | } 48 | return true; 49 | } 50 | 51 | export function pushCommandLineOptions(array: string[], argv: Argv): void { 52 | array.push( 53 | "--force-millis", `${argv.forceMillis}`, 54 | "--skip-millis", `${argv.skipMillis}`, 55 | argv.expandTypes ? "--expand-types" : "--no-expand-types", 56 | argv.color ? "--color" : "--no-color", 57 | argv.json ? "--json" : "--no-json", 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/analyze-trace-utilities.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import fs = require("fs"); 5 | import path = require("path"); 6 | import countImportExpressions = require("./count-import-expressions"); 7 | import normalizePositions = require("./normalize-positions"); 8 | import simplify = require("./simplify-type"); 9 | import { EventSpan, ParseResult } from "./parse-trace-file"; 10 | 11 | import jsonstream = require("jsonstream-next"); 12 | 13 | export function buildHotPathsTree(parseResult: ParseResult, thresholdDuration: number, minPercentage: number): EventSpan { 14 | const { minTime, maxTime, spans, unclosedStack } = parseResult; 15 | 16 | for (let i = unclosedStack.length - 1; i >= 0; i--) { 17 | const event = unclosedStack[i]; 18 | spans.push({ event, start: +event.ts, end: maxTime, children: [] }); 19 | } 20 | 21 | spans.sort((a, b) => a.start - b.start); 22 | 23 | const root: EventSpan = { start: minTime, end: maxTime, children: [] }; 24 | const stack = [ root ]; 25 | 26 | for (const span of spans) { 27 | let i = stack.length - 1; 28 | for (; i > 0; i--) { // No need to check root at stack[0] 29 | const curr = stack[i]; 30 | if (curr.end > span.start) { 31 | // Pop down to parent 32 | stack.length = i + 1; 33 | break; 34 | } 35 | } 36 | 37 | const parent = stack[i]; 38 | const duration = span.end - span.start; 39 | if (duration >= thresholdDuration || duration >= minPercentage * (parent.end - parent.start)) { 40 | parent.children.push(span); 41 | stack.push(span); 42 | } 43 | } 44 | 45 | return root; 46 | } 47 | 48 | type LineChar = normalizePositions.LineChar; 49 | export type PositionMap = Map>; // Path to position (offset or LineChar) to LineChar 50 | 51 | export async function getNormalizedPositions(root: EventSpan, relatedTypes: Map | undefined): Promise { 52 | const positionMap = new Map(); 53 | recordPositions(root, /*currentFile*/ undefined); 54 | if (relatedTypes) { 55 | for (const type of relatedTypes.values()) { 56 | const location: any = (type as any).location; 57 | if (location) { 58 | recordPosition(location.path, [ location.line, location.char ]); 59 | } 60 | } 61 | } 62 | 63 | const map = new Map>(); // NB: can't use LineChar as map key 64 | for (const entry of Array.from(positionMap.entries())) { 65 | try { 66 | const path = entry[0]; 67 | const sourceStream = fs.createReadStream(path, { encoding: "utf-8" }); 68 | 69 | const rawPositions = entry[1]; 70 | const normalizedPositions = await normalizePositions(sourceStream, rawPositions); 71 | 72 | const pathMap = new Map(); 73 | for (let i = 0; i < rawPositions.length; i++) { 74 | const rawPosition = rawPositions[i]; 75 | const key = typeof rawPosition === "number" ? Math.abs(rawPosition).toString() : getLineCharMapKey(...rawPosition as LineChar); 76 | pathMap.set(key, normalizedPositions[i]); 77 | } 78 | 79 | map.set(path, pathMap); 80 | } catch { 81 | // Not finding a file is expected if this isn't the box on which the trace was recorded. 82 | } 83 | } 84 | 85 | return map; 86 | 87 | function recordPositions(span: EventSpan, currentFile: string | undefined): void { 88 | if (span.event?.name === "checkSourceFile") { 89 | currentFile = span.event!.args!.path; 90 | } 91 | else if (span.event?.cat === "check") { 92 | const args = span.event.args; 93 | currentFile = args?.path ?? currentFile; 94 | if (currentFile) { 95 | if (args?.pos) { 96 | recordPosition(currentFile, args.pos); 97 | } 98 | if (args?.end) { 99 | recordPosition(currentFile, -args.end); // Negative since end should not be moved past trivia 100 | } 101 | } 102 | } 103 | 104 | for (const child of span.children) { 105 | recordPositions(child, currentFile); 106 | } 107 | } 108 | 109 | function recordPosition(path: string, position: number | LineChar): void { 110 | if (!positionMap.has(path)) { 111 | positionMap.set(path, []); 112 | } 113 | 114 | positionMap.get(path)!.push(position); 115 | } 116 | } 117 | 118 | export function getLineCharMapKey(line: number, char: number) { 119 | return `${line},${char}`; 120 | } 121 | 122 | export async function getPackageVersion(packagePath: string): Promise { 123 | try { 124 | const jsonPath = path.join(packagePath, "package.json"); 125 | const jsonString = await fs.promises.readFile(jsonPath, { encoding: "utf-8" }); 126 | const jsonObj = JSON.parse(jsonString); 127 | return jsonObj.version; 128 | } 129 | catch { 130 | } 131 | 132 | return undefined; 133 | } 134 | 135 | export function unmangleCamelCase(name: string) { 136 | let result = ""; 137 | for (const char of [...name]) { 138 | if (!result.length) { 139 | result += char.toLocaleUpperCase(); 140 | continue; 141 | } 142 | 143 | const lower = char.toLocaleLowerCase(); 144 | if (char !== lower) { 145 | result += " "; 146 | } 147 | 148 | result += lower; 149 | } 150 | return result; 151 | } 152 | 153 | let typesCache: undefined | readonly any[]; 154 | export async function getTypes(typesPath: string): Promise { 155 | if (!typesCache) { 156 | return new Promise((resolve, _reject) => { 157 | typesCache = []; 158 | 159 | const readStream = fs.createReadStream(typesPath, { encoding: "utf-8" }); 160 | readStream.on("end", () => { 161 | resolve(typesCache!); 162 | }); 163 | readStream.on("error", onError); 164 | 165 | // expects types file to be {object[]} 166 | const parser = jsonstream.parse("*"); 167 | parser.on("data", (data: object) => { 168 | (typesCache as any[]).push(data); 169 | }); 170 | parser.on("error", onError); 171 | 172 | readStream.pipe(parser); 173 | 174 | function onError(e: Error) { 175 | console.error(`Error reading types file: ${e.message}`); 176 | resolve(typesCache!); 177 | } 178 | }); 179 | } 180 | 181 | return typesCache; 182 | } 183 | 184 | export interface EmittedImport { 185 | name: string; 186 | count: number; 187 | } 188 | 189 | export async function getEmittedImports(dtsPath: string, importExpressionThreshold: number): Promise { 190 | const sourceStream = fs.createReadStream(dtsPath, { encoding: "utf-8" }); 191 | const frequency = await countImportExpressions(sourceStream); 192 | const sorted = Array.from(frequency.entries()) 193 | .sort(([import1, count1], [import2, count2]) => count2 - count1 || import1.localeCompare(import2)) 194 | .filter(([_, count]) => count >= importExpressionThreshold) 195 | .map(([name, count]) => ({ name, count })); 196 | return sorted; 197 | } 198 | 199 | export async function getRelatedTypes(root: EventSpan, typesPath: string, leafOnly: boolean): Promise> { 200 | const relatedTypes = new Map(); 201 | 202 | const stack: EventSpan[] = []; 203 | stack.push(root); 204 | 205 | while(stack.length) { 206 | const curr = stack.pop()!; 207 | if (!leafOnly || curr.children.length === 0) { 208 | if (curr.event?.name === "structuredTypeRelatedTo") { 209 | const types = await getTypes(typesPath); 210 | if (types.length) { 211 | addRelatedTypes(types, curr.event.args!.sourceId, relatedTypes); 212 | addRelatedTypes(types, curr.event.args!.targetId, relatedTypes); 213 | } 214 | } 215 | else if (curr.event?.name === "getVariancesWorker") { 216 | const types = await getTypes(typesPath); 217 | if (types.length) { 218 | addRelatedTypes(types, curr.event.args!.id, relatedTypes); 219 | } 220 | } 221 | } 222 | 223 | stack.push(...curr.children); // Order doesn't matter during this traversal 224 | } 225 | 226 | return relatedTypes; 227 | } 228 | 229 | function addRelatedTypes(types: readonly object[], id: number, relatedTypes: Map): void { 230 | worker(id); 231 | 232 | function worker(id: any): void { 233 | if (typeof id !== "number") return; 234 | const type: any = types[id - 1]; 235 | if (!type) return; 236 | 237 | // If there's a cycle, suppress the children, but not the type itself 238 | if (!relatedTypes.has(id)) { 239 | relatedTypes.set(id, simplify(type)); 240 | 241 | for (const prop in type) { 242 | if (prop.match(/type/i)) { 243 | if (Array.isArray(type[prop])) { 244 | for (const t of type[prop]) { 245 | worker(t); 246 | } 247 | } 248 | else { 249 | worker(type[prop]); 250 | } 251 | } 252 | } 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/count-import-expressions.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import stream = require("stream"); 5 | import TriviaStateMachine = require("./trivia-state-machine"); 6 | 7 | /** 8 | * @param stream A stream of string chunks with respect to which `positions` should be normalized. 9 | * @returns A frequency table of imported module names. 10 | */ 11 | function countImportExpressions(stream: stream.Readable): Promise> { 12 | return new Promise>((resolve, reject) => { 13 | let prevCh = -1; 14 | 15 | stream.on("error", err => reject(err)); 16 | 17 | // The actual event handling is in onChar and onEof below. 18 | // These handlers provided a simplified current- and next-char view. 19 | stream.on("data", chunk => { 20 | const text = chunk as string; 21 | const length = text.length; 22 | for (let i = 0; i < length; i++) { 23 | const ch = text.charCodeAt(i); 24 | if (prevCh >= 0) { 25 | onChar(prevCh, ch); 26 | } 27 | prevCh = ch; 28 | } 29 | }); 30 | 31 | stream.on("close", () => { 32 | if (prevCh >= 0) { 33 | onChar(prevCh, -1); 34 | } 35 | 36 | onEof(); 37 | }); 38 | 39 | const target = "import("; // tsc doesn't emit a space before the lparen 40 | let targetPos = 0; 41 | 42 | const buf: number[] = []; 43 | 44 | const frequency = new Map(); 45 | 46 | let stateMachine = TriviaStateMachine.create(); 47 | 48 | function onChar(ch: number, nextCh: number) { 49 | const { charKind } = stateMachine.step(ch, nextCh); 50 | 51 | if (targetPos === target.length) { 52 | if (charKind === "string") { 53 | buf.push(ch); 54 | } 55 | else { 56 | const name = String.fromCharCode(...buf); 57 | 58 | if (!frequency.has(name)) { 59 | frequency.set(name, 0); 60 | } 61 | frequency.set(name, frequency.get(name)! + 1); 62 | 63 | targetPos = 0; 64 | buf.length = 0; 65 | } 66 | } 67 | else if (charKind === "code" && ch === target.charCodeAt(targetPos)) { 68 | targetPos++; 69 | } 70 | else { 71 | targetPos = 0; 72 | } 73 | } 74 | 75 | function onEof() { 76 | resolve(frequency); 77 | } 78 | }); 79 | } 80 | 81 | export = countImportExpressions; -------------------------------------------------------------------------------- /src/get-type-tree.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import simplify = require("./simplify-type"); 5 | 6 | function getTypeTree(id: number, simplifiedTypes: Map, types?: readonly object[]): object { 7 | const tree = {}; 8 | addTypeToTree(tree, id, []); 9 | return tree; 10 | 11 | function addTypeToTree(tree: {}, id: any, ancestorIds: any[]): void { 12 | if (typeof id !== "number") return; 13 | let type = simplifiedTypes.get(id); 14 | if (!type) { 15 | type = types && simplify(types[id - 1]); 16 | if (!type) return; 17 | simplifiedTypes.set(id, type); 18 | } 19 | 20 | const children = {}; 21 | 22 | // If there's a cycle, suppress the children, but not the type itself 23 | if (ancestorIds.indexOf(id) < 0) { 24 | ancestorIds.push(id); 25 | 26 | for (const prop in type) { 27 | if (prop.match(/type/i)) { 28 | if (Array.isArray(type[prop])) { 29 | for (const t of type[prop]) { 30 | addTypeToTree(children, t, ancestorIds); 31 | } 32 | } 33 | else { 34 | addTypeToTree(children, type[prop], ancestorIds); 35 | } 36 | } 37 | } 38 | 39 | ancestorIds.pop(); 40 | } 41 | 42 | tree[JSON.stringify(type)] = children; 43 | } 44 | } 45 | 46 | export = getTypeTree; -------------------------------------------------------------------------------- /src/normalize-positions.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import stream = require("stream"); 5 | import TriviaStateMachine = require("./trivia-state-machine"); 6 | 7 | namespace normalizePositions { 8 | export type LineChar = readonly [line: number, char: number]; 9 | } 10 | 11 | type MutableLineChar = [line: number, char: number]; 12 | type LineChar = normalizePositions.LineChar; 13 | 14 | interface PositionWrapper { 15 | returnIndex: number; 16 | offset?: number; 17 | lineChar?: LineChar; 18 | } 19 | 20 | let freezeCache: LineChar = [-1, -1]; 21 | function freeze(lineChar: MutableLineChar): LineChar { 22 | if (lineChar[0] !== freezeCache[0] || lineChar[1] !== freezeCache[1]) { 23 | freezeCache = [lineChar[0], lineChar[1]]; 24 | } 25 | 26 | return freezeCache; 27 | } 28 | 29 | function compareOffsets(a: number, b: number): number { 30 | return a - b; 31 | } 32 | 33 | function compareLineChars(a: LineChar, b: LineChar): number { 34 | return a[0] - b[0] || a[1] - b[1]; 35 | } 36 | 37 | /** 38 | * @param stream A stream of string chunks with respect to which `positions` should be normalized. 39 | * @param positions Positions to be normalized. NB: negative numbers will be treated as positions 40 | * that should be converted to line-char without but not moved past trivia. 41 | * @returns A list of normalized line-char positions. 42 | */ 43 | function normalizePositions(stream: stream.Readable, positions: ReadonlyArray): Promise> { 44 | return positions.length === 0 ? Promise.resolve([]) : new Promise>((resolve, reject) => { 45 | let prevCh = -1; 46 | 47 | stream.on("error", err => reject(err)); 48 | 49 | // The actual event handling is in onChar and onEof below. 50 | // These handlers provided a simplified current- and next-char view. 51 | stream.on("data", chunk => { 52 | const text = chunk as string; 53 | const length = text.length; 54 | for (let i = 0; i < length; i++) { 55 | const ch = text.charCodeAt(i); 56 | if (prevCh >= 0) { 57 | onChar(prevCh, ch); 58 | } 59 | prevCh = ch; 60 | } 61 | }); 62 | 63 | stream.on("close", () => { 64 | if (prevCh >= 0) { 65 | onChar(prevCh, -1); 66 | } 67 | 68 | onEof(); 69 | }); 70 | 71 | // We partition the positions because the varieties 72 | // cannot be sorted with respect to each other. 73 | const fixedOffsetWrappers: PositionWrapper[] = []; // Don't skip trivia 74 | const offsetWrappers: PositionWrapper[] = []; // Do skip trivia 75 | const fixedLineCharWrappers: PositionWrapper[] = []; // Don't skip trivia 76 | const lineCharWrappers: PositionWrapper[] = []; // Do skip trivia 77 | 78 | for (let i = 0; i < positions.length; i++) { 79 | const position = positions[i]; 80 | if (typeof position === "number") { 81 | if (position < 0) { 82 | fixedOffsetWrappers.push({ 83 | returnIndex: i, 84 | offset: -position, 85 | }); 86 | } 87 | else { 88 | offsetWrappers.push({ 89 | returnIndex: i, 90 | offset: position, 91 | }); 92 | } 93 | } 94 | else { 95 | if (position[0] < 0 || position[1] < 0) { 96 | fixedLineCharWrappers.push({ 97 | returnIndex: i, 98 | lineChar: [Math.abs(position[0]), Math.abs(position[1])], 99 | }); 100 | } 101 | else { 102 | lineCharWrappers.push({ 103 | returnIndex: i, 104 | lineChar: position, 105 | }); 106 | } 107 | } 108 | } 109 | 110 | fixedOffsetWrappers.sort((a, b) => compareOffsets(a.offset!, b.offset!)); 111 | offsetWrappers.sort((a, b) => compareOffsets(a.offset!, b.offset!)); 112 | fixedLineCharWrappers.sort((a, b) => compareLineChars(a.lineChar!, b.lineChar!)); 113 | lineCharWrappers.sort((a, b) => compareLineChars(a.lineChar!, b.lineChar!)); 114 | 115 | let fixedOffsetWrappersPos = 0; 116 | let offsetWrappersPos = 0; 117 | let fixedLineCharWrappersPos = 0; 118 | let lineCharWrappersPos = 0; 119 | 120 | let currOffset = 0; 121 | let currLineChar: MutableLineChar = [1, 1]; 122 | let stateMachine = TriviaStateMachine.create(); 123 | 124 | function onChar(ch: number, nextCh: number) { 125 | const { charKind, wrapLine } = stateMachine.step(ch, nextCh); 126 | const isTrivia = charKind === "comment" || charKind === "whitespace"; 127 | 128 | // This is handy when debugging 129 | // console.error(`${currOffset}\t${/^[a-zA-Z0-9!@#$%^&*()[\]{}\\/;':"<,>.?`~+=_\-]$/.test(String.fromCharCode(ch)) ? String.fromCharCode(ch) : "0x" + ch.toString(16)}\t(${currLineChar[0]},${currLineChar[1]})\t${charKind}`); 130 | 131 | for (; fixedOffsetWrappersPos < fixedOffsetWrappers.length && compareOffsets(fixedOffsetWrappers[fixedOffsetWrappersPos].offset!, currOffset) <= 0; fixedOffsetWrappersPos++) { 132 | fixedOffsetWrappers[fixedOffsetWrappersPos].offset = currOffset; 133 | fixedOffsetWrappers[fixedOffsetWrappersPos].lineChar = freeze(currLineChar); 134 | } 135 | 136 | for (; fixedLineCharWrappersPos < fixedLineCharWrappers.length && compareLineChars(fixedLineCharWrappers[fixedLineCharWrappersPos].lineChar!, currLineChar) <= 0; fixedLineCharWrappersPos++) { 137 | fixedLineCharWrappers[fixedLineCharWrappersPos].offset = currOffset; 138 | fixedLineCharWrappers[fixedLineCharWrappersPos].lineChar = freeze(currLineChar); 139 | } 140 | 141 | if (!isTrivia) { 142 | for (; offsetWrappersPos < offsetWrappers.length && compareOffsets(offsetWrappers[offsetWrappersPos].offset!, currOffset) <= 0; offsetWrappersPos++) { 143 | offsetWrappers[offsetWrappersPos].offset = currOffset; 144 | offsetWrappers[offsetWrappersPos].lineChar = freeze(currLineChar); 145 | } 146 | 147 | for (; lineCharWrappersPos < lineCharWrappers.length && compareLineChars(lineCharWrappers[lineCharWrappersPos].lineChar!, currLineChar) <= 0; lineCharWrappersPos++) { 148 | lineCharWrappers[lineCharWrappersPos].offset = currOffset; 149 | lineCharWrappers[lineCharWrappersPos].lineChar = freeze(currLineChar); 150 | } 151 | } 152 | 153 | currOffset++; 154 | 155 | if (wrapLine) { 156 | currLineChar[0]++; 157 | currLineChar[1] = 1; 158 | } 159 | else { 160 | currLineChar[1]++; 161 | } 162 | 163 | // TODO (https://github.com/microsoft/typescript-analyze-trace/issues/4) 164 | } 165 | 166 | function onEof() { 167 | const result: LineChar[] = []; 168 | 169 | const eofLineChar = freeze(currLineChar); 170 | 171 | for (let i = 0; i < fixedOffsetWrappersPos; i++) { 172 | const wrapper = fixedOffsetWrappers[i]; 173 | result[wrapper.returnIndex] = wrapper.lineChar!; 174 | } 175 | 176 | for (let i = fixedOffsetWrappersPos; i < fixedOffsetWrappers.length; i++) { 177 | const wrapper = fixedOffsetWrappers[i]; 178 | result[wrapper.returnIndex] = eofLineChar; 179 | } 180 | 181 | for (let i = 0; i < offsetWrappersPos; i++) { 182 | const wrapper = offsetWrappers[i]; 183 | result[wrapper.returnIndex] = wrapper.lineChar!; 184 | } 185 | 186 | for (let i = offsetWrappersPos; i < offsetWrappers.length; i++) { 187 | const wrapper = offsetWrappers[i]; 188 | result[wrapper.returnIndex] = eofLineChar; 189 | } 190 | 191 | for (let i = 0; i < fixedLineCharWrappersPos; i++) { 192 | const wrapper = fixedLineCharWrappers[i]; 193 | result[wrapper.returnIndex] = wrapper.lineChar!; 194 | } 195 | 196 | for (let i = fixedLineCharWrappersPos; i < fixedLineCharWrappers.length; i++) { 197 | const wrapper = fixedLineCharWrappers[i]; 198 | result[wrapper.returnIndex] = eofLineChar; 199 | } 200 | 201 | for (let i = 0; i < lineCharWrappersPos; i++) { 202 | const wrapper = lineCharWrappers[i]; 203 | result[wrapper.returnIndex] = wrapper.lineChar!; 204 | } 205 | 206 | for (let i = lineCharWrappersPos; i < lineCharWrappers.length; i++) { 207 | const wrapper = lineCharWrappers[i]; 208 | result[wrapper.returnIndex] = eofLineChar; 209 | } 210 | 211 | resolve(result); 212 | } 213 | }); 214 | } 215 | 216 | export = normalizePositions; -------------------------------------------------------------------------------- /src/parse-trace-file.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import fs = require("fs"); 5 | 6 | const Parser = require("jsonparse"); 7 | 8 | const packageNameRegex = /\/node_modules\/((?:[^@][^/]+)|(?:@[^/]+\/[^/]+))/g; 9 | 10 | export interface Event { 11 | ph: string; 12 | ts: string; 13 | dur?: string; 14 | name: string; 15 | cat: string; 16 | args?: any; 17 | } 18 | 19 | export interface EventSpan { 20 | event?: Event; 21 | start: number; 22 | end: number; 23 | children: EventSpan[]; 24 | } 25 | 26 | export interface ParseResult { 27 | minTime: number; 28 | maxTime: number; 29 | spans: EventSpan[]; 30 | unclosedStack: Event[]; 31 | nodeModulePaths: Map; 32 | } 33 | 34 | export function parse(tracePath: string, minDuration: number): Promise { 35 | return new Promise(resolve => { 36 | const p = new Parser(); 37 | 38 | let minTime = Infinity; 39 | let maxTime = 0; 40 | const unclosedStack: Event[] = []; // Sorted in increasing order of start time (even when below timestamp resolution) 41 | const spans: EventSpan[] = []; // Sorted in increasing order of end time, then increasing order of start time (even when below timestamp resolution) 42 | const nodeModulePaths = new Map(); 43 | p.onValue = function (value: any) { 44 | if (this.stack.length !== 1) return; 45 | if (this.mode !== Parser.C.ARRAY) throw new Error(`Unexpected mode ${this.mode}`); 46 | this.value = []; 47 | 48 | // Metadata objects are uninteresting 49 | if (value.ph === "M") return; 50 | 51 | // TODO (https://github.com/microsoft/typescript-analyze-trace/issues/1) 52 | if (value.ph === "i" || value.ph === "I") return; 53 | 54 | const event = value as Event; 55 | 56 | if (event.ph === "B") { 57 | unclosedStack.push(event); 58 | return; 59 | } 60 | 61 | let span: EventSpan; 62 | if (event.ph === "E") { 63 | const beginEvent = unclosedStack.pop()!; 64 | span = { event: beginEvent, start: +beginEvent.ts, end: +event.ts, children: [] }; 65 | } 66 | else if (event.ph === "X") { 67 | const start = +event.ts; 68 | const duration = +event.dur!; 69 | span = { event, start, end: start + duration, children: [] } 70 | } 71 | else { 72 | throw new Error(`Unknown event phase ${event.ph}`); 73 | } 74 | 75 | minTime = Math.min(minTime, span.start); 76 | maxTime = Math.max(maxTime, span.end); 77 | 78 | // Note that we need to do this before events are being dropped based on `minDuration` 79 | if (span.event!.name === "findSourceFile") { 80 | const path = span.event!.args?.fileName; 81 | if (path) { 82 | while (true) { 83 | const m = packageNameRegex.exec(path); 84 | if (!m) break; 85 | const packageName = m[1]; 86 | const packagePath = m.input.substring(0, m.index + m[0].length); 87 | if (nodeModulePaths.has(packageName)) { 88 | const paths = nodeModulePaths.get(packageName); 89 | if (paths!.indexOf(packagePath) < 0) { // Usually contains exactly one element 90 | paths!.push(packagePath); 91 | } 92 | } 93 | else { 94 | nodeModulePaths.set(packageName, [ packagePath ]); 95 | } 96 | } 97 | } 98 | } 99 | 100 | if ((span.end - span.start) >= minDuration) { 101 | spans.push(span); 102 | } 103 | } 104 | 105 | const readStream = fs.createReadStream(tracePath); 106 | readStream.on("data", chunk => p.write(chunk)); 107 | readStream.on("end", () => { 108 | resolve({ 109 | minTime, 110 | maxTime, 111 | spans, 112 | unclosedStack, 113 | nodeModulePaths, 114 | }); 115 | }); 116 | }); 117 | } -------------------------------------------------------------------------------- /src/print-trace-analysis-json.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import { assert } from "console"; 5 | import fs = require("fs"); 6 | import path = require("path"); 7 | 8 | import { EventSpan, parse } from "./parse-trace-file"; 9 | import { buildHotPathsTree, EmittedImport, getEmittedImports, getLineCharMapKey, getNormalizedPositions, getPackageVersion, getRelatedTypes, getTypes, PositionMap, unmangleCamelCase } from "./analyze-trace-utilities"; 10 | 11 | export async function reportHighlights( 12 | tracePath: string, 13 | typesPath: string | undefined, 14 | thresholdDuration: number, 15 | minDuration: number, 16 | minPercentage: number, 17 | importExpressionThreshold: number): Promise { 18 | 19 | const parseResult = await parse(tracePath, minDuration); 20 | 21 | const root = buildHotPathsTree(parseResult, thresholdDuration, minPercentage); 22 | 23 | const result = {}; 24 | 25 | const unclosedEvents = parseResult.unclosedStack; 26 | unclosedEvents.reverse(); 27 | result["unterminatedEvents"] = undefinedIfEmpty(unclosedEvents); 28 | 29 | const hotSpots = await getHotSpots(root, importExpressionThreshold, typesPath); 30 | result["hotSpots"] = undefinedIfEmpty(hotSpots); 31 | 32 | const duplicatePackages = await getDuplicateNodeModules(parseResult.nodeModulePaths); 33 | result["duplicatePackages"] = undefinedIfEmpty(duplicatePackages); 34 | 35 | console.log(JSON.stringify(result, undefined, 2)); 36 | 37 | return !!hotSpots.length || !!duplicatePackages.length; 38 | } 39 | 40 | interface DuplicatedPackage { 41 | name: string, 42 | instances: DuplicatedPackageInstance[], 43 | } 44 | 45 | interface DuplicatedPackageInstance { 46 | path: string, 47 | version?: string, 48 | } 49 | 50 | async function getDuplicateNodeModules(nodeModulePaths: Map): Promise { 51 | const duplicates: DuplicatedPackage[] = []; 52 | for (const [packageName, packagePaths] of nodeModulePaths.entries()) { 53 | if (packagePaths.length < 2) continue; 54 | const instances: DuplicatedPackageInstance[] = []; 55 | for (const packagePath of packagePaths) { 56 | instances.push({ 57 | path: packagePath, 58 | version: await getPackageVersion(packagePath), 59 | }); 60 | } 61 | duplicates.push({ 62 | name: packageName, 63 | instances, 64 | }); 65 | } 66 | 67 | return duplicates; 68 | } 69 | 70 | interface HotFrame { 71 | description: string; 72 | timeMs: number; 73 | path?: string; 74 | startLine?: number; 75 | startChar?: number; 76 | startOffset?: number; 77 | endLine?: number; 78 | endChar?: number; 79 | endOffset?: number; 80 | types?: HotType[]; 81 | children: HotFrame[]; 82 | emittedImports?: EmittedImport[]; 83 | } 84 | 85 | interface HotType { 86 | type: object; 87 | children: HotType[]; 88 | } 89 | 90 | async function getHotSpots(root: EventSpan, importExpressionThreshold: number, typesPath: string | undefined): Promise { 91 | const relatedTypes = typesPath ? await getRelatedTypes(root, typesPath, /*leafOnly*/ false) : undefined; 92 | const positionMap = await getNormalizedPositions(root, relatedTypes); 93 | const types = typesPath ? await getTypes(typesPath) : undefined; 94 | return await getHotSpotsWorker(root, /*currentFile*/ undefined, positionMap, relatedTypes, importExpressionThreshold); 95 | } 96 | 97 | async function getHotSpotsWorker(curr: EventSpan, currentFile: string | undefined, positionMap: PositionMap, relatedTypes: Map | undefined, importExpressionThreshold: number): Promise { 98 | if (curr.event?.cat === "check") { 99 | const path = curr.event.args!.path; 100 | if (path) { 101 | currentFile = path; 102 | } 103 | else { 104 | assert(curr.event?.name !== "checkSourceFile", "checkSourceFile should have a path"); 105 | } 106 | } 107 | 108 | const timeMs = Math.round((curr.end - curr.start) / 1000); 109 | const children: HotFrame[] = []; 110 | if (curr.children.length) { 111 | // Sort slow to fast 112 | const sortedChildren = curr.children.sort((a, b) => (b.end - b.start) - (a.end - a.start)); 113 | for (const child of sortedChildren) { 114 | children.push(...await getHotSpotsWorker(child, currentFile, positionMap, relatedTypes, importExpressionThreshold)); 115 | } 116 | } 117 | 118 | if (curr.event) { 119 | const hotFrame = await makeHotFrame(); 120 | if (hotFrame) { 121 | return [hotFrame]; 122 | } 123 | } 124 | 125 | return children; 126 | 127 | async function makeHotFrame(): Promise { 128 | const event = curr.event!; 129 | switch (event.name) { 130 | // case "findSourceFile": 131 | // TODO (https://github.com/microsoft/typescript-analyze-trace/issues/2) 132 | case "emitDeclarationFileOrBundle": 133 | const dtsPath = event.args.declarationFilePath; 134 | if (!dtsPath || !fs.existsSync(dtsPath)) { 135 | return undefined; 136 | } 137 | try { 138 | const emittedImports = await getEmittedImports(dtsPath, importExpressionThreshold); 139 | if (emittedImports.length === 0) { 140 | return undefined; 141 | } 142 | return { 143 | description: `Emit declarations file`, 144 | timeMs, 145 | path: formatPath(dtsPath), 146 | children, 147 | emittedImports, 148 | }; 149 | } 150 | catch { 151 | return undefined; 152 | } 153 | case "checkSourceFile": 154 | return { 155 | description: `Check file ${formatPath(currentFile!)}`, 156 | timeMs, 157 | path: formatPath(currentFile!), 158 | children, 159 | }; 160 | case "structuredTypeRelatedTo": 161 | const args = event.args!; 162 | return { 163 | description: `Compare types ${args.sourceId} and ${args.targetId}`, 164 | timeMs, 165 | children, 166 | types: relatedTypes ? [ getHotType(args.sourceId), getHotType(args.targetId) ] : undefined, 167 | }; 168 | case "getVariancesWorker": 169 | return { 170 | description: `Determine variance of type ${event.args!.id}`, 171 | timeMs, 172 | children, 173 | types: relatedTypes ? [ getHotType(event.args!.id) ] : undefined, 174 | }; 175 | default: 176 | if (event.cat === "check" && event.args && event.args.pos && event.args.end) { 177 | const frame: HotFrame = { 178 | description: unmangleCamelCase(event.name), 179 | timeMs, 180 | path: formatPath(currentFile!), 181 | children: undefined as any, 182 | }; 183 | if (positionMap.has(currentFile!)) { 184 | const updatedPos = positionMap.get(currentFile!)!.get(event.args.pos.toString())!; 185 | const updatedEnd = positionMap.get(currentFile!)!.get(event.args.end.toString())!; 186 | frame.startLine = updatedPos[0]; 187 | frame.startChar = updatedPos[1]; 188 | frame.endLine = updatedEnd[0]; 189 | frame.endChar = updatedEnd[1]; 190 | } 191 | else { 192 | frame.startOffset = event.args.pos; 193 | frame.endOffset = event.args.end; 194 | } 195 | // Hack to print the children last for readability 196 | delete (frame as any).children; 197 | frame.children = children; 198 | return frame; 199 | } 200 | return undefined; 201 | } 202 | } 203 | 204 | function getHotType(id: number): HotType { 205 | return worker(id, [])!; 206 | 207 | function worker(id: any, ancestorIds: any[]): HotType | undefined { 208 | if (typeof id !== "number") return; 209 | const type: any = relatedTypes!.get(id); 210 | if (!type) return undefined; 211 | 212 | if (type.location) { 213 | const path = type.location.path; 214 | if (positionMap.has(path)) { 215 | const updatedPosition = positionMap.get(path)!.get(getLineCharMapKey(type.location.line, type.location.char)); 216 | if (updatedPosition) { 217 | [ type.location.line, type.location.char ] = updatedPosition; 218 | } 219 | type.location.path = formatPath(path); 220 | } 221 | } 222 | 223 | const children: HotType[] = []; 224 | 225 | // If there's a cycle, suppress the children, but not the type itself 226 | if (ancestorIds.indexOf(id) < 0) { 227 | ancestorIds.push(id); 228 | 229 | for (const prop in type) { 230 | if (prop.match(/type/i)) { 231 | if (Array.isArray(type[prop])) { 232 | for (const t of type[prop]) { 233 | const child = worker(t, ancestorIds); 234 | if (child) { 235 | children.push(child); 236 | } 237 | } 238 | } 239 | else { 240 | const child = worker(type[prop], ancestorIds); 241 | if (child) { 242 | children.push(child); 243 | } 244 | } 245 | } 246 | } 247 | 248 | ancestorIds.pop(); 249 | } 250 | 251 | return { type, children }; 252 | } 253 | } 254 | } 255 | 256 | function formatPath(p: string) { 257 | return path.normalize(p); 258 | } 259 | 260 | function undefinedIfEmpty(arr: T[]): T[] | undefined { 261 | return arr.length ? arr : undefined; 262 | } 263 | -------------------------------------------------------------------------------- /src/print-trace-analysis-text.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import { assert } from "console"; 5 | import chalk = require("chalk"); 6 | import treeify = require("treeify"); 7 | import fs = require("fs"); 8 | import path = require("path"); 9 | 10 | import getTypeTree = require("./get-type-tree"); 11 | import { EventSpan, parse } from "./parse-trace-file"; 12 | import { buildHotPathsTree, getEmittedImports, getLineCharMapKey, getNormalizedPositions, getPackageVersion, getRelatedTypes, getTypes, PositionMap, unmangleCamelCase } from "./analyze-trace-utilities"; 13 | 14 | export async function reportHighlights( 15 | tracePath: string, 16 | typesPath: string | undefined, 17 | thresholdDuration: number, 18 | minDuration: number, 19 | minPercentage: number, 20 | importExpressionThreshold: number): Promise { 21 | 22 | const parseResult = await parse(tracePath, minDuration); 23 | 24 | const root = buildHotPathsTree(parseResult, thresholdDuration, minPercentage); 25 | 26 | const unclosedStack = parseResult.unclosedStack; 27 | if (unclosedStack.length) { 28 | console.log("Trace ended unexpectedly"); 29 | 30 | while (unclosedStack.length) { 31 | const event = unclosedStack.pop()!; 32 | console.log(`> ${event.name}: ${JSON.stringify(event.args)}`); 33 | } 34 | 35 | console.log(); 36 | } 37 | 38 | const sawHotspots = await printHotStacks(root, importExpressionThreshold, typesPath); 39 | console.log(); 40 | const sawDuplicates = await printDuplicateNodeModules(parseResult.nodeModulePaths); 41 | 42 | return sawHotspots || sawDuplicates; 43 | } 44 | 45 | async function printDuplicateNodeModules(nodeModulePaths: Map): Promise { 46 | const tree = {}; 47 | let sawDuplicate = false; 48 | const sorted = Array.from(nodeModulePaths.entries()).sort(([n1,p1], [n2,p2]) => p2.length - p1.length || n1.localeCompare(n2)); 49 | for (const [packageName, packagePaths] of sorted) { 50 | if (packagePaths.length < 2) continue; 51 | sawDuplicate = true; 52 | const packageTree = {}; 53 | for (const packagePath of packagePaths.sort((p1, p2) => p1.localeCompare(p2))) { 54 | const version = await getPackageVersion(packagePath); 55 | packageTree[`${version ? `Version ${version}` : `Unknown version`} from ${packagePath}`] = {}; 56 | } 57 | tree[packageName] = packageTree; 58 | } 59 | 60 | if (sawDuplicate) { 61 | console.log("Duplicate packages"); 62 | console.log(treeify.asTree(tree, /*showValues*/ false, /*hideFunctions*/ true).trimEnd()); 63 | } 64 | else { 65 | console.log("No duplicate packages found"); 66 | } 67 | 68 | return sawDuplicate; 69 | } 70 | 71 | async function printHotStacks(root: EventSpan, importExpressionThreshold: number, typesPath: string | undefined): Promise { 72 | const relatedTypes = typesPath ? await getRelatedTypes(root, typesPath, /*leafOnly*/ true) : undefined; 73 | 74 | const positionMap = await getNormalizedPositions(root, relatedTypes); 75 | const tree = await makePrintableTree(root, /*currentFile*/ undefined, positionMap, relatedTypes, importExpressionThreshold); 76 | 77 | const sawHotspots = Object.entries(tree).length > 0; 78 | if (sawHotspots) { 79 | console.log("Hot Spots"); 80 | console.log(treeify.asTree(tree, /*showValues*/ false, /*hideFunctions*/ true).trimEnd()); 81 | } 82 | else { 83 | console.log("No hot spots found"); 84 | } 85 | return sawHotspots; 86 | } 87 | 88 | async function makePrintableTree(curr: EventSpan, currentFile: string | undefined, positionMap: PositionMap, relatedTypes: Map | undefined, importExpressionThreshold: number): Promise<{}> { 89 | let childTree = {}; 90 | 91 | let showCurrentFile = false; 92 | if (curr.event?.cat === "check") { 93 | const path = curr.event.args!.path; 94 | if (path) { 95 | showCurrentFile = path !== currentFile; 96 | currentFile = path; 97 | } 98 | else { 99 | assert(curr.event?.name !== "checkSourceFile", "checkSourceFile should have a path"); 100 | } 101 | } 102 | 103 | if (curr.children.length) { 104 | // Sort slow to fast 105 | const sortedChildren = curr.children.sort((a, b) => (b.end - b.start) - (a.end - a.start)); 106 | for (const child of sortedChildren) { 107 | Object.assign(childTree, await makePrintableTree(child, currentFile, positionMap, relatedTypes, importExpressionThreshold)); 108 | } 109 | } 110 | 111 | if (curr.event) { 112 | const eventStr = await eventToString(); 113 | if (eventStr) { 114 | let result = {}; 115 | result[`${eventStr} (${Math.round((curr.end - curr.start) / 1000)}ms)`] = childTree; 116 | return result; 117 | } 118 | } 119 | 120 | return childTree; 121 | 122 | async function eventToString(): Promise { 123 | const event = curr.event!; 124 | switch (event.name) { 125 | // TODO (https://github.com/microsoft/typescript-analyze-trace/issues/2) 126 | // case "findSourceFile": 127 | // return `Load file ${event.args!.fileName}`; 128 | case "emitDeclarationFileOrBundle": 129 | const dtsPath = event.args.declarationFilePath; 130 | if (!dtsPath || !fs.existsSync(dtsPath)) { 131 | return undefined; 132 | } 133 | try { 134 | const emittedImports = await getEmittedImports(dtsPath, importExpressionThreshold); 135 | if (emittedImports.length === 0) { 136 | return undefined; 137 | } 138 | for (const { name, count } of emittedImports) { 139 | // Directly modifying childTree is pretty hacky 140 | childTree[`Consider adding \`${chalk.cyan(`import ${chalk.cyan(name)}`)}\` which is used in ${count} places`] = {}; 141 | } 142 | return `Emit declarations file ${formatPath(dtsPath)}`; 143 | } 144 | catch { 145 | return undefined; 146 | } 147 | case "checkSourceFile": 148 | return `Check file ${formatPath(currentFile!)}`; 149 | case "structuredTypeRelatedTo": 150 | const args = event.args!; 151 | if (relatedTypes && curr.children.length === 0) { 152 | const typeTree = { 153 | ...getTypeTree(args.sourceId, relatedTypes), 154 | ...getTypeTree(args.targetId, relatedTypes), 155 | }; 156 | // Directly modifying childTree is pretty hacky 157 | Object.assign(childTree, updateTypeTreePositions(typeTree)); 158 | } 159 | return `Compare types ${args.sourceId} and ${args.targetId}`; 160 | case "getVariancesWorker": 161 | if (relatedTypes && curr.children.length === 0) { 162 | const typeTree = getTypeTree(event.args!.id, relatedTypes); 163 | // Directly modifying childTree is pretty hacky 164 | Object.assign(childTree, updateTypeTreePositions(typeTree)); 165 | } 166 | return `Determine variance of type ${event.args!.id}`; 167 | default: 168 | if (event.cat === "check" && event.args && event.args.pos && event.args.end) { 169 | const currentFileClause = showCurrentFile 170 | ? ` in ${formatPath(currentFile!)}` 171 | : ""; 172 | if (positionMap.has(currentFile!)) { 173 | const updatedPos = positionMap.get(currentFile!)!.get(event.args.pos.toString())!; 174 | const updatedEnd = positionMap.get(currentFile!)!.get(event.args.end.toString())!; 175 | return `${unmangleCamelCase(event.name)}${currentFileClause} from (line ${updatedPos[0]}, char ${updatedPos[1]}) to (line ${updatedEnd[0]}, char ${updatedEnd[1]})`; 176 | } 177 | else { 178 | return `${unmangleCamelCase(event.name)}${currentFileClause} from offset ${event.args.pos} to offset ${event.args.end}`; 179 | } 180 | } 181 | return undefined; 182 | } 183 | } 184 | 185 | function updateTypeTreePositions(typeTree: any): any { 186 | let newTree = {}; 187 | for (let typeString in typeTree) { 188 | const subtree = typeTree[typeString]; 189 | 190 | const type = JSON.parse(typeString); 191 | if (type.location) { 192 | const path = type.location.path; 193 | if (positionMap.has(path)) { 194 | const updatedPosition = positionMap.get(path)!.get(getLineCharMapKey(type.location.line, type.location.char))!; 195 | [ type.location.line, type.location.char ] = updatedPosition; 196 | 197 | typeString = JSON.stringify(type); 198 | } 199 | 200 | typeString = typeString.replace(path, formatPath(path)); 201 | } 202 | 203 | newTree[typeString] = updateTypeTreePositions(subtree); 204 | } 205 | 206 | return newTree; 207 | } 208 | } 209 | 210 | function formatPath(p: string) { 211 | if (/node_modules/.test(p)) { 212 | p = p.replace(/\/node_modules\/([^@][^/]+)\//g, `/node_modules/${chalk.cyan("$1")}/`); 213 | p = p.replace(/\/node_modules\/(@[^/]+\/[^/]+)/g, `/node_modules/${chalk.cyan("$1")}/`); 214 | } 215 | else { 216 | p = path.join(path.dirname(p), chalk.cyan(path.basename(p))); 217 | } 218 | return chalk.magenta(path.normalize(p)); 219 | } -------------------------------------------------------------------------------- /src/print-types.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | if (process.argv.length < 4) { 5 | const path = require("path"); 6 | console.error(`Usage: ${path.basename(process.argv[0])} ${path.basename(process.argv[1])} type_path id+`); 7 | process.exit(1); 8 | } 9 | 10 | import fs = require("fs"); 11 | import treeify = require("treeify"); 12 | import getTypeTree = require("./get-type-tree"); 13 | 14 | 15 | const typesPath = process.argv[2]; 16 | const ids = process.argv.slice(3).map(x => +x); 17 | 18 | const json = fs.readFileSync(typesPath, { encoding: "utf-8" }); 19 | const types: any[] = JSON.parse(json); 20 | 21 | console.log(ids.map(id => printType(id)).join("\n")); 22 | 23 | function printType(id: number): string { 24 | const cache = new Map(); 25 | const tree = getTypeTree(id, cache, types) as {}; 26 | return treeify.asTree(tree, /*showValues*/ false, /*hideFunctions*/ true); 27 | } 28 | -------------------------------------------------------------------------------- /src/simplify-type.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | function simplifyType(type: object): object { 5 | const processed = simplifyTypeHelper({...type}); 6 | 7 | // Normalize the property order 8 | 9 | const result: any = { 10 | id: processed.id, 11 | kind: processed.kind, 12 | name: processed.name, 13 | aliasTypeArguments: processed.aliasTypeArguments, 14 | instantiatedType: processed.instantiatedType, 15 | typeArguments: processed.typeArguments, 16 | }; 17 | 18 | for (const prop in processed) { 19 | if (!result[prop] && prop !== "location" && prop !== "display") { 20 | result[prop] = processed[prop]; 21 | } 22 | } 23 | 24 | result["location"] = processed.location; 25 | result["display"] = processed.display; 26 | 27 | return result; 28 | } 29 | 30 | function simplifyTypeHelper(type: any): any { 31 | type.name = type.symbolName; 32 | type.symbolName = undefined; 33 | 34 | const isDestructuring = !!type.destructuringPattern; 35 | const node = type.destructuringPattern ?? type.referenceLocation ?? type.firstDeclaration; 36 | type.destructuringPattern = undefined; 37 | type.referenceLocation = undefined; 38 | type.firstDeclaration = undefined; 39 | type.location = node && { 40 | path: node.path, 41 | line: node.start?.line, 42 | char: node.start?.character, 43 | }; 44 | 45 | const flags = getTypeFlags(type); 46 | type.flags = undefined; 47 | 48 | const display = type.display; 49 | type.display = undefined; 50 | 51 | type.recursionId = undefined; 52 | 53 | if (type.intrinsicName) { 54 | return { 55 | kind: "Intrinsic", 56 | ...type, 57 | name: type.intrinsicName, 58 | intrinsicName: undefined, 59 | }; 60 | } 61 | 62 | if (type.unionTypes) { 63 | return { 64 | kind: makeAliasedKindIfNamed(type, "Union"), 65 | count: type.unionTypes.length, 66 | types: type.unionTypes, 67 | ...type, 68 | unionTypes: undefined, 69 | }; 70 | } 71 | 72 | if (type.intersectionTypes) { 73 | return { 74 | kind: makeAliasedKindIfNamed(type, "Intersection"), 75 | count: type.intersectionTypes.length, 76 | types: type.intersectionTypes, 77 | ...type, 78 | intersectionTypes: undefined, 79 | }; 80 | } 81 | 82 | if (type.indexedAccessObjectType) { 83 | return { 84 | kind: makeAliasedKindIfNamed(type, "IndexedAccess"), 85 | ...type, 86 | }; 87 | } 88 | 89 | if (type.keyofType) { 90 | return { 91 | kind: makeAliasedKindIfNamed(type, "IndexType"), 92 | ...type, 93 | }; 94 | } 95 | 96 | if (type.isTuple) { 97 | return { 98 | kind: makeAliasedKindIfNamed(type, "Tuple"), 99 | ...type, 100 | isTuple: undefined, 101 | }; 102 | } 103 | 104 | if (type.conditionalCheckType) { 105 | return { 106 | kind: makeAliasedKindIfNamed(type, "ConditionalType"), 107 | ...type, 108 | conditionalTrueType: type.conditionalTrueType < 0 ? undefined : type.conditionalTrueType, 109 | conditionalFalseType: type.conditionalFalseType < 0 ? undefined : type.conditionalFalseType, 110 | }; 111 | } 112 | 113 | if (type.substitutionBaseType) { 114 | return { 115 | kind: makeAliasedKindIfNamed(type, "SubstitutionType"), 116 | originalType: type.substitutionBaseType, 117 | ...type, 118 | substitutionBaseType: undefined, 119 | }; 120 | } 121 | 122 | if (type.reverseMappedSourceType) { 123 | return { 124 | kind: makeAliasedKindIfNamed(type, "ReverseMappedType"), 125 | sourceType: type.reverseMappedSourceType, 126 | mappedType: type.reverseMappedMappedType, 127 | constraintType: type.reverseMappedConstraintType, 128 | ...type, 129 | reverseMappedSourceType: undefined, 130 | reverseMappedMappedType: undefined, 131 | reverseMappedConstraintType: undefined, 132 | }; 133 | } 134 | 135 | if (type.aliasTypeArguments) { 136 | return { 137 | kind: "GenericTypeAlias", 138 | ...type, 139 | instantiatedType: undefined, 140 | aliasedType: type.instantiatedType, 141 | aliasedTypeTypeArguments: type.typeArguments, 142 | }; 143 | } 144 | 145 | if (type.instantiatedType && type.typeArguments?.length) { 146 | const instantiatedIsSelf = type.instantiatedType === type.id; 147 | return { 148 | kind: instantiatedIsSelf ? "GenericType" : "GenericInstantiation", 149 | ...type, 150 | instantiatedType: instantiatedIsSelf ? undefined : type.instantiatedType, 151 | }; 152 | } 153 | 154 | if (isDestructuring) { 155 | return { 156 | kind: "Destructuring", 157 | ...type, 158 | }; 159 | } 160 | 161 | if (flags.includes("StringLiteral")) { 162 | return { 163 | kind: "StringLiteral", 164 | value: display, 165 | ...type, 166 | }; 167 | } 168 | 169 | if (flags.includes("NumberLiteral")) { 170 | return { 171 | kind: "NumberLiteral", 172 | value: display, 173 | ...type, 174 | }; 175 | } 176 | 177 | if (flags.includes("BigIntLiteral")) { 178 | return { 179 | kind: "BigIntLiteral", 180 | value: display, 181 | ...type, 182 | }; 183 | } 184 | 185 | if (flags.includes("TypeParameter")) { 186 | return { 187 | kind: "TypeParameter", 188 | ...type, 189 | }; 190 | } 191 | 192 | if (flags.includes("UniqueESSymbol")) { 193 | return { 194 | kind: "Unique", 195 | ...type, 196 | }; 197 | } 198 | 199 | if (type.name?.startsWith("__@")) { 200 | const match = /^__@([^@]+)@\d+$/.exec(type.name); 201 | return { 202 | kind: "KnownSymbol", 203 | ...type, 204 | name: match ? match[1] : type.name, 205 | }; 206 | } 207 | 208 | if (type.name === "__function" || 209 | type.name === "__type" || 210 | type.name === "__class" || 211 | type.name === "__object") { 212 | return makeAnonymous(type); 213 | } 214 | 215 | if (type.name === "__jsxAttributes") { 216 | return makeAnonymous(type, "JsxAttributesType"); 217 | } 218 | 219 | // This is less specific than the name checks 220 | if (flags.includes("Object") && type.name) { 221 | // Unclear why this happens - known instances are non-generic classes or interfaces 222 | if (type.instantiatedType === type.id && type.typeArguments?.length === 0) { 223 | type.instantiatedType = undefined; 224 | type.typeArguments = undefined; 225 | } 226 | 227 | return { 228 | kind: "Object", 229 | ...type, 230 | }; 231 | } 232 | 233 | // This goes at the end because it's a guess and depends on other interpretations having been checked previously 234 | if (display && display.startsWith("(props:") && display.endsWith("=> Element")) { 235 | return { 236 | kind: "JsxElementSignature", 237 | ...type, 238 | display 239 | }; 240 | } 241 | 242 | return { 243 | kind: "Other", 244 | ...type, 245 | display 246 | }; 247 | 248 | function makeAnonymous(type: any, kind?: string): any { 249 | return { 250 | kind: kind ?? "Anonymous" + firstToUpper(type.name.replace(/^__/, "")), 251 | ...type, 252 | display: type.location ? undefined : display, 253 | name: undefined, 254 | }; 255 | } 256 | 257 | function makeAliasedKindIfNamed(type: { name?: string }, kind: string) { 258 | return type.name ? "Aliased" + kind : kind; 259 | } 260 | } 261 | 262 | function firstToUpper(name: string) { 263 | return name[0].toUpperCase() + name.substring(1); 264 | } 265 | 266 | function expandTypeFlags41(flags41: number): string[] { 267 | const flags: string[] = []; 268 | 269 | if (flags41 & 1 << 0) flags.push("Any"); 270 | if (flags41 & 1 << 1) flags.push("Unknown"); 271 | if (flags41 & 1 << 2) flags.push("String"); 272 | if (flags41 & 1 << 3) flags.push("Number"); 273 | if (flags41 & 1 << 4) flags.push("Boolean"); 274 | if (flags41 & 1 << 5) flags.push("Enum"); 275 | if (flags41 & 1 << 6) flags.push("BigInt"); 276 | if (flags41 & 1 << 7) flags.push("StringLiteral"); 277 | if (flags41 & 1 << 8) flags.push("NumberLiteral"); 278 | if (flags41 & 1 << 9) flags.push("BooleanLiteral"); 279 | if (flags41 & 1 << 10) flags.push("EnumLiteral"); 280 | if (flags41 & 1 << 11) flags.push("BigIntLiteral"); 281 | if (flags41 & 1 << 12) flags.push("ESSymbol"); 282 | if (flags41 & 1 << 13) flags.push("UniqueESSymbol"); 283 | if (flags41 & 1 << 14) flags.push("Void"); 284 | if (flags41 & 1 << 15) flags.push("Undefined"); 285 | if (flags41 & 1 << 16) flags.push("Null"); 286 | if (flags41 & 1 << 17) flags.push("Never"); 287 | if (flags41 & 1 << 18) flags.push("TypeParameter"); 288 | if (flags41 & 1 << 19) flags.push("Object"); 289 | if (flags41 & 1 << 20) flags.push("Union"); 290 | if (flags41 & 1 << 21) flags.push("Intersection"); 291 | if (flags41 & 1 << 22) flags.push("Index"); 292 | if (flags41 & 1 << 23) flags.push("IndexedAccess"); 293 | if (flags41 & 1 << 24) flags.push("Conditional"); 294 | if (flags41 & 1 << 25) flags.push("Substitution"); 295 | if (flags41 & 1 << 26) flags.push("NonPrimitive"); 296 | if (flags41 & 1 << 27) flags.push("TemplateLiteral"); 297 | if (flags41 & 1 << 28) flags.push("StringMapping"); 298 | 299 | return flags; 300 | } 301 | 302 | function getTypeFlags({ flags }: { flags: readonly string[] }): readonly string[] { 303 | // Traces from TS 4.1 contained numeric flags, rather than their string equivalents 304 | return flags.length === 1 && /^\d+$/.test(flags[0]) 305 | ? expandTypeFlags41(+flags[0]) 306 | : flags; 307 | } 308 | 309 | export = simplifyType; -------------------------------------------------------------------------------- /src/simplify-types-file.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import fs = require("fs"); 5 | import perf_hooks = require("perf_hooks"); 6 | import stream = require("stream"); 7 | import util = require("util"); 8 | import zlib = require("zlib"); 9 | 10 | import split = require("split2"); 11 | import yargs = require("yargs"); 12 | 13 | import jsonstream = require("jsonstream-next"); 14 | 15 | import simplifyType = require("./simplify-type"); 16 | 17 | const pipeline: (...stream: any[]) => Promise = util.promisify(stream.pipeline); 18 | 19 | const args = yargs(process.argv.slice(2)) 20 | .command("$0 ", "Preprocess tracing type dumps", yargs => yargs 21 | .positional("input", { type: "string", desc: "json file to read (possibly compressed)" }) 22 | .positional("output", { type: "string", desc: "json file to write (possibly compressed)" }) 23 | .options({ 24 | "m": { 25 | alias: "multiline", 26 | describe: "use true json parsing, rather than assuming each element is on a separate line", 27 | type: "boolean" 28 | } 29 | }) 30 | .help("h").alias("h", "help") 31 | .strict()) 32 | .argv; 33 | 34 | async function processFile(processElement: (element: {}) => readonly {}[]) { 35 | const stages: any[] = []; 36 | 37 | const inputPath = args.input!; 38 | 39 | stages.push(fs.createReadStream(inputPath)); 40 | 41 | if (inputPath.endsWith(".gz")) { 42 | stages.push(zlib.createGunzip()); 43 | } 44 | else if (inputPath.endsWith(".br")) { 45 | stages.push(zlib.createBrotliDecompress()); 46 | } 47 | 48 | if (args.m) { 49 | const transform = jsonstream.parse("*"); 50 | 51 | const oldFlush = transform._flush.bind(transform); 52 | const newFlush: typeof oldFlush = cb => { 53 | return oldFlush(err => { 54 | if (err) { 55 | // Incomplete JSON is normal (e.g. crash during tracing), so we swallow errors 56 | // and finish writing the output. 57 | console.log("Parse error: " + err.message); 58 | } 59 | cb(); 60 | }); 61 | }; 62 | transform._flush = newFlush; 63 | 64 | stages.push(transform); 65 | } 66 | else { 67 | stages.push(split(/,?\r?\n/)); 68 | 69 | let sawError = false; 70 | stages.push(new stream.Transform({ 71 | objectMode: true, 72 | transform(chunk, _encoding, callback) { 73 | if (!sawError) { 74 | try { 75 | const obj = JSON.parse(chunk.replace(/^\[/, "").replace(/\]$/, "")); 76 | callback(undefined, obj); 77 | return; 78 | } 79 | catch (e) { 80 | if (!(e instanceof SyntaxError)) { 81 | throw e; 82 | } 83 | 84 | // Incomplete JSON is normal (e.g. crash during tracing), so we swallow errors 85 | // and finish writing the output. 86 | sawError = true; 87 | console.log("Parse error: " + e.message); 88 | console.log("\tConsider re-running with '-m'"); 89 | } 90 | } 91 | 92 | console.log("\tDropping " + chunk); 93 | callback(); 94 | }, 95 | })); 96 | } 97 | 98 | stages.push(new stream.Transform({ 99 | objectMode: true, 100 | transform(obj, _encoding, callback) { 101 | const results = processElement(obj); 102 | if (results && results.length) { 103 | for (const result of results) { 104 | this.push(result); 105 | } 106 | } 107 | callback(); 108 | } 109 | })); 110 | 111 | let first = true; 112 | stages.push(new stream.Transform({ 113 | objectMode: true, 114 | transform(chunk, _encoding, callback) { 115 | if (first) { 116 | first = false; 117 | this.push("["); 118 | } 119 | else { 120 | this.push(",\n"); 121 | } 122 | 123 | this.push(JSON.stringify(chunk)); 124 | 125 | callback(); 126 | }, 127 | flush(callback) { 128 | callback(undefined, "]"); 129 | } 130 | })); 131 | 132 | const outputPath = args.output!; 133 | if (outputPath.endsWith(".gz")) { 134 | stages.push(zlib.createGzip()); 135 | } 136 | else if (outputPath.endsWith(".br")) { 137 | stages.push(zlib.createBrotliCompress()); 138 | } 139 | 140 | stages.push(fs.createWriteStream(outputPath)); 141 | 142 | await pipeline(stages); 143 | } 144 | 145 | async function run() { 146 | const start = perf_hooks.performance.now(); 147 | let itemCount = 0; 148 | console.log("Processing..."); 149 | try { 150 | await processFile(item => (itemCount++, [simplifyType(item)])); 151 | console.log("Done"); 152 | } 153 | catch (e: any) { 154 | console.log(`Error: ${e.message}`); 155 | } 156 | console.log(`Processed ${itemCount} items in ${Math.round(perf_hooks.performance.now() - start)} ms`); 157 | } 158 | 159 | run().catch(console.error); -------------------------------------------------------------------------------- /src/trivia-state-machine.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const code_CarriageReturn = "\r".charCodeAt(0); 5 | const code_NewLine = "\n".charCodeAt(0); 6 | const code_Space = " ".charCodeAt(0); 7 | const code_Tab = "\t".charCodeAt(0); 8 | const code_Slash = "/".charCodeAt(0); 9 | const code_Backslash = "\\".charCodeAt(0); 10 | const code_Star = "*".charCodeAt(0); 11 | const code_Hash = "#".charCodeAt(0); 12 | const code_Bang = "!".charCodeAt(0); 13 | const code_SingleQuote = "'".charCodeAt(0); 14 | const code_DoubleQuote = "\"".charCodeAt(0); 15 | const code_OpenBrace = "{".charCodeAt(0); 16 | const code_CloseBrace = "}".charCodeAt(0); 17 | const code_OpenBracket = "[".charCodeAt(0); 18 | const code_CloseBracket = "]".charCodeAt(0); 19 | const code_Backtick = "`".charCodeAt(0); 20 | const code_Dollar = "$".charCodeAt(0); 21 | 22 | const enum State { 23 | Uninitialized, 24 | Default, 25 | StartSingleLineComment, 26 | SingleLineComment, 27 | StartMultiLineComment, 28 | MultiLineComment, 29 | EndMultiLineComment, 30 | StartShebangComment, 31 | ShebangComment, 32 | SingleQuoteString, 33 | SingleQuoteStringEscapeBackslash, 34 | SingleQuoteStringEscapeQuote, 35 | DoubleQuoteString, 36 | DoubleQuoteStringEscapeBackslash, 37 | DoubleQuoteStringEscapeQuote, 38 | TemplateString, 39 | TemplateStringEscapeBackslash, 40 | TemplateStringEscapeQuote, 41 | StartExpressionHole, 42 | Regex, 43 | RegexEscapeBackslash, 44 | RegexEscapeSlash, 45 | RegexEscapeOpenBracket, 46 | CharClass, 47 | CharClassEscapeBackslash, 48 | CharClassEscapeCloseBracket, 49 | } 50 | 51 | export type CharKind = 52 | | "whitespace" 53 | | "comment" 54 | | "string" 55 | | "regex" 56 | | "code" 57 | ; 58 | 59 | interface StepResult { 60 | charKind: CharKind; 61 | wrapLine: boolean; 62 | } 63 | 64 | export interface StateMachine { 65 | step(ch: number, nextCh: number): StepResult; 66 | } 67 | 68 | export function create(): StateMachine { 69 | let state = State.Default; 70 | let braceDepth = 0; 71 | let templateStringBraceDepthStack: number[] = []; 72 | let isBOF = true; 73 | 74 | function step(ch: number, nextCh: number): StepResult { 75 | let nextStateId = State.Uninitialized;; 76 | let wrapLine = false; 77 | switch (ch) { 78 | case code_CarriageReturn: 79 | if (nextCh === code_NewLine) { 80 | if (state === State.ShebangComment || 81 | state === State.SingleLineComment || 82 | // Cases below are for error recovery 83 | state === State.SingleQuoteString || 84 | state === State.DoubleQuoteString || 85 | state === State.Regex || 86 | state === State.CharClass) { 87 | state = State.Default; 88 | } 89 | break; 90 | } 91 | // Fall through 92 | case code_NewLine: 93 | wrapLine = true; 94 | if (state === State.ShebangComment || 95 | state === State.SingleLineComment) { 96 | state = State.Default; 97 | } 98 | else if (state === State.SingleQuoteString || // Error recovery 99 | state === State.DoubleQuoteString) { // Error recovery 100 | state = State.Default; 101 | } 102 | break; 103 | 104 | case code_Slash: 105 | if (state === State.Default) { 106 | if (nextCh === code_Slash) { 107 | state = State.StartSingleLineComment; 108 | } 109 | else if (nextCh === code_Star) { 110 | // It seems like there might technically be a corner case where this is the beginning of an invalid regex 111 | state = State.StartMultiLineComment; 112 | } 113 | else { 114 | // TODO (https://github.com/microsoft/typescript-analyze-trace/issues/14): this is too aggressive - it will catch division 115 | state = State.Regex; 116 | } 117 | } 118 | else if (state === State.StartSingleLineComment) { 119 | state = State.SingleLineComment; 120 | } 121 | else if (state === State.EndMultiLineComment) { 122 | nextStateId = State.Default; 123 | } 124 | else if (state === State.Regex) { 125 | nextStateId = State.Default; 126 | } 127 | else if (state === State.RegexEscapeSlash) { 128 | nextStateId = State.Regex; 129 | } 130 | break; 131 | 132 | case code_Star: 133 | if (state === State.StartMultiLineComment) { 134 | state = State.MultiLineComment; 135 | } 136 | else if (state === State.MultiLineComment) { 137 | if (nextCh === code_Slash) { 138 | state = State.EndMultiLineComment; 139 | } 140 | } 141 | break; 142 | 143 | case code_Hash: 144 | if (isBOF && state === State.Default && nextCh === code_Bang) { 145 | state = State.StartShebangComment; 146 | } 147 | break; 148 | 149 | case code_Bang: 150 | if (state === State.StartShebangComment) { 151 | state = State.ShebangComment; 152 | } 153 | break; 154 | 155 | case code_SingleQuote: 156 | if (state === State.Default) { 157 | state = State.SingleQuoteString; 158 | } 159 | else if (state === State.SingleQuoteStringEscapeQuote) { 160 | nextStateId = State.SingleQuoteString; 161 | } 162 | else if (state === State.SingleQuoteString) { 163 | nextStateId = State.Default; 164 | } 165 | break; 166 | 167 | case code_DoubleQuote: 168 | if (state === State.Default) { 169 | state = State.DoubleQuoteString; 170 | } 171 | else if (state === State.DoubleQuoteStringEscapeQuote) { 172 | nextStateId = State.DoubleQuoteString; 173 | } 174 | else if (state === State.DoubleQuoteString) { 175 | nextStateId = State.Default; 176 | } 177 | break; 178 | 179 | case code_Backtick: 180 | if (state === State.Default) { 181 | state = State.TemplateString; 182 | } 183 | else if (state === State.TemplateStringEscapeQuote) { 184 | nextStateId = State.TemplateString; 185 | } 186 | else if (state === State.TemplateString) { 187 | nextStateId = State.Default; 188 | } 189 | break; 190 | 191 | case code_Backslash: 192 | if (state === State.SingleQuoteString) { 193 | if (nextCh === code_SingleQuote) { 194 | state = State.SingleQuoteStringEscapeQuote; 195 | } 196 | else if (nextCh === code_Backslash) { 197 | state = State.SingleQuoteStringEscapeBackslash; 198 | } 199 | } 200 | else if (state === State.DoubleQuoteString) { 201 | if (nextCh === code_DoubleQuote) { 202 | state = State.DoubleQuoteStringEscapeQuote; 203 | } 204 | else if (nextCh === code_Backslash) { 205 | state = State.DoubleQuoteStringEscapeBackslash; 206 | } 207 | } 208 | else if (state === State.TemplateString) { 209 | if (nextCh === code_Backtick) { 210 | state = State.TemplateStringEscapeQuote; 211 | } 212 | else if (nextCh === code_Backslash) { 213 | state = State.TemplateStringEscapeBackslash; 214 | } 215 | } 216 | else if (state === State.Regex) { 217 | if (nextCh === code_OpenBracket) { 218 | state = State.RegexEscapeOpenBracket; 219 | } 220 | else if (nextCh === code_Slash) { 221 | state = State.RegexEscapeSlash; 222 | } 223 | else if (nextCh === code_Backslash) { 224 | state = State.RegexEscapeBackslash; 225 | } 226 | } 227 | else if (state === State.CharClass) { 228 | if (nextCh === code_CloseBracket) { 229 | state = State.CharClassEscapeCloseBracket; 230 | } 231 | else if (nextCh === code_Backslash) { 232 | state = State.CharClassEscapeBackslash; 233 | } 234 | } 235 | else if (state === State.SingleQuoteStringEscapeBackslash) { 236 | nextStateId = State.SingleQuoteString; 237 | } 238 | else if (state === State.DoubleQuoteStringEscapeBackslash) { 239 | nextStateId = State.DoubleQuoteString; 240 | } 241 | else if (state === State.TemplateStringEscapeBackslash) { 242 | nextStateId = State.TemplateString; 243 | } 244 | else if (state === State.RegexEscapeBackslash) { 245 | nextStateId = State.Regex; 246 | } 247 | else if (state === State.CharClassEscapeBackslash) { 248 | nextStateId = State.CharClass; 249 | } 250 | break; 251 | 252 | case code_Dollar: 253 | if (state === State.TemplateString && nextCh === code_OpenBrace) { 254 | state = State.StartExpressionHole; 255 | } 256 | break; 257 | 258 | case code_OpenBrace: 259 | if (state === State.Default) { 260 | braceDepth++; 261 | } 262 | else if (state === State.StartExpressionHole) { 263 | templateStringBraceDepthStack.push(braceDepth); 264 | nextStateId = State.Default; 265 | } 266 | break; 267 | 268 | case code_CloseBrace: 269 | if (templateStringBraceDepthStack.length && braceDepth === templateStringBraceDepthStack[templateStringBraceDepthStack.length - 1]) { 270 | templateStringBraceDepthStack.pop(); 271 | state = State.TemplateString; 272 | } 273 | else if (state === State.Default && braceDepth > 0) { // Error recovery 274 | braceDepth--; 275 | } 276 | break; 277 | 278 | case code_OpenBracket: 279 | if (state === State.RegexEscapeOpenBracket) { 280 | nextStateId = State.Regex; 281 | } 282 | else if (state === State.Regex) { 283 | state = State.CharClass; 284 | } 285 | break; 286 | 287 | case code_CloseBracket: 288 | if (state === State.CharClassEscapeCloseBracket) { 289 | nextStateId = State.CharClass; 290 | } 291 | else if (state === State.CharClass) { 292 | nextStateId = State.Regex; 293 | } 294 | break; 295 | } 296 | 297 | let charKind: CharKind; 298 | 299 | switch (state) { 300 | case State.StartSingleLineComment: 301 | case State.SingleLineComment: 302 | case State.StartMultiLineComment: 303 | case State.MultiLineComment: 304 | case State.EndMultiLineComment: 305 | case State.StartShebangComment: 306 | case State.ShebangComment: 307 | charKind = "comment"; 308 | break; 309 | case State.SingleQuoteString: 310 | case State.SingleQuoteStringEscapeBackslash: 311 | case State.SingleQuoteStringEscapeQuote: 312 | case State.DoubleQuoteString: 313 | case State.DoubleQuoteStringEscapeBackslash: 314 | case State.DoubleQuoteStringEscapeQuote: 315 | case State.TemplateString: 316 | case State.TemplateStringEscapeBackslash: 317 | case State.TemplateStringEscapeQuote: 318 | case State.StartExpressionHole: 319 | charKind = "string"; 320 | break; 321 | case State.Regex: 322 | case State.RegexEscapeBackslash: 323 | case State.RegexEscapeSlash: 324 | case State.RegexEscapeOpenBracket: 325 | case State.CharClass: 326 | case State.CharClassEscapeBackslash: 327 | case State.CharClassEscapeCloseBracket: 328 | charKind = "regex"; 329 | break; 330 | default: 331 | const isWhitespace = 332 | ch === code_Space || 333 | ch === code_Tab || 334 | ch === code_NewLine || 335 | ch === code_CarriageReturn || 336 | /^\s$/.test(String.fromCharCode(ch)); 337 | charKind = isWhitespace ? "whitespace" : "code"; 338 | break; 339 | } 340 | 341 | if (nextStateId !== State.Uninitialized) { 342 | state = nextStateId; 343 | } 344 | 345 | isBOF = false; 346 | 347 | return { charKind, wrapLine }; 348 | } 349 | 350 | return { step }; 351 | } -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "../dist", 4 | "target": "ES2017", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "declaration": true, 10 | "noImplicitAny": false, 11 | } 12 | } --------------------------------------------------------------------------------