├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __test__ └── deno_dir_manifest │ └── deps │ ├── https │ ├── another.example.com │ │ ├── 32cd9336a09393d88fc22cf6f95ae006e3f2742a6c461967b2ba7954c5283fbf │ │ ├── 32cd9336a09393d88fc22cf6f95ae006e3f2742a6c461967b2ba7954c5283fbf.metadata.json │ │ ├── eac382fbc5e96dcb72874cba87121a37b029ea76f42f0bbd2a56c995759e775e │ │ └── eac382fbc5e96dcb72874cba87121a37b029ea76f42f0bbd2a56c995759e775e.metadata.json │ ├── example.com │ │ ├── 2fd053a88435858a2f3f08c14ac445b0e2f9fd6b74bc40762658c68a42108670 │ │ ├── 2fd053a88435858a2f3f08c14ac445b0e2f9fd6b74bc40762658c68a42108670.metadata.json │ │ ├── 510a38a33ab16b39d957c066a56c4b04a38c866e84b10c55d0f7ad9f4b368201 │ │ ├── 510a38a33ab16b39d957c066a56c4b04a38c866e84b10c55d0f7ad9f4b368201.metadata.json │ │ ├── 63e68703782f0f93a22b2dd57729731840632865d2e116b1f57d2fa3af2e59e7 │ │ ├── 63e68703782f0f93a22b2dd57729731840632865d2e116b1f57d2fa3af2e59e7.metadata.json │ │ ├── 70456f90a5c5a173c9d5bc68b36f661a4061b3cb7850e4b9675557a64513deb0 │ │ ├── 7617203222d94a074bea3e57a893d74af5546f17c1f90760f37f46299faf0cb0 │ │ ├── 7617203222d94a074bea3e57a893d74af5546f17c1f90760f37f46299faf0cb0.metadata.json │ │ ├── 80f9458ee4d37ba8e56a92d01e5cbd229f10e8cf4f3ab31823c289cf031c630d │ │ ├── 80f9458ee4d37ba8e56a92d01e5cbd229f10e8cf4f3ab31823c289cf031c630d.metadata.json │ │ ├── 8afd52da760dab7f2deda4b7453197f50421f310372c5da3f3847ffd062fa1cf │ │ ├── 8afd52da760dab7f2deda4b7453197f50421f310372c5da3f3847ffd062fa1cf.metadata.json │ │ ├── 91eadc577dcd94ede732f546c6725fb79d9d24924505c05a22f657ec31c78f31 │ │ ├── 91eadc577dcd94ede732f546c6725fb79d9d24924505c05a22f657ec31c78f31.metadata.json │ │ ├── 933405cb905c548e870daee56d0589b7dd8e146c0cdbd5f16a959f8227c1fe06 │ │ ├── 933405cb905c548e870daee56d0589b7dd8e146c0cdbd5f16a959f8227c1fe06.metadata.json │ │ ├── 94b9af04676e29b71da17af190d513d298fabcdeeeaddf90ee36000fed3f534a │ │ ├── 94b9af04676e29b71da17af190d513d298fabcdeeeaddf90ee36000fed3f534a.metadata.json │ │ ├── b09ad5614eb13279b5858f2bb3eed7f13770a6a7a08140e19d9e5a00a4699af0 │ │ ├── b09ad5614eb13279b5858f2bb3eed7f13770a6a7a08140e19d9e5a00a4699af0.metadata.json │ │ ├── d2c891d1a65880123bd43fc2fba9a6d094b8de88a9efc3023acd53c961c9f4b5 │ │ ├── d2c891d1a65880123bd43fc2fba9a6d094b8de88a9efc3023acd53c961c9f4b5.metadata.json │ │ ├── da88efaa8b70cda7903ddc29b8d4c6ea3015de65329ea393289f4104ae2da941 │ │ ├── da88efaa8b70cda7903ddc29b8d4c6ea3015de65329ea393289f4104ae2da941.metadata.json │ │ ├── f13574acadcffaf55de9aada6cffa50fe178ac8b0ea1bc5aebf022b93e248f98 │ │ └── f13574acadcffaf55de9aada6cffa50fe178ac8b0ea1bc5aebf022b93e248f98.metadata.json │ ├── invalid.com │ │ └── 70456f90a5c5a173c9d5bc68b36f661a4061b3cb7850e4b9675557a64513deb0 │ ├── invalid_file │ ├── invalid_meta.com │ │ ├── 1d6ab9051e9ae4e3a9d501b1a4316583ae8ecd51d41d327b514abf0194c14dc4 │ │ ├── 1d6ab9051e9ae4e3a9d501b1a4316583ae8ecd51d41d327b514abf0194c14dc4.metadata.json │ │ ├── 2f5c120425d2222c81506ea48f25c608a89272ed179233ab4fd47debb0f5d05f │ │ └── 2f5c120425d2222c81506ea48f25c608a89272ed179233ab4fd47debb0f5d05f.metadata.json │ ├── redirect.com │ │ ├── 0278046a318a368d7a8da89fdf59c60892dbf3498b996b36f11faf7d74d141c8 │ │ ├── 0278046a318a368d7a8da89fdf59c60892dbf3498b996b36f11faf7d74d141c8.metadata.json │ │ ├── 1d037d3d472f7c85f4aa26767ec20e9114fb42bdfb8c9e4b46332a3b2f47589d │ │ ├── 1d037d3d472f7c85f4aa26767ec20e9114fb42bdfb8c9e4b46332a3b2f47589d.metadata.json │ │ ├── 276c3ca0faeb1836b15cda49f57e0c03fde69133b73c424790a91cca8baa4f93 │ │ ├── 276c3ca0faeb1836b15cda49f57e0c03fde69133b73c424790a91cca8baa4f93.metadata.json │ │ ├── 39bce9e2269e45937f28bc4ff60fb3add9df7e0048078c06dec04c490a90bf9f │ │ ├── 39bce9e2269e45937f28bc4ff60fb3add9df7e0048078c06dec04c490a90bf9f.metadata.json │ │ ├── 63e68703782f0f93a22b2dd57729731840632865d2e116b1f57d2fa3af2e59e7 │ │ ├── 63e68703782f0f93a22b2dd57729731840632865d2e116b1f57d2fa3af2e59e7.metadata.json │ │ ├── 6a3cfff3f701f7e3e2611e61d98e9b04ec6a12fe2a72418eb96b929d3836dfd9 │ │ ├── 6a3cfff3f701f7e3e2611e61d98e9b04ec6a12fe2a72418eb96b929d3836dfd9.metadata.json │ │ ├── 881c89ae27a3543d72b594b4d48c8c7020c0f1fd8250d2d83bbd5837f02664ae │ │ ├── 881c89ae27a3543d72b594b4d48c8c7020c0f1fd8250d2d83bbd5837f02664ae.metadata.json │ │ ├── b8b956180a494f61cbc6cd74b6c48793fd975f9c3e9cd96410042a61c275823b │ │ └── b8b956180a494f61cbc6cd74b6c48793fd975f9c3e9cd96410042a61c275823b.metadata.json │ └── unknown_module.com │ │ ├── 27e936010957fa71c39ae46b56a40b4b4c5b388f50d950637e4522708a340663 │ │ └── 27e936010957fa71c39ae46b56a40b4b4c5b388f50d950637e4522708a340663.metadata.json │ └── invalid_file ├── jest.config.js ├── lib ├── lib.deno.d.ts └── lib.webworker.d.ts ├── package.json ├── src ├── code_fixes │ ├── import_fixes.ts │ └── index.ts ├── codefix_provider.ts ├── deno_modules.ts ├── deno_type_hint.test.ts ├── deno_type_hint.ts ├── index.ts ├── logger.ts ├── module_resolver │ ├── hash_meta.test.ts │ ├── hash_meta.ts │ ├── local_module_resolver.ts │ ├── remote_module_resolver.ts │ ├── types.ts │ └── universal_module_resolver.ts ├── ts_utils.ts ├── tsls_host_wrappers │ ├── compilation_settings.ts │ ├── resolve_module_names.ts │ ├── resolve_type_reference_directives.ts │ └── script_file_names.ts ├── tsls_wrappers │ ├── code_fixes.ts │ ├── completion_entry_details.ts │ ├── completions.ts │ ├── resolve_moduel_names.ts │ └── semantic_diagnostics.ts ├── utils.test.ts └── utils.ts ├── tsconfig.json ├── types └── merge-deep.d.ts └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, macOS-latest, windows-latest] 11 | name: test in ${{ matrix.os }} 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup node.js 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: 12.x 20 | 21 | - name: Setup Deno 22 | uses: denolib/setup-deno@master 23 | with: 24 | deno-version: 1.x 25 | 26 | - name: Log versions 27 | run: | 28 | node -v 29 | npm -v 30 | yarn --version 31 | deno --version 32 | 33 | - name: Install 34 | run: yarn --frozen-lockfile --non-interactive 35 | 36 | - name: Check code format 37 | run: yarn run check 38 | 39 | - name: Test 40 | run: yarn run test 41 | 42 | - name: Compile 43 | run: yarn compile 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Dependencies 9 | node_modules 10 | 11 | # dotenv environment variables file 12 | .env 13 | .env.test 14 | 15 | # Intermediary compilation files 16 | out 17 | coverage 18 | 19 | # VS Code 20 | .vscode/* 21 | !.vscode/settings.json 22 | !.vscode/tasks.json 23 | !.vscode/launch.json 24 | !.vscode/extensions.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | /src 3 | /test 4 | /out/test 5 | /out/**/*.map 6 | /e2e 7 | /examples 8 | /coverage 9 | 10 | /.vscode 11 | /.github 12 | 13 | tsconfig.json 14 | tslint.json 15 | .travis.yml 16 | node_modules 17 | *.test.js 18 | *.js.map 19 | yarn.lock 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "deno.enable": false 4 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.31.0 - 2020-05-29 4 | 5 | - support @deno-types ([#46](https://github.com/justjavac/typescript-deno-plugin/pull/46)) 6 | 7 | ## 1.30.0 - 2020-05-28 8 | 9 | - Fix collection updating and undefined error ([#42](https://github.com/justjavac/typescript-deno-plugin/pull/42)) 10 | - bump deno v1.0.2 11 | 12 | ## 1.29.0 - 2020-05-18 13 | 14 | - avoid call circular 15 | 16 | ## 1.28.0 - 2020-05-18 17 | 18 | - improve auto imports [#40](https://github.com/justjavac/typescript-deno-plugin/pull/40) 19 | 20 | ## 1.27.0 - 2020-05-16 21 | 22 | - fix intellisense on cached files 23 | 24 | ## 1.26.0 - 2020-05-15 25 | 26 | - add unstable types 27 | 28 | ## 1.25.0 - 2020-05-15 29 | 30 | - fix importmap using local files 31 | - remvoe crypto 32 | 33 | ## 1.24.0 - 2020-05-15 34 | 35 | - bump deno@v1.0.0 36 | 37 | ## 1.23.1 - 2020-05-15 38 | 39 | - fix typo 40 | 41 | ## 1.23.0 - 2020-05-15 42 | 43 | - fix non-importmaps issue 44 | 45 | ## 1.22.0 - 2020-05-15 46 | 47 | - bump deno@v1.0.0-rc3 48 | 49 | ## 1.21.0 - 2020-05-15 50 | 51 | - fix import maps 52 | 53 | ## 1.18.0 - 2020-05-15 54 | 55 | - fix ts error 2691: cannot end with `'.ts'` 56 | 57 | ## 1.17.0 - 2020-05-15 58 | 59 | - remove `dtsPath` config 60 | 61 | ## 1.16.0 - 2020-05-15 62 | 63 | - support for webworker 64 | - format using `deno fmt` 65 | 66 | ## 1.15.0 - 2020-05-10 67 | 68 | - bump deno@1.0.0-rc2 69 | 70 | ## 1.6.0 - 2020-05-03 71 | 72 | - rewrite module resolver [#35](https://github.com/justjavac/typescript-deno-plugin/pull/35) 73 | 74 | ## 1.4.1 - 2020-04-13 75 | 76 | - no changes 77 | 78 | ## 1.4.0 - 2020-04-13 79 | 80 | - force overwrite deno's tsconfig [#32](https://github.com/justjavac/typescript-deno-plugin/pull/32) 81 | 82 | ## 1.3.0 - 2020-04-10 83 | 84 | - update ts compiler optins [#29](https://github.com/justjavac/typescript-deno-plugin/pull/29) 85 | - update deno types to 0.40.0 86 | 87 | ## 1.2.7 - 2019-08-29 88 | 89 | - Add support for query strings in import urls [#16](https://github.com/justjavac/typescript-deno-plugin/pull/16) 90 | - update deno types to 0.16.0 91 | 92 | ## 1.2.6 - 2019-06-19 93 | 94 | - update deno types to 0.9.0 [#14](https://github.com/justjavac/typescript-deno-plugin/pull/14) 95 | 96 | ## 1.2.5 - 2019-06-05 97 | 98 | - deep merge `compilerOptions` [#34](https://github.com/justjavac/vscode-deno/issues/34) 99 | 100 | ## 1.2.2 - 1.2.3 - 2019-04-19 101 | 102 | - fix typescript not found bug 103 | 104 | ## 1.2.1 - 2019-04-19 105 | 106 | - improve deno declaration searching [#7](https://github.com/justjavac/typescript-deno-plugin/pull/7) 107 | 108 | ## 1.2.0 - 2019-04-18 109 | 110 | - add headers fallback when module is not found [#3](https://github.com/justjavac/typescript-deno-plugin/pull/3) 111 | - set default compilation options [#4](https://github.com/justjavac/typescript-deno-plugin/pull/4) 112 | - add deno declaration files [#5](https://github.com/justjavac/typescript-deno-plugin/pull/5) 113 | 114 | ## 1.1.0 - 2019-03-07 115 | 116 | - Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 justjavac 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 | # **MOVE TO ** 2 | 3 | ----------- 4 | 5 | # typescript-deno-plugin 6 | 7 | > Deno language service plugin for TypeScript. 8 | 9 | [![npm package](https://nodei.co/npm/typescript-deno-plugin.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/typescript-deno-plugin/) 10 | 11 | [![Build Status](https://travis-ci.com/justjavac/typescript-deno-plugin.svg?branch=master)](https://travis-ci.com/justjavac/typescript-deno-plugin) 12 | [![NPM version](https://img.shields.io/npm/v/typescript-deno-plugin.svg)](https://www.npmjs.com/package/typescript-deno-plugin) 13 | [![NPM Downloads](https://img.shields.io/npm/dm/typescript-deno-plugin.svg?style=flat)](https://npmcharts.com/compare/typescript-deno-plugin?minimal=true) 14 | [![Install Size](https://packagephobia.now.sh/badge?p=typescript-deno-plugin)](https://packagephobia.now.sh/result?p=typescript-deno-plugin) 15 | 16 | ## Editor Support 17 | 18 | This plugin requires TypeScript 2.3 or later. It can provide intellisense in TypeScript files within any editors that uses TypeScript to power their language features. 19 | 20 | ### With VS Code 21 | 22 | The simplest way to use this plugin is to install the [TypeScript Deno Plugin VS Code extension](https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno). This extension enables the plugin when using VS Code's version of TypeScript. 23 | 24 | If you are using a workspace version of TypeScript, you must manually install the plugin alongside the version of TypeScript in your workspace. 25 | 26 | **use npm**: 27 | 28 | ```bash 29 | npm install --save-dev typescript-deno-plugin typescript 30 | ``` 31 | 32 | or **use yarn**: 33 | 34 | ```bash 35 | yarn add -D typescript-deno-plugin typescript 36 | ``` 37 | 38 | ### With JetBrains IDEs 39 | 40 | Prerequisite: Follow manual installation instructions as described in [VSCode install](#with-vs-code). 41 | 42 | 1. Open TypScript preferences: 43 | 44 | `Preferences | Languages & Frameworks | TypeScript` 45 | 46 | 2. Change the TypeScript path to the local installed typescript path e.g. `~/myapp/node_modules/typescript` 47 | 48 | *Tested with WebStorm. Hypothetically, should run in all JetBrains IDEs.* 49 | 50 | ### Configuration 51 | 52 | After install typescript-deno-plugin, Then you can add a `plugins` section to your [tsconfig.json](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html). 53 | 54 | ```json 55 | { 56 | "compilerOptions": { 57 | "plugins": [ 58 | { 59 | "name": "typescript-deno-plugin", 60 | "enable": true, // default is `true` 61 | "importmap": "import_map.json" 62 | } 63 | ] 64 | } 65 | } 66 | ``` 67 | 68 | Finally, run the `Select TypeScript version` command in VS Code to switch to use the workspace version of TypeScript for VS Code's JavaScript and TypeScript language support. You can find more information about managing typescript versions [in the VS Code documentation](https://code.visualstudio.com/Docs/languages/typescript#_using-newer-typescript-versions). 69 | 70 | ### With Visual Studio 71 | 72 | This plugin works Visual Studio 2017 using the TypeScript 2.3+ SDK. 73 | 74 | First install the plugin in your project. 75 | 76 | **use npm**: 77 | 78 | ```bash 79 | npm install --save-dev typescript-deno-plugin typescript 80 | ``` 81 | 82 | or **use yarn**: 83 | 84 | ```bash 85 | yarn add -D typescript-deno-plugin typescript 86 | ``` 87 | 88 | Then add a plugins section to your [tsconfig.json](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html). 89 | 90 | ```json 91 | { 92 | "compilerOptions": { 93 | "plugins": [ 94 | { 95 | "name": "typescript-deno-plugin" 96 | } 97 | ] 98 | } 99 | } 100 | ``` 101 | 102 | Then reload your project to make sure the plugin has been loaded properly. 103 | 104 | ### With Atom 105 | 106 | This plugin works with the [Atom TypeScript plugin](https://atom.io/packages/atom-typescript). 107 | 108 | First install the plugin and a copy of TypeScript in your workspace. 109 | 110 | **use npm**: 111 | 112 | ```bash 113 | npm install --save-dev typescript-deno-plugin typescript 114 | ``` 115 | 116 | or **use yarn**: 117 | 118 | ```bash 119 | yarn add -D typescript-deno-plugin typescript 120 | ``` 121 | 122 | Then add a plugins section to your [tsconfig.json](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html). 123 | 124 | ```json 125 | { 126 | "compilerOptions": { 127 | "plugins": [ 128 | { 129 | "name": "typescript-deno-plugin" 130 | } 131 | ] 132 | } 133 | } 134 | ``` 135 | 136 | Then restart Atom. 137 | 138 | ### Credits 139 | 140 | - [justjavac](https://github.com/justjavac) 141 | 142 | ### License 143 | 144 | typescript-deno-plugin is released under the MIT License. See the bundled [LICENSE](./LICENSE) file for details. 145 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/another.example.com/32cd9336a09393d88fc22cf6f95ae006e3f2742a6c461967b2ba7954c5283fbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/another.example.com/32cd9336a09393d88fc22cf6f95ae006e3f2742a6c461967b2ba7954c5283fbf -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/another.example.com/32cd9336a09393d88fc22cf6f95ae006e3f2742a6c461967b2ba7954c5283fbf.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": {}, 3 | "url": "https://another.example.com/path/mod.ts" 4 | } 5 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/another.example.com/eac382fbc5e96dcb72874cba87121a37b029ea76f42f0bbd2a56c995759e775e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/another.example.com/eac382fbc5e96dcb72874cba87121a37b029ea76f42f0bbd2a56c995759e775e -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/another.example.com/eac382fbc5e96dcb72874cba87121a37b029ea76f42f0bbd2a56c995759e775e.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": {}, 3 | "url": "https://another.example.com/path/mod.ts?foo=bar" 4 | } 5 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/2fd053a88435858a2f3f08c14ac445b0e2f9fd6b74bc40762658c68a42108670: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/example.com/2fd053a88435858a2f3f08c14ac445b0e2f9fd6b74bc40762658c68a42108670 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/2fd053a88435858a2f3f08c14ac445b0e2f9fd6b74bc40762658c68a42108670.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "location": "go_to_invalid" 4 | }, 5 | "url": "https://example.com/redirect_to_invalid" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/510a38a33ab16b39d957c066a56c4b04a38c866e84b10c55d0f7ad9f4b368201: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/example.com/510a38a33ab16b39d957c066a56c4b04a38c866e84b10c55d0f7ad9f4b368201 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/510a38a33ab16b39d957c066a56c4b04a38c866e84b10c55d0f7ad9f4b368201.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "location": "/esm/mod.ts" 4 | }, 5 | "url": "https://example.com/redirect_to_absolute" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/63e68703782f0f93a22b2dd57729731840632865d2e116b1f57d2fa3af2e59e7: -------------------------------------------------------------------------------- 1 | // /redirect -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/63e68703782f0f93a22b2dd57729731840632865d2e116b1f57d2fa3af2e59e7.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "location": "https://another.example.com/path/mod.ts" 4 | }, 5 | "url": "https://example.com/redirect" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/70456f90a5c5a173c9d5bc68b36f661a4061b3cb7850e4b9675557a64513deb0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/example.com/70456f90a5c5a173c9d5bc68b36f661a4061b3cb7850e4b9675557a64513deb0 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/7617203222d94a074bea3e57a893d74af5546f17c1f90760f37f46299faf0cb0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/example.com/7617203222d94a074bea3e57a893d74af5546f17c1f90760f37f46299faf0cb0 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/7617203222d94a074bea3e57a893d74af5546f17c1f90760f37f46299faf0cb0.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": {}, 3 | "url": "https://example.com/x-typescript-types.d.ts" 4 | } 5 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/80f9458ee4d37ba8e56a92d01e5cbd229f10e8cf4f3ab31823c289cf031c630d: -------------------------------------------------------------------------------- 1 | // /without-extension-name -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/80f9458ee4d37ba8e56a92d01e5cbd229f10e8cf4f3ab31823c289cf031c630d.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": {}, 3 | "url": "https://example.com/without-extension-name" 4 | } 5 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/8afd52da760dab7f2deda4b7453197f50421f310372c5da3f3847ffd062fa1cf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/example.com/8afd52da760dab7f2deda4b7453197f50421f310372c5da3f3847ffd062fa1cf -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/8afd52da760dab7f2deda4b7453197f50421f310372c5da3f3847ffd062fa1cf.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": {}, 3 | "url": "https://example.com/esm/mod.ts" 4 | } 5 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/91eadc577dcd94ede732f546c6725fb79d9d24924505c05a22f657ec31c78f31: -------------------------------------------------------------------------------- 1 | // /content-type -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/91eadc577dcd94ede732f546c6725fb79d9d24924505c05a22f657ec31c78f31.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "content-type": "text/javascript;charset=UTF-8" 4 | }, 5 | "url": "https://example.com/content-type" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/933405cb905c548e870daee56d0589b7dd8e146c0cdbd5f16a959f8227c1fe06: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/example.com/933405cb905c548e870daee56d0589b7dd8e146c0cdbd5f16a959f8227c1fe06 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/933405cb905c548e870daee56d0589b7dd8e146c0cdbd5f16a959f8227c1fe06.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": {}, 3 | "url": "https://example.com/demo/mod.ts" 4 | } 5 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/94b9af04676e29b71da17af190d513d298fabcdeeeaddf90ee36000fed3f534a: -------------------------------------------------------------------------------- 1 | // /content-type-typescript -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/94b9af04676e29b71da17af190d513d298fabcdeeeaddf90ee36000fed3f534a.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "content-type": "text/typescript" 4 | }, 5 | "url": "https://example.com/content-type-typescript" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/b09ad5614eb13279b5858f2bb3eed7f13770a6a7a08140e19d9e5a00a4699af0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/example.com/b09ad5614eb13279b5858f2bb3eed7f13770a6a7a08140e19d9e5a00a4699af0 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/b09ad5614eb13279b5858f2bb3eed7f13770a6a7a08140e19d9e5a00a4699af0.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "x-typescript-types": "/x-typescript-types.d.ts" 4 | }, 5 | "url": "https://example.com/x-typescript-types" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/d2c891d1a65880123bd43fc2fba9a6d094b8de88a9efc3023acd53c961c9f4b5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/example.com/d2c891d1a65880123bd43fc2fba9a6d094b8de88a9efc3023acd53c961c9f4b5 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/d2c891d1a65880123bd43fc2fba9a6d094b8de88a9efc3023acd53c961c9f4b5.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "location": "https://example.com/redirect_to_loop" 4 | }, 5 | "url": "https://example.com/redirect_to_loop" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/da88efaa8b70cda7903ddc29b8d4c6ea3015de65329ea393289f4104ae2da941: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/example.com/da88efaa8b70cda7903ddc29b8d4c6ea3015de65329ea393289f4104ae2da941 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/da88efaa8b70cda7903ddc29b8d4c6ea3015de65329ea393289f4104ae2da941.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": {}, 3 | "url": "https://example.com/demo/sub/mod.ts" 4 | } 5 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/f13574acadcffaf55de9aada6cffa50fe178ac8b0ea1bc5aebf022b93e248f98: -------------------------------------------------------------------------------- 1 | // /demo.js -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/example.com/f13574acadcffaf55de9aada6cffa50fe178ac8b0ea1bc5aebf022b93e248f98.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": {}, 3 | "url": "https://example.com/demo.js" 4 | } 5 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/invalid.com/70456f90a5c5a173c9d5bc68b36f661a4061b3cb7850e4b9675557a64513deb0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/invalid.com/70456f90a5c5a173c9d5bc68b36f661a4061b3cb7850e4b9675557a64513deb0 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/invalid_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/invalid_file -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/invalid_meta.com/1d6ab9051e9ae4e3a9d501b1a4316583ae8ecd51d41d327b514abf0194c14dc4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/invalid_meta.com/1d6ab9051e9ae4e3a9d501b1a4316583ae8ecd51d41d327b514abf0194c14dc4 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/invalid_meta.com/1d6ab9051e9ae4e3a9d501b1a4316583ae8ecd51d41d327b514abf0194c14dc4.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "missing-url": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/invalid_meta.com/2f5c120425d2222c81506ea48f25c608a89272ed179233ab4fd47debb0f5d05f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/invalid_meta.com/2f5c120425d2222c81506ea48f25c608a89272ed179233ab4fd47debb0f5d05f -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/invalid_meta.com/2f5c120425d2222c81506ea48f25c608a89272ed179233ab4fd47debb0f5d05f.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://invalid_meta.com/missing_headers" 3 | } 4 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/0278046a318a368d7a8da89fdf59c60892dbf3498b996b36f11faf7d74d141c8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/redirect.com/0278046a318a368d7a8da89fdf59c60892dbf3498b996b36f11faf7d74d141c8 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/0278046a318a368d7a8da89fdf59c60892dbf3498b996b36f11faf7d74d141c8.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "location": "./result" 4 | }, 5 | "url": "https://redirect.com/redirect_to_relative" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/1d037d3d472f7c85f4aa26767ec20e9114fb42bdfb8c9e4b46332a3b2f47589d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/redirect.com/1d037d3d472f7c85f4aa26767ec20e9114fb42bdfb8c9e4b46332a3b2f47589d -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/1d037d3d472f7c85f4aa26767ec20e9114fb42bdfb8c9e4b46332a3b2f47589d.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "location": "bb:aa@123" 4 | }, 5 | "url": "https://redirect.com/invalid_location" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/276c3ca0faeb1836b15cda49f57e0c03fde69133b73c424790a91cca8baa4f93: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/redirect.com/276c3ca0faeb1836b15cda49f57e0c03fde69133b73c424790a91cca8baa4f93 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/276c3ca0faeb1836b15cda49f57e0c03fde69133b73c424790a91cca8baa4f93.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "location": "/circle" 4 | }, 5 | "url": "https://redirect.com/circle" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/39bce9e2269e45937f28bc4ff60fb3add9df7e0048078c06dec04c490a90bf9f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/redirect.com/39bce9e2269e45937f28bc4ff60fb3add9df7e0048078c06dec04c490a90bf9f -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/39bce9e2269e45937f28bc4ff60fb3add9df7e0048078c06dec04c490a90bf9f.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": {}, 3 | "url": "https://redirect.com/entry" 4 | } 5 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/63e68703782f0f93a22b2dd57729731840632865d2e116b1f57d2fa3af2e59e7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/redirect.com/63e68703782f0f93a22b2dd57729731840632865d2e116b1f57d2fa3af2e59e7 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/63e68703782f0f93a22b2dd57729731840632865d2e116b1f57d2fa3af2e59e7.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "location": "/result" 4 | }, 5 | "url": "https://redirect.com/redirect" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/6a3cfff3f701f7e3e2611e61d98e9b04ec6a12fe2a72418eb96b929d3836dfd9: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/redirect.com/6a3cfff3f701f7e3e2611e61d98e9b04ec6a12fe2a72418eb96b929d3836dfd9 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/6a3cfff3f701f7e3e2611e61d98e9b04ec6a12fe2a72418eb96b929d3836dfd9.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": {}, 3 | "url": "https://redirect.com/result" 4 | } 5 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/881c89ae27a3543d72b594b4d48c8c7020c0f1fd8250d2d83bbd5837f02664ae: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/redirect.com/881c89ae27a3543d72b594b4d48c8c7020c0f1fd8250d2d83bbd5837f02664ae -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/881c89ae27a3543d72b594b4d48c8c7020c0f1fd8250d2d83bbd5837f02664ae.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "location": "https://redirect.com/result" 4 | }, 5 | "url": "https://redirect.com/full_url_redirect" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/b8b956180a494f61cbc6cd74b6c48793fd975f9c3e9cd96410042a61c275823b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/redirect.com/b8b956180a494f61cbc6cd74b6c48793fd975f9c3e9cd96410042a61c275823b -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/redirect.com/b8b956180a494f61cbc6cd74b6c48793fd975f9c3e9cd96410042a61c275823b.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": { 3 | "x-typescript-types": "/result" 4 | }, 5 | "url": "https://redirect.com/redirect-by-x-typescript-types" 6 | } 7 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/unknown_module.com/27e936010957fa71c39ae46b56a40b4b4c5b388f50d950637e4522708a340663: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/https/unknown_module.com/27e936010957fa71c39ae46b56a40b4b4c5b388f50d950637e4522708a340663 -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/https/unknown_module.com/27e936010957fa71c39ae46b56a40b4b4c5b388f50d950637e4522708a340663.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": {}, 3 | "url": "https://unknown_module.com/unknown_module_without_extension" 4 | } 5 | -------------------------------------------------------------------------------- /__test__/deno_dir_manifest/deps/invalid_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/typescript-deno-plugin/dba8d8a970c7705ecbdb324440123f0ce6a88983/__test__/deno_dir_manifest/deps/invalid_file -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | testPathIgnorePatterns: ["/node_modules/", "out", "lib"] 5 | }; 6 | -------------------------------------------------------------------------------- /lib/lib.webworker.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2020 justjavac. All rights reserved. MIT license. 2 | 3 | /// 4 | 5 | declare function onerror(e: ErrorEvent): void; 6 | declare function onmessage(e: MessageEvent): void; 7 | declare function onmessageerror(e: MessageEvent): void; 8 | declare function postMessage(message: any, transfer: ArrayBuffer[]): void; 9 | declare function postMessage(message: any, options?: PostMessageOptions): void; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-deno-plugin", 3 | "version": "1.31.0", 4 | "description": "Deno language service plugin for TypeScript", 5 | "main": "out/index.js", 6 | "types": "out/index.d.ts", 7 | "scripts": { 8 | "prepublishOnly": "tsc", 9 | "compile": "tsc", 10 | "types": "deno types --unstable > lib/lib.deno.d.ts", 11 | "check": "deno fmt src --check", 12 | "format": "deno fmt src", 13 | "test": "jest --coverage" 14 | }, 15 | "keywords": [ 16 | "deno", 17 | "typescript" 18 | ], 19 | "author": "justjavac ", 20 | "license": "MIT", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/justjavac/typescript-deno-plugin.git" 24 | }, 25 | "dependencies": { 26 | "import-maps": "^0.2.1", 27 | "merge-deep": "^3.0.2" 28 | }, 29 | "devDependencies": { 30 | "@types/jest": "^25.2.3", 31 | "@types/node": "^14.0.1", 32 | "jest": "^26.0.1", 33 | "ts-jest": "^26.1.0", 34 | "typescript": "^3.9.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/code_fixes/import_fixes.ts: -------------------------------------------------------------------------------- 1 | import { CodeFixAction } from "typescript/lib/tsserverlibrary"; 2 | 3 | import { registerCodeFix } from "../codefix_provider"; 4 | import { HashMeta } from "../module_resolver/hash_meta"; 5 | 6 | export const importFixName = "import"; 7 | // const importFixId = "fixMissingImport"; 8 | 9 | const errorCodes: readonly number[] = [ 10 | 2304, // Diagnostics.Cannot_find_name_0.code, 11 | 2552, // Diagnostics.Cannot_find_name_0_Did_you_mean_1.code, 12 | 2663, // Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code, 13 | 2662, // Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0.code, 14 | 2503, // Diagnostics.Cannot_find_namespace_0.code, 15 | 2686, // Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code, 16 | 2693, // Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here.code, 17 | ]; 18 | 19 | function replaceCodeActions(codeFixActions: readonly CodeFixAction[]): void { 20 | for (const codeAction of codeFixActions) { 21 | if (codeAction.fixName !== importFixName) { 22 | continue; 23 | } 24 | 25 | const matchs = codeAction.description.match( 26 | /\.\..+deno\/deps\/https?\/.+\/\w{64}/, 27 | ); 28 | if (matchs == null || matchs.length === 0) { 29 | continue; 30 | } 31 | 32 | const originImport = matchs[0]; 33 | const meta = HashMeta.create(`${originImport}.metadata.json`); 34 | if (meta == null) { 35 | continue; 36 | } 37 | 38 | const newImport = meta.url.href; 39 | codeAction.description = codeAction.description.replace( 40 | originImport, 41 | newImport, 42 | ); 43 | 44 | for (const change of codeAction.changes) { 45 | for (const textChange of change.textChanges) { 46 | textChange.newText = textChange.newText.replace( 47 | originImport, 48 | newImport, 49 | ); 50 | } 51 | } 52 | } 53 | } 54 | 55 | registerCodeFix({ 56 | errorCodes, 57 | replaceCodeActions, 58 | }); 59 | -------------------------------------------------------------------------------- /src/code_fixes/index.ts: -------------------------------------------------------------------------------- 1 | import "./import_fixes"; 2 | -------------------------------------------------------------------------------- /src/codefix_provider.ts: -------------------------------------------------------------------------------- 1 | import { CodeFixAction } from "typescript/lib/tsserverlibrary"; 2 | 3 | export interface CodeFixRegistration { 4 | errorCodes: readonly number[]; 5 | // fixIds: readonly string[], 6 | replaceCodeActions: (codeFixActions: readonly CodeFixAction[]) => void; 7 | } 8 | 9 | export const errorCodeToFixes: Map< 10 | number, 11 | Pick[] 12 | > = new Map(); 13 | 14 | export function registerCodeFix(reg: CodeFixRegistration) { 15 | for (const error of reg.errorCodes) { 16 | if (errorCodeToFixes.has(error)) { 17 | errorCodeToFixes.get(error)!.push(reg); 18 | } else { 19 | errorCodeToFixes.set(error, [reg]); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/deno_modules.ts: -------------------------------------------------------------------------------- 1 | import ts from "typescript/lib/tsserverlibrary"; 2 | 3 | import { parseCompileHint } from "./deno_type_hint"; 4 | 5 | interface Comment extends ts.CommentRange { 6 | text: string; 7 | } 8 | 9 | export type Position = { 10 | line: number; // zero base 11 | character: number; // zero base 12 | }; 13 | 14 | export type Range = { 15 | start: Position; 16 | end: Position; 17 | }; 18 | 19 | export type Hint = { 20 | text: string; 21 | range: Range; 22 | contentRange: Range; 23 | }; 24 | 25 | export type ImportModule = { 26 | moduleName: string; 27 | hint?: Hint; // if import module with @deno-types="xxx" hint 28 | location: Range; 29 | start: number; 30 | length: number; 31 | leadingComments?: Comment[]; 32 | trailingComments?: Comment[]; 33 | }; 34 | 35 | export function getImportModules(sourceFile: ts.SourceFile): ImportModule[] { 36 | const moduleNodes: ts.LiteralLikeNode[] = []; 37 | 38 | function delint(SourceFile: ts.SourceFile) { 39 | function delintNode(node: ts.Node) { 40 | let moduleNode: ts.LiteralLikeNode | null = null; 41 | 42 | // import('xxx') 43 | if (ts.isCallExpression(node)) { 44 | const expression = node.expression; 45 | const args = node.arguments; 46 | const isDynamicImport = expression.kind === ts.SyntaxKind.ImportKeyword; 47 | /* istanbul ignore else */ 48 | if (isDynamicImport) { 49 | const argv = args[0] as ts.StringLiteral; 50 | 51 | /* istanbul ignore else */ 52 | if (argv && ts.isStringLiteral(argv)) { 53 | moduleNode = argv; 54 | } 55 | } 56 | } // import ts = require('ts') 57 | else if (ts.isImportEqualsDeclaration(node)) { 58 | const ref = node.moduleReference; 59 | 60 | /* istanbul ignore else */ 61 | if ( 62 | ts.isExternalModuleReference(ref) && 63 | ref.expression && 64 | ts.isStringLiteral(ref.expression) 65 | ) { 66 | moduleNode = ref.expression; 67 | } 68 | } // import * as from 'xx' 69 | // import 'xx' 70 | // import xx from 'xx' 71 | else if (ts.isImportDeclaration(node)) { 72 | const spec = node.moduleSpecifier; 73 | /* istanbul ignore else */ 74 | if (ts.isStringLiteral(spec)) { 75 | moduleNode = spec; 76 | } 77 | } // export { window } from "xxx"; 78 | // export * from "xxx"; 79 | // export * as xxx from "xxx"; 80 | else if (ts.isExportDeclaration(node)) { 81 | const exportSpec = node.moduleSpecifier; 82 | /* istanbul ignore else */ 83 | if (exportSpec && ts.isStringLiteral(exportSpec)) { 84 | moduleNode = exportSpec; 85 | } 86 | } 87 | 88 | if (moduleNode) { 89 | moduleNodes.push(moduleNode); 90 | } 91 | 92 | ts.forEachChild(node, delintNode); 93 | } 94 | 95 | delintNode(SourceFile); 96 | } 97 | 98 | // delint it 99 | delint(sourceFile); 100 | 101 | const text: string = sourceFile.getFullText(); 102 | 103 | const getComments = ( 104 | node: ts.Node, 105 | isTrailing: boolean, 106 | ): Comment[] | undefined => { 107 | /* istanbul ignore else */ 108 | if (node.parent) { 109 | const nodePos: number = isTrailing ? node.end : node.pos; 110 | const parentPos: number = isTrailing ? node.parent.end : node.parent.pos; 111 | 112 | if ( 113 | node.parent.kind === ts.SyntaxKind.SourceFile || 114 | nodePos !== parentPos 115 | ) { 116 | const comments: ts.CommentRange[] | undefined = isTrailing 117 | ? ts.getTrailingCommentRanges(sourceFile.text, nodePos) 118 | : ts.getLeadingCommentRanges(sourceFile.text, nodePos); 119 | 120 | if (Array.isArray(comments)) { 121 | return comments.map((v) => { 122 | const target: Comment = { 123 | ...v, 124 | text: text.substring(v.pos, v.end), 125 | }; 126 | 127 | return target; 128 | }); 129 | } 130 | 131 | return undefined; 132 | } 133 | } 134 | }; 135 | 136 | const modules: ImportModule[] = sourceFile.typeReferenceDirectives 137 | .map((directive: ts.FileReference) => { 138 | const start = sourceFile.getLineAndCharacterOfPosition(directive.pos); 139 | const end = sourceFile.getLineAndCharacterOfPosition(directive.end); 140 | 141 | const module: ImportModule = { 142 | moduleName: directive.fileName, 143 | location: { start, end }, 144 | start: directive.pos, 145 | length: directive.end - directive.pos, 146 | }; 147 | 148 | return module; 149 | }) 150 | .concat( 151 | moduleNodes.map((node) => { 152 | const numberOfSpaces = Math.abs( 153 | // why plus 2? 154 | // because `moduleNode.text` only contain the plaintext without two quotes 155 | // eg `import "./test"` 156 | node.end - node.pos - (node.text.length + 2), 157 | ); 158 | 159 | const startPosition = node.pos + numberOfSpaces + 1; // +1 to remove quotes 160 | const endPosition = startPosition + node.text.length; 161 | 162 | const start = sourceFile.getLineAndCharacterOfPosition(startPosition); 163 | const end = sourceFile.getLineAndCharacterOfPosition(endPosition); 164 | 165 | const location = { 166 | start, 167 | end, 168 | }; 169 | 170 | const leadingComments = getComments(node.parent, false); 171 | const trailingComments = getComments(node.parent, true); 172 | 173 | const module: ImportModule = { 174 | moduleName: node.text, 175 | location, 176 | start: startPosition, 177 | length: endPosition - startPosition, 178 | }; 179 | 180 | if (trailingComments) { 181 | module.trailingComments = trailingComments; 182 | } 183 | 184 | if (leadingComments) { 185 | module.leadingComments = leadingComments; 186 | // get the last comment 187 | const comment = 188 | module.leadingComments[module.leadingComments.length - 1]; 189 | 190 | const hint = parseCompileHint(sourceFile, comment); 191 | 192 | module.hint = hint; 193 | } 194 | 195 | return module; 196 | }), 197 | ); 198 | 199 | return modules; 200 | } 201 | -------------------------------------------------------------------------------- /src/deno_type_hint.test.ts: -------------------------------------------------------------------------------- 1 | import ts from "typescript"; 2 | 3 | import { getDenoCompileHint, Range } from "./deno_type_hint"; 4 | 5 | test("core / deno_type_hint: without compile hint", async () => { 6 | const sourceFile = ts.createSourceFile( 7 | "./test.ts", 8 | `console.log(123)`, 9 | ts.ScriptTarget.ESNext, 10 | ); 11 | const comments = getDenoCompileHint(sourceFile); 12 | 13 | expect(comments).toHaveLength(0); 14 | }); 15 | 16 | test("core / deno_type_hint: with compile hint", async () => { 17 | const sourceFile = ts.createSourceFile( 18 | "./test.ts", 19 | `// @deno-types="./foo.d.ts" 20 | import "./foo.ts" 21 | `, 22 | ts.ScriptTarget.ESNext, 23 | true, 24 | ts.ScriptKind.TSX, 25 | ); 26 | const [comment] = getDenoCompileHint(sourceFile); 27 | 28 | expect(comment).not.toBe(undefined); 29 | expect(comment.text).toEqual(`./foo.d.ts`); 30 | expect(comment.range).toEqual({ 31 | start: { line: 0, character: 0 }, 32 | end: { line: 0, character: 27 }, 33 | } as Range); 34 | expect(comment.contentRange).toEqual({ 35 | start: { line: 0, character: 16 }, 36 | end: { line: 0, character: 26 }, 37 | } as Range); 38 | }); 39 | 40 | test("core / deno_type_hint: with compile hint", async () => { 41 | const sourceFile = ts.createSourceFile( 42 | "./test.ts", 43 | `// @deno-types="/absolute/path/to/foo.d.ts" 44 | import "./foo.ts" 45 | `, 46 | ts.ScriptTarget.ESNext, 47 | true, 48 | ts.ScriptKind.TSX, 49 | ); 50 | const [comment] = getDenoCompileHint(sourceFile); 51 | 52 | expect(comment).not.toBe(undefined); 53 | expect(comment.text).toEqual(`/absolute/path/to/foo.d.ts`); 54 | }); 55 | 56 | test("core / deno_type_hint: with compile hint 1", async () => { 57 | const sourceFile = ts.createSourceFile( 58 | "./test.ts", 59 | ` 60 | 61 | 62 | // @deno-types="./foo.d.ts" 63 | 64 | 65 | import "./foo.ts" 66 | `, 67 | ts.ScriptTarget.ESNext, 68 | true, 69 | ts.ScriptKind.TSX, 70 | ); 71 | const [comment] = getDenoCompileHint(sourceFile); 72 | 73 | expect(comment).not.toBe(undefined); 74 | expect(comment.text).toEqual(`./foo.d.ts`); 75 | expect(comment.range).toEqual({ 76 | start: { line: 3, character: 0 }, 77 | end: { line: 3, character: 27 }, 78 | } as Range); 79 | expect(comment.contentRange).toEqual({ 80 | start: { line: 3, character: 16 }, 81 | end: { line: 3, character: 26 }, 82 | } as Range); 83 | }); 84 | 85 | test("core / deno_type_hint: with compile hint 2", async () => { 86 | const sourceFile = ts.createSourceFile( 87 | "./test.ts", 88 | `// foo 89 | // bar 90 | // 123 91 | // @deno-types="./foo.d.ts" 92 | 93 | /** 94 | * 95 | * 96 | */ 97 | /* prefix */ import "./foo.ts" // hasTrailingNewLine 98 | `, 99 | ts.ScriptTarget.ESNext, 100 | true, 101 | ts.ScriptKind.TSX, 102 | ); 103 | const [comment] = getDenoCompileHint(sourceFile); 104 | 105 | expect(comment).not.toBe(undefined); 106 | expect(comment.text).toEqual(`./foo.d.ts`); 107 | expect(comment.range).toEqual({ 108 | start: { line: 3, character: 0 }, 109 | end: { line: 3, character: 27 }, 110 | } as Range); 111 | expect(comment.contentRange).toEqual({ 112 | start: { line: 3, character: 16 }, 113 | end: { line: 3, character: 26 }, 114 | } as Range); 115 | }); 116 | -------------------------------------------------------------------------------- /src/deno_type_hint.ts: -------------------------------------------------------------------------------- 1 | import ts from "typescript/lib/tsserverlibrary"; 2 | 3 | export interface Position { 4 | line: number; 5 | character: number; 6 | } 7 | 8 | export interface Range { 9 | start: Position; 10 | end: Position; 11 | } 12 | 13 | export const Position = { 14 | /** 15 | * Creates a new Position literal from the given line and character. 16 | * @param line The position's line. 17 | * @param character The position's character. 18 | */ 19 | create(line: number, character: number): Position { 20 | return { line, character }; 21 | }, 22 | }; 23 | 24 | export const Range = { 25 | create(start: Position, end: Position): Range { 26 | return { start, end }; 27 | }, 28 | }; 29 | 30 | export type compileHint = { 31 | text: string; 32 | range: Range; 33 | contentRange: Range; 34 | }; 35 | 36 | export function parseCompileHint( 37 | sourceFile: ts.SourceFile, 38 | comment: ts.CommentRange, 39 | ): compileHint | undefined { 40 | const text = sourceFile.getFullText().substring(comment.pos, comment.end); 41 | const regexp = /@deno-types=['"]([^'"]+)['"]/; 42 | 43 | const matchers = regexp.exec(text); 44 | 45 | if (!matchers) { 46 | return; 47 | } 48 | 49 | const start = sourceFile.getLineAndCharacterOfPosition(comment.pos); 50 | const end = sourceFile.getLineAndCharacterOfPosition(comment.end); 51 | 52 | const moduleNameStart = Position.create( 53 | start.line, 54 | start.character + '// @deno-types="'.length, 55 | ); 56 | const moduleNameEnd = Position.create(end.line, end.character - '"'.length); 57 | 58 | const moduleName = matchers[1]; 59 | 60 | return { 61 | text: moduleName, 62 | range: Range.create(start, end), 63 | contentRange: Range.create(moduleNameStart, moduleNameEnd), 64 | }; 65 | } 66 | 67 | /** 68 | * Get Deno compile hint from a source file 69 | * @param ts 70 | */ 71 | export function getDenoCompileHint( 72 | sourceFile: ts.SourceFile, 73 | pos = 0, 74 | ): compileHint[] { 75 | const denoTypesComments: compileHint[] = []; 76 | 77 | const comments = ts.getLeadingCommentRanges(sourceFile.getFullText(), pos) || 78 | []; 79 | 80 | for (const comment of comments) { 81 | if (comment.hasTrailingNewLine) { 82 | const text = sourceFile 83 | .getFullText() 84 | .substring(comment.pos, comment.end); 85 | const regexp = /@deno-types=['"]([^'"]+)['"]/; 86 | 87 | const matchers = regexp.exec(text); 88 | 89 | if (matchers) { 90 | const compileHint = parseCompileHint(sourceFile, comment); 91 | 92 | /* istanbul ignore else */ 93 | if (compileHint) { 94 | denoTypesComments.push(compileHint); 95 | } 96 | } 97 | } 98 | } 99 | 100 | return denoTypesComments; 101 | } 102 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // modified from https://github.com/Microsoft/typescript-tslint-plugin 2 | import ts_module, { 3 | CompilerOptions, 4 | } from "typescript/lib/tsserverlibrary"; 5 | 6 | import { Logger } from "./logger"; 7 | 8 | import "./code_fixes"; 9 | import { getTsUtils } from "./ts_utils"; 10 | 11 | import getCompletionsAtPositionWrapper from "./tsls_wrappers/completions"; 12 | import getCompletionEntryDetailsnWrapper from "./tsls_wrappers/completion_entry_details"; 13 | import getSemanticDiagnosticsWrapper from "./tsls_wrappers/semantic_diagnostics"; 14 | import getCodeFixesAtPositionWrapper from "./tsls_wrappers/code_fixes"; 15 | 16 | import resolveModuleNamesWrapper from "./tsls_host_wrappers/resolve_module_names"; 17 | import getCompilationSettingsWrapper from "./tsls_host_wrappers/compilation_settings"; 18 | import resolveTypeReferenceDirectivesWrapper from "./tsls_host_wrappers/resolve_type_reference_directives"; 19 | import getScriptFileNamesWrapper from "./tsls_host_wrappers/script_file_names"; 20 | 21 | let logger: Logger; 22 | let pluginInfo: ts_module.server.PluginCreateInfo; 23 | 24 | type DenoPluginConfig = { 25 | enable: boolean; 26 | importmap?: string; 27 | tsconfig?: string; 28 | }; 29 | 30 | const config: DenoPluginConfig = { 31 | enable: true, 32 | }; 33 | 34 | let projectDirectory: string; 35 | 36 | module.exports = function init( 37 | { typescript }: { typescript: typeof ts_module }, 38 | ) { 39 | // see https://github.com/denoland/deno/blob/2debbdacb935cfe1eb7bb8d1f40a5063b339d90b/js/compiler.ts#L159-L170 40 | const OPTIONS: CompilerOptions = { 41 | allowJs: true, 42 | checkJs: true, 43 | esModuleInterop: true, 44 | module: typescript.ModuleKind.ESNext, 45 | moduleResolution: typescript.ModuleResolutionKind.NodeJs, 46 | jsx: typescript.JsxEmit.React, 47 | noEmit: true, 48 | strict: true, 49 | outDir: "$deno$", 50 | removeComments: true, 51 | stripComments: true, 52 | resolveJsonModule: true, 53 | sourceMap: true, 54 | target: typescript.ScriptTarget.ESNext, 55 | typeRoots: [], 56 | }; 57 | 58 | const OPTIONS_OVERWRITE_BY_DENO: CompilerOptions = { 59 | allowNonTsExtensions: false, 60 | jsx: OPTIONS.jsx, 61 | module: OPTIONS.module, 62 | moduleResolution: OPTIONS.moduleResolution, 63 | resolveJsonModule: OPTIONS.resolveJsonModule, 64 | strict: OPTIONS.strict, 65 | noEmit: OPTIONS.noEmit, 66 | noEmitHelpers: OPTIONS.noEmitHelpers, 67 | target: typescript.ScriptTarget.ESNext, 68 | }; 69 | 70 | return { 71 | create(info: ts_module.server.PluginCreateInfo): ts_module.LanguageService { 72 | logger = Logger.forPlugin(info); 73 | logger.info("plugin created."); 74 | 75 | pluginInfo = info; 76 | const tsLs = info.languageService; 77 | const tsLsHost = info.languageServiceHost; 78 | const project = info.project; 79 | 80 | const tsUtils = getTsUtils(tsLs); 81 | 82 | Object.assign(config, info.config); 83 | 84 | if (!config.enable) { 85 | logger.info("plugin disabled."); 86 | return tsLs; 87 | } 88 | 89 | projectDirectory = project.getCurrentDirectory(); 90 | // TypeScript plugins have a `cwd` of `/`, which causes issues with import resolution. 91 | process.chdir(projectDirectory); 92 | 93 | // ref https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#customizing-module-resolution 94 | tsLsHost.resolveModuleNames = resolveModuleNamesWrapper( 95 | tsLsHost, 96 | logger, 97 | config, 98 | typescript, 99 | projectDirectory, 100 | ); 101 | tsLsHost.resolveTypeReferenceDirectives = 102 | resolveTypeReferenceDirectivesWrapper(tsLsHost, config, logger); 103 | tsLsHost.getCompilationSettings = getCompilationSettingsWrapper( 104 | tsLsHost, 105 | config, 106 | OPTIONS, 107 | OPTIONS_OVERWRITE_BY_DENO, 108 | ); 109 | tsLsHost.getScriptFileNames = getScriptFileNamesWrapper( 110 | tsLsHost, 111 | config, 112 | logger, 113 | ); 114 | 115 | const getCompletionsAtPosition = getCompletionsAtPositionWrapper( 116 | projectDirectory, 117 | config, 118 | tsLs, 119 | tsUtils, 120 | ); 121 | const getCompletionEntryDetails = getCompletionEntryDetailsnWrapper( 122 | tsLs, 123 | config, 124 | ); 125 | const getSemanticDiagnostics = getSemanticDiagnosticsWrapper( 126 | tsLs, 127 | config, 128 | logger, 129 | projectDirectory, 130 | ); 131 | // TODO(justjavac): maybe also `getCombinedCodeFix` 132 | const getCodeFixesAtPosition = getCodeFixesAtPositionWrapper(tsLs); 133 | 134 | const proxy: ts_module.LanguageService = { 135 | ...tsLs, 136 | 137 | getCompletionsAtPosition, 138 | getCompletionEntryDetails, 139 | getSemanticDiagnostics, 140 | getCodeFixesAtPosition, 141 | }; 142 | 143 | return proxy; 144 | }, 145 | 146 | onConfigurationChanged(c: DenoPluginConfig) { 147 | logger.info("config change to:\n" + JSON.stringify(c, null, " ")); 148 | Object.assign(config, c); 149 | 150 | pluginInfo.project.markAsDirty(); 151 | pluginInfo.project.refreshDiagnostics(); 152 | pluginInfo.project.updateGraph(); 153 | pluginInfo.languageService.getProgram()?.emit(); 154 | }, 155 | }; 156 | }; 157 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | // modified from https://github.com/Microsoft/typescript-tslint-plugin 2 | import ts_module from "typescript/lib/tsserverlibrary"; 3 | 4 | const pluginId = "typescript-deno-plugin"; 5 | 6 | export class Logger { 7 | public static forPlugin(info: ts_module.server.PluginCreateInfo) { 8 | return new Logger(info.project.projectService.logger); 9 | } 10 | 11 | private constructor(private readonly logger: ts_module.server.Logger) {} 12 | 13 | public info(message: string) { 14 | this.logger.info(`[${pluginId}] ${message}`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/module_resolver/hash_meta.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | import { HashMeta, Type } from "./hash_meta"; 4 | 5 | const TEST_DIR = path.join(__dirname, "..", "..", "__test__"); 6 | const denoDir = path.join(TEST_DIR, "deno_dir_manifest"); 7 | 8 | beforeAll(() => { 9 | process.env["DENO_DIR"] = denoDir; 10 | }); 11 | 12 | afterAll(() => { 13 | process.env["DENO_DIR"] = undefined; 14 | }); 15 | 16 | test("hash_meta", () => { 17 | const cacheFilepath = path.join( 18 | denoDir, 19 | "deps", 20 | "https", 21 | "example.com", 22 | "933405cb905c548e870daee56d0589b7dd8e146c0cdbd5f16a959f8227c1fe06", 23 | ); 24 | const meta = HashMeta.create(cacheFilepath + ".metadata.json") as HashMeta; 25 | expect(meta).not.toBe(undefined); 26 | expect(meta.filepath).toEqual(cacheFilepath + ".metadata.json"); 27 | expect(meta.type).toEqual(Type.TypeScript); 28 | expect(meta.extension).toEqual(".ts"); 29 | expect(meta.url.href).toEqual("https://example.com/demo/mod.ts"); 30 | expect(meta.destinationFilepath).toEqual(cacheFilepath); 31 | }); 32 | 33 | test("hash_meta with javascript file", () => { 34 | const cacheFilepath = path.join( 35 | denoDir, 36 | "deps", 37 | "https", 38 | "example.com", 39 | "f13574acadcffaf55de9aada6cffa50fe178ac8b0ea1bc5aebf022b93e248f98", 40 | ); 41 | 42 | const meta = HashMeta.create(cacheFilepath + ".metadata.json") as HashMeta; 43 | expect(meta).not.toBe(undefined); 44 | expect(meta.filepath).toEqual(cacheFilepath + ".metadata.json"); 45 | expect(meta.type).toEqual(Type.JavaScript); 46 | expect(meta.extension).toEqual(".js"); 47 | expect(meta.url.href).toEqual("https://example.com/demo.js"); 48 | expect(meta.destinationFilepath).toEqual(cacheFilepath); 49 | }); 50 | 51 | test("hash_meta without extension name", () => { 52 | const cacheFilepath = path.join( 53 | denoDir, 54 | "deps", 55 | "https", 56 | "example.com", 57 | "80f9458ee4d37ba8e56a92d01e5cbd229f10e8cf4f3ab31823c289cf031c630d", 58 | ); 59 | 60 | const meta = HashMeta.create(cacheFilepath + ".metadata.json") as HashMeta; 61 | expect(meta).not.toBe(undefined); 62 | expect(meta.filepath).toEqual(cacheFilepath + ".metadata.json"); 63 | expect(meta.type).toEqual(Type.PlainText); 64 | expect(meta.extension).toEqual(""); 65 | expect(meta.url.href).toEqual("https://example.com/without-extension-name"); 66 | expect(meta.destinationFilepath).toEqual(cacheFilepath); 67 | }); 68 | 69 | test("hash_meta if have content-type", () => { 70 | const cacheFilepath = path.join( 71 | denoDir, 72 | "deps", 73 | "https", 74 | "example.com", 75 | "91eadc577dcd94ede732f546c6725fb79d9d24924505c05a22f657ec31c78f31", 76 | ); 77 | 78 | const meta = HashMeta.create(cacheFilepath + ".metadata.json") as HashMeta; 79 | expect(meta).not.toBe(undefined); 80 | expect(meta.filepath).toEqual(cacheFilepath + ".metadata.json"); 81 | expect(meta.type).toEqual(Type.JavaScript); 82 | expect(meta.extension).toEqual(".js"); 83 | expect(meta.url.href).toEqual("https://example.com/content-type"); 84 | expect(meta.destinationFilepath).toEqual(cacheFilepath); 85 | }); 86 | 87 | test("hash_meta if have content-type typescript", () => { 88 | const cacheFilepath = path.join( 89 | denoDir, 90 | "deps", 91 | "https", 92 | "example.com", 93 | "94b9af04676e29b71da17af190d513d298fabcdeeeaddf90ee36000fed3f534a", 94 | ); 95 | 96 | const meta = HashMeta.create(cacheFilepath + ".metadata.json") as HashMeta; 97 | expect(meta).not.toBe(undefined); 98 | expect(meta.filepath).toEqual(cacheFilepath + ".metadata.json"); 99 | expect(meta.type).toEqual(Type.TypeScript); 100 | expect(meta.extension).toEqual(".ts"); 101 | expect(meta.url.href).toEqual("https://example.com/content-type-typescript"); 102 | expect(meta.destinationFilepath).toEqual(cacheFilepath); 103 | }); 104 | 105 | test("hash_meta: if not exist", () => { 106 | const notExistCache = path.join( 107 | denoDir, 108 | "deps", 109 | "https", 110 | "example.com", 111 | "933405cb905c548e870daee56d0589b7dd8e146c0cdbd5f16a959f8227c1fxxx", 112 | ); 113 | 114 | const meta = HashMeta.create(notExistCache + ".metadata.json"); 115 | expect(meta).toBe(undefined); 116 | }); 117 | 118 | test("hash_meta: if meta file missing url", () => { 119 | const notExistCache = path.join( 120 | denoDir, 121 | "deps", 122 | "https", 123 | "invalid_mta.com", 124 | "1d6ab9051e9ae4e3a9d501b1a4316583ae8ecd51d41d327b514abf0194c14dc4", 125 | ); 126 | 127 | const meta = HashMeta.create(notExistCache + ".metadata.json"); 128 | expect(meta).toBe(undefined); 129 | }); 130 | 131 | test("hash_meta: if meta file missing headers", () => { 132 | const notExistCache = path.join( 133 | denoDir, 134 | "deps", 135 | "https", 136 | "invalid_mta.com", 137 | "2f5c120425d2222c81506ea48f25c608a89272ed179233ab4fd47debb0f5d05f", 138 | ); 139 | 140 | const meta = HashMeta.create(notExistCache + ".metadata.json"); 141 | expect(meta).toBe(undefined); 142 | }); 143 | -------------------------------------------------------------------------------- /src/module_resolver/hash_meta.ts: -------------------------------------------------------------------------------- 1 | /// Copyright axetroy 2 | import * as fs from "fs"; 3 | import * as path from "path"; 4 | import { URL } from "url"; 5 | 6 | import { pathExistsSync, normalizeFilepath } from "../utils"; 7 | import { DenoExtension } from "./types"; 8 | 9 | type HTTPHeaders = { [key: string]: string }; 10 | 11 | type MetaFileContent = { 12 | url: string; 13 | headers: HTTPHeaders; 14 | }; 15 | 16 | export enum Type { 17 | JavaScript = "javascript", 18 | JavaScriptReact = "javascriptreact", 19 | TypeScript = "typescript", 20 | TypeScriptReact = "typescriptreact", 21 | JSON = "json", 22 | WebAssembly = "WebAssembly", 23 | PlainText = "plaintext", 24 | } 25 | 26 | interface HashMetaInterface { 27 | filepath: string; // the filepath of this meta file 28 | destinationFilepath: string; // the file that this meta file provide 29 | url: URL; 30 | headers: HTTPHeaders; 31 | type: Type; 32 | } 33 | 34 | const extNameMap: { [key: string]: Type } = { 35 | ".ts": Type.TypeScript, 36 | ".tsx": Type.TypeScriptReact, 37 | ".js": Type.JavaScript, 38 | ".jsx": Type.JavaScriptReact, 39 | ".mjs": Type.JavaScript, 40 | ".json": Type.JSON, 41 | ".wasm": Type.WebAssembly, 42 | }; 43 | 44 | const contentTypeMap: [string[], Type][] = [ 45 | [ 46 | ["text/typescript", "application/typescript", "application/x-typescript"], 47 | Type.TypeScript, 48 | ], 49 | [ 50 | [ 51 | "text/javascript", 52 | "application/javascript", 53 | "application/x-javascript", 54 | "text/ecmascript", 55 | "application/ecmascript", 56 | "text/jscript", 57 | ], 58 | Type.JavaScript, 59 | ], 60 | [["application/json"], Type.JSON], 61 | [["application/wasm"], Type.WebAssembly], 62 | ]; 63 | 64 | export class HashMeta implements HashMetaInterface { 65 | static create(metaFilepath: string): HashMeta | void { 66 | metaFilepath = normalizeFilepath(metaFilepath); 67 | if (!pathExistsSync(metaFilepath)) { 68 | return; 69 | } 70 | const metaMap: MetaFileContent = JSON.parse( 71 | fs.readFileSync(metaFilepath, { encoding: "utf8" }), 72 | ); 73 | 74 | return new HashMeta(metaFilepath, new URL(metaMap.url), metaMap.headers); 75 | } 76 | private constructor( 77 | public filepath: string, 78 | public url: URL, 79 | public headers: HTTPHeaders, 80 | ) {} 81 | get type(): Type { 82 | const extname = path.posix.extname(this.url.pathname); 83 | 84 | if (extname && extNameMap[extname]) { 85 | return extNameMap[extname]; 86 | } 87 | 88 | const contentType = (this.headers["content-type"] || "").toLowerCase(); 89 | 90 | // ref: https://mathiasbynens.be/demo/javascript-mime-type 91 | if (contentType) { 92 | for (const [contentTypes, type] of contentTypeMap) { 93 | // text/javascript;charset=UTF-8 94 | const arr = contentType.split(";"); 95 | for (const _contentType of arr) { 96 | if (contentTypes.includes(_contentType.toLowerCase())) { 97 | return type; 98 | } 99 | } 100 | } 101 | } 102 | 103 | return Type.PlainText; 104 | } 105 | get extension(): DenoExtension { 106 | const type = this.type; 107 | 108 | switch (type) { 109 | case Type.JavaScript: 110 | return ".js"; 111 | /* istanbul ignore next */ 112 | case Type.JavaScriptReact: 113 | return ".jsx"; 114 | case Type.TypeScript: 115 | if (this.url.pathname.endsWith(".d.ts")) { 116 | return ".d.ts"; 117 | } 118 | return ".ts"; 119 | /* istanbul ignore next */ 120 | case Type.TypeScriptReact: 121 | return ".tsx"; 122 | case Type.JSON: 123 | return ".json"; 124 | /* istanbul ignore next */ 125 | case Type.WebAssembly: 126 | return ".wasm"; 127 | } 128 | 129 | return ""; 130 | } 131 | get destinationFilepath(): string { 132 | return this.filepath.replace(/\.metadata\.json$/, ""); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/module_resolver/local_module_resolver.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | import { pathExistsSync } from "../utils"; 4 | 5 | import { IModuleResolver, DenoExtension, DenoResolvedModule } from "./types"; 6 | 7 | export const localModuleResolver: IModuleResolver = { 8 | resolve( 9 | moduleName: string, 10 | originModuleName: string = moduleName, 11 | ): (undefined | DenoResolvedModule) { 12 | if (!pathExistsSync(moduleName)) { 13 | return; 14 | } 15 | 16 | return { 17 | originModuleName, 18 | filepath: moduleName, 19 | extension: getExtensionFromFile(moduleName), 20 | }; 21 | }, 22 | }; 23 | 24 | export function getExtensionFromFile(filename: string): DenoExtension { 25 | const extName = path.extname(filename); 26 | 27 | if (extName === ".ts") { 28 | if (/\.d\.ts$/.test(filename)) { 29 | return ".d.ts"; 30 | } 31 | } 32 | 33 | return extName as DenoExtension; 34 | } 35 | -------------------------------------------------------------------------------- /src/module_resolver/remote_module_resolver.ts: -------------------------------------------------------------------------------- 1 | import { URL } from "url"; 2 | import path from "path"; 3 | 4 | import { getDenoDepsDir, hashURL, isHttpURL } from "../utils"; 5 | 6 | import { IModuleResolver, DenoResolvedModule } from "./types"; 7 | import { HashMeta } from "./hash_meta"; 8 | import { universalModuleResolver } from "./universal_module_resolver"; 9 | 10 | export const remoteModuleResolver: IModuleResolver = { 11 | resolve( 12 | moduleName: string, 13 | originModuleName: string = moduleName, 14 | ): (undefined | DenoResolvedModule) { 15 | const url = new URL(moduleName); 16 | 17 | const originDir = path.join( 18 | getDenoDepsDir(), 19 | url.protocol.replace(/:$/, ""), // https: -> https 20 | `${url.hostname}${url.port ? `_PORT${url.port}` : ""}`, // hostname.xyz:3000 -> hostname.xyz_PORT3000 21 | ); 22 | 23 | const hash = hashURL(url); 24 | 25 | const metaFilepath = path.join(originDir, `${hash}.metadata.json`); 26 | 27 | const meta = HashMeta.create(metaFilepath); 28 | 29 | if (!meta) { 30 | return; 31 | } 32 | 33 | let redirect = meta.headers.location; 34 | 35 | if (redirect) { 36 | redirect = isHttpURL(redirect) // eg: https://redirect.com/path/to/redirect 37 | ? redirect 38 | : path.posix.isAbsolute(redirect) // eg: /path/to/redirect 39 | ? `${url.protocol}//${url.host}${redirect}` 40 | : // eg: ./path/to/redirect 41 | `${url.protocol}//${url.host}${ 42 | path.posix.resolve( 43 | url.pathname, 44 | redirect, 45 | ) 46 | }`; 47 | 48 | // avoid Circular 49 | if (!isHttpURL(redirect) || redirect === moduleName) { 50 | return; 51 | } 52 | 53 | return universalModuleResolver.resolve(redirect, originModuleName); 54 | } 55 | 56 | const moduleFilepath = path.join(originDir, hash); 57 | 58 | const typescriptTypes = meta.headers["x-typescript-types"]; 59 | if (typescriptTypes) { 60 | const typeModule = universalModuleResolver.resolve( 61 | typescriptTypes, 62 | originModuleName, 63 | ); 64 | 65 | if (typeModule) { 66 | return typeModule; 67 | } 68 | } 69 | 70 | return { 71 | originModuleName, 72 | filepath: moduleFilepath, 73 | extension: meta.extension, 74 | }; 75 | }, 76 | }; 77 | -------------------------------------------------------------------------------- /src/module_resolver/types.ts: -------------------------------------------------------------------------------- 1 | export type DenoExtension = 2 | | ".ts" 3 | | ".tsx" 4 | | ".d.ts" 5 | | ".js" 6 | | ".jsx" 7 | | ".json" 8 | | ".wasm" 9 | | ""; 10 | 11 | export type DenoResolvedModule = { 12 | originModuleName: string; 13 | filepath: string; 14 | extension: DenoExtension; 15 | }; 16 | 17 | export interface IModuleResolver { 18 | resolve( 19 | moduleName: string, 20 | originModuleName?: string, 21 | ): DenoResolvedModule | undefined; 22 | } 23 | -------------------------------------------------------------------------------- /src/module_resolver/universal_module_resolver.ts: -------------------------------------------------------------------------------- 1 | import { isHttpURL } from "../utils"; 2 | 3 | import { IModuleResolver, DenoResolvedModule } from "./types"; 4 | import { remoteModuleResolver } from "./remote_module_resolver"; 5 | import { localModuleResolver } from "./local_module_resolver"; 6 | 7 | export const universalModuleResolver: IModuleResolver = { 8 | resolve( 9 | moduleName: string, 10 | originModuleName: string = moduleName, 11 | ): (undefined | DenoResolvedModule) { 12 | // If import from remote 13 | if (isHttpURL(moduleName)) { 14 | return remoteModuleResolver.resolve( 15 | moduleName, 16 | originModuleName, 17 | ); 18 | } 19 | 20 | return localModuleResolver.resolve( 21 | moduleName, 22 | originModuleName, 23 | ); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/ts_utils.ts: -------------------------------------------------------------------------------- 1 | import ts, { SourceFile, Node } from "typescript/lib/tsserverlibrary"; 2 | 3 | export function getTsUtils(languageService: ts.LanguageService): TsUtils { 4 | function findNode(sourceFile: ts.SourceFile, position: number) { 5 | function find(node: ts.Node): ts.Node | undefined { 6 | if (position >= node.getStart() && position < node.getEnd()) { 7 | return ts.forEachChild(node, find) || node; 8 | } 9 | } 10 | 11 | return find(sourceFile); 12 | } 13 | 14 | return { 15 | getSourceFile(fileName: string) { 16 | const program = languageService.getProgram(); 17 | if (!program) { 18 | throw new Error("language service host does not have program!"); 19 | } 20 | 21 | const s = program.getSourceFile(fileName); 22 | if (!s) { 23 | throw new Error("No source file: " + fileName); 24 | } 25 | return s; 26 | }, 27 | getNode(fileName: string, position: number) { 28 | return findNode(this.getSourceFile(fileName), position); 29 | }, 30 | getNodeText(node: ts.Node, fileName: string) { 31 | const sourceFile = this.getSourceFile(fileName); 32 | const sourceText = sourceFile.text; 33 | return sourceText.slice(node.pos, node.end); 34 | }, 35 | }; 36 | } 37 | 38 | export interface TsUtils { 39 | getSourceFile(fileName: string): SourceFile; 40 | getNode(fileName: string, position: number): Node | undefined; 41 | getNodeText(node: ts.Node, fileName: string): string; 42 | } 43 | -------------------------------------------------------------------------------- /src/tsls_host_wrappers/compilation_settings.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LanguageServiceHost, 3 | CompilerOptions, 4 | } from "typescript/lib/tsserverlibrary"; 5 | import merge from "merge-deep"; 6 | 7 | export default function getCompilationSettingsWrapper( 8 | tsLsHost: LanguageServiceHost, 9 | config: any, 10 | OPTIONS: CompilerOptions, 11 | OPTIONS_OVERWRITE_BY_DENO: CompilerOptions, 12 | ) { 13 | const originalGetCompilationSettings = tsLsHost.getCompilationSettings; 14 | 15 | if (!config.enable) { 16 | return originalGetCompilationSettings; 17 | } 18 | 19 | const getCompilationSettings: typeof tsLsHost.getCompilationSettings = () => { 20 | const projectConfig = originalGetCompilationSettings.call( 21 | tsLsHost, 22 | ); 23 | 24 | if (!config.enable) { 25 | return projectConfig; 26 | } 27 | 28 | const compilationSettings = merge( 29 | merge(OPTIONS, projectConfig), 30 | OPTIONS_OVERWRITE_BY_DENO, 31 | ); 32 | return compilationSettings; 33 | }; 34 | 35 | return getCompilationSettings; 36 | } 37 | -------------------------------------------------------------------------------- /src/tsls_host_wrappers/resolve_module_names.ts: -------------------------------------------------------------------------------- 1 | import ts_module, { 2 | LanguageServiceHost, 3 | ResolvedModuleFull, 4 | } from "typescript/lib/tsserverlibrary"; 5 | import { Logger } from "logger"; 6 | import { 7 | parseImportMapFromFile, 8 | parseModuleName, 9 | resolveDenoModule, 10 | } from "../utils"; 11 | import { getImportModules } from "../deno_modules"; 12 | import { universalModuleResolver } from "../module_resolver/universal_module_resolver"; 13 | 14 | export default function resolveModuleNamesWrapper( 15 | tsLsHost: LanguageServiceHost, 16 | logger: Logger, 17 | config: any, 18 | typescript: typeof ts_module, 19 | projectDirectory: string, 20 | ) { 21 | const originalResolveModuleNames = tsLsHost.resolveModuleNames; 22 | if (!originalResolveModuleNames) { 23 | return; 24 | } 25 | 26 | const resolveModuleNames: typeof tsLsHost.resolveModuleNames = ( 27 | moduleNames: string[], 28 | containingFile: string, 29 | ...rest 30 | ) => { 31 | logger.info("resolveModuleNames"); 32 | if (!config.enable) { 33 | logger.info("plugin disabled."); 34 | return originalResolveModuleNames.call( 35 | tsLsHost, 36 | moduleNames, 37 | containingFile, 38 | ...rest, 39 | ); 40 | } 41 | 42 | const resolvedModules: (ResolvedModuleFull | undefined)[] = []; 43 | 44 | const parsedImportMap = parseImportMapFromFile( 45 | projectDirectory, 46 | config.importmap, 47 | ); 48 | 49 | const content = typescript.sys.readFile(containingFile, "utf8"); 50 | 51 | // handle @deno-types 52 | if (content && content.indexOf("// @deno-types=") >= 0) { 53 | const sourceFile = typescript.createSourceFile( 54 | containingFile, 55 | content, 56 | typescript.ScriptTarget.ESNext, 57 | true, 58 | ); 59 | 60 | const modules = getImportModules(sourceFile); 61 | 62 | for (const m of modules) { 63 | if (m.hint) { 64 | const index = moduleNames.findIndex((v) => v === m.moduleName); 65 | moduleNames[index] = m.hint.text; 66 | } 67 | } 68 | } 69 | 70 | // try resolve typeReferenceDirectives 71 | for (let moduleName of moduleNames) { 72 | const parsedModuleName = parseModuleName( 73 | moduleName, 74 | containingFile, 75 | parsedImportMap, 76 | logger, 77 | ); 78 | 79 | if (parsedModuleName == null) { 80 | logger.info(`module "${moduleName}" can not parsed`); 81 | resolvedModules.push(undefined); 82 | continue; 83 | } 84 | 85 | const resolvedModule = resolveDenoModule(parsedModuleName); 86 | 87 | if (!resolvedModule) { 88 | logger.info(`module "${moduleName}" can not resolved`); 89 | resolvedModules.push(undefined); 90 | continue; 91 | } 92 | 93 | logger.info(`module "${moduleName}" -> ${resolvedModule.filepath}`); 94 | 95 | resolvedModules.push({ 96 | extension: resolvedModule.extension as ts_module.Extension, 97 | isExternalLibraryImport: false, 98 | resolvedFileName: resolvedModule.filepath, 99 | }); 100 | 101 | const content = typescript.sys.readFile(resolvedModule.filepath); 102 | 103 | if (!content) { 104 | continue; 105 | } 106 | 107 | const { typeReferenceDirectives } = typescript.preProcessFile( 108 | content, 109 | true, 110 | true, 111 | ); 112 | 113 | if (!typeReferenceDirectives.length) { 114 | continue; 115 | } 116 | 117 | for (const typeRef of typeReferenceDirectives) { 118 | const module = universalModuleResolver.resolve( 119 | typeRef.fileName, 120 | containingFile, 121 | ); 122 | if (module) { 123 | resolvedModule.originModuleName = module.originModuleName; 124 | resolvedModule.filepath = module.filepath; 125 | } 126 | } 127 | } 128 | 129 | return resolvedModules; 130 | }; 131 | 132 | return resolveModuleNames; 133 | } 134 | -------------------------------------------------------------------------------- /src/tsls_host_wrappers/resolve_type_reference_directives.ts: -------------------------------------------------------------------------------- 1 | import ts_module, { 2 | LanguageServiceHost, 3 | } from "typescript/lib/tsserverlibrary"; 4 | import { Logger } from "logger"; 5 | 6 | export default function resolveTypeReferenceDirectivesWrapper( 7 | tsLsHost: LanguageServiceHost, 8 | config: any, 9 | logger: Logger, 10 | ) { 11 | const originalResolveTypeReferenceDirectives = 12 | tsLsHost.resolveTypeReferenceDirectives; 13 | if (!originalResolveTypeReferenceDirectives) { 14 | return; 15 | } 16 | 17 | const resolveTypeReferenceDirectives: 18 | typeof tsLsHost.resolveTypeReferenceDirectives = ( 19 | typeDirectiveNames: string[], 20 | containingFile: string, 21 | redirectedReference: ts_module.ResolvedProjectReference | undefined, 22 | options: ts_module.CompilerOptions, 23 | ): (ts_module.ResolvedTypeReferenceDirective | undefined)[] => { 24 | const ret = originalResolveTypeReferenceDirectives.call( 25 | tsLsHost, 26 | typeDirectiveNames, 27 | containingFile, 28 | redirectedReference, 29 | options, 30 | ); 31 | 32 | if (!config.enable) { 33 | logger.info("plugin disabled."); 34 | return ret; 35 | } 36 | 37 | return ret; 38 | }; 39 | 40 | return resolveTypeReferenceDirectives; 41 | } 42 | -------------------------------------------------------------------------------- /src/tsls_host_wrappers/script_file_names.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LanguageServiceHost, 3 | } from "typescript/lib/tsserverlibrary"; 4 | import { Logger } from "logger"; 5 | import { getDenoDtsPath } from "../utils"; 6 | 7 | export default function getScriptFileNamesWrapper( 8 | tsLsHost: LanguageServiceHost, 9 | config: any, 10 | logger: Logger, 11 | ) { 12 | const originalGetScriptFileNames = tsLsHost.getScriptFileNames; 13 | if (!config.enable) { 14 | return originalGetScriptFileNames; 15 | } 16 | 17 | const getScriptFileNames: typeof tsLsHost.getScriptFileNames = () => { 18 | const originalScriptFileNames = originalGetScriptFileNames.call( 19 | tsLsHost, 20 | ); 21 | 22 | const scriptFileNames = [...originalScriptFileNames]; 23 | 24 | const libDenoDts = getDenoDtsPath("lib.deno.d.ts"); 25 | if (!libDenoDts) { 26 | logger.info(`Can not load lib.deno.d.ts from ${libDenoDts}.`); 27 | return scriptFileNames; 28 | } 29 | scriptFileNames.push(libDenoDts); 30 | 31 | const libWebworkerDts = getDenoDtsPath("lib.webworker.d.ts"); 32 | if (!libWebworkerDts) { 33 | logger.info( 34 | `Can not load lib.webworker.d.ts from ${libWebworkerDts}.`, 35 | ); 36 | return scriptFileNames; 37 | } 38 | scriptFileNames.push(libWebworkerDts); 39 | 40 | return scriptFileNames; 41 | }; 42 | 43 | return getScriptFileNames; 44 | } 45 | -------------------------------------------------------------------------------- /src/tsls_wrappers/code_fixes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LanguageService, 3 | FormatCodeSettings, 4 | UserPreferences, 5 | CodeFixAction, 6 | } from "typescript/lib/tsserverlibrary"; 7 | import { errorCodeToFixes } from "../codefix_provider"; 8 | 9 | export default function getCodeFixesAtPositionWrapper(tsLs: LanguageService) { 10 | const getCodeFixesAtPosition = ( 11 | fileName: string, 12 | start: number, 13 | end: number, 14 | errorCodes: readonly number[], 15 | formatOptions: FormatCodeSettings, 16 | preferences: UserPreferences, 17 | ): readonly CodeFixAction[] => { 18 | const codeFixActions = tsLs.getCodeFixesAtPosition( 19 | fileName, 20 | start, 21 | end, 22 | errorCodes, 23 | formatOptions, 24 | preferences, 25 | ); 26 | 27 | for (const errorCode of errorCodes) { 28 | const fixes = errorCodeToFixes.get(errorCode); 29 | if (fixes == null) continue; 30 | 31 | for (const fix of fixes) { 32 | fix.replaceCodeActions(codeFixActions); 33 | } 34 | } 35 | 36 | return codeFixActions; 37 | }; 38 | 39 | return getCodeFixesAtPosition; 40 | } 41 | -------------------------------------------------------------------------------- /src/tsls_wrappers/completion_entry_details.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LanguageService, 3 | FormatCodeOptions, 4 | FormatCodeSettings, 5 | UserPreferences, 6 | CompletionEntryDetails, 7 | } from "typescript/lib/tsserverlibrary"; 8 | 9 | export default function getCompletionEntryDetailsnWrapper( 10 | tsLs: LanguageService, 11 | config: any, 12 | ) { 13 | const getCompletionEntryDetails = ( 14 | fileName: string, 15 | position: number, 16 | entryName: string, 17 | formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, 18 | source: string | undefined, 19 | preferences: UserPreferences | undefined, 20 | ): CompletionEntryDetails | undefined => { 21 | const details = tsLs.getCompletionEntryDetails( 22 | fileName, 23 | position, 24 | entryName, 25 | formatOptions, 26 | source, 27 | preferences, 28 | ); 29 | 30 | if (!config.enable) { 31 | return details; 32 | } 33 | 34 | if (details) { 35 | if (details.codeActions && details.codeActions.length) { 36 | for (const ca of details.codeActions) { 37 | for (const change of ca.changes) { 38 | if (!change.isNewFile) { 39 | for (const tc of change.textChanges) { 40 | tc.newText = tc.newText.replace( 41 | /^(import .* from ['"])(\..*)(['"];\n)/i, 42 | "$1$2.ts$3", 43 | ); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | return details; 52 | }; 53 | 54 | return getCompletionEntryDetails; 55 | } 56 | -------------------------------------------------------------------------------- /src/tsls_wrappers/completions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetCompletionsAtPositionOptions, 3 | LanguageService, 4 | ScriptElementKind, 5 | CompletionEntry, 6 | } from "typescript/lib/tsserverlibrary"; 7 | import { TsUtils } from "../ts_utils"; 8 | import { 9 | getDenoDir, 10 | parseImportMapFromFile, 11 | } from "../utils"; 12 | import { readdirSync, statSync } from "fs"; 13 | import { ImportMaps } from "import-maps"; 14 | import { join } from "path"; 15 | 16 | const importPathSanitizeRegex = /^\ ?['"](.*)['"]$/; 17 | 18 | interface ImportPath { 19 | path: string; 20 | isDir: boolean; 21 | } 22 | 23 | export default function getCompletionsAtPositionWrapper( 24 | projectDirectory: string, 25 | config: any, 26 | tsLs: LanguageService, 27 | tsUtils: TsUtils, 28 | ) { 29 | function getCompletionsAtPosition( 30 | fileName: string, 31 | position: number, 32 | options: GetCompletionsAtPositionOptions, 33 | ) { 34 | const node = tsUtils.getNode(fileName, position); 35 | if (!node) { 36 | return tsLs.getCompletionsAtPosition(fileName, position, options); 37 | } 38 | 39 | // 254 === ImportDeclaration 40 | // 10 === StringLiteral 41 | if (node.parent.kind !== 254 || node.kind !== 10) { 42 | return tsLs.getCompletionsAtPosition(fileName, position, options); 43 | } 44 | 45 | const importMap = 46 | parseImportMapFromFile(projectDirectory, config.importmap)["imports"]; 47 | const importPath = tsUtils.getNodeText(node, fileName); 48 | 49 | const sanitizedImportPath = 50 | (importPathSanitizeRegex.exec(importPath) as string[])[1]; 51 | 52 | const importMapCompletions = getImportMapCompletions( 53 | sanitizedImportPath, 54 | importMap, 55 | ); 56 | 57 | const importMappedPath = applyImportMap(sanitizedImportPath, importMap); 58 | const completions = getPathCompletions(importMappedPath, fileName); 59 | 60 | return { 61 | isGlobalCompletion: false, 62 | isMemberCompletion: true, 63 | isNewIdentifierLocation: false, 64 | entries: [...completions, ...importMapCompletions], 65 | }; 66 | } 67 | 68 | return getCompletionsAtPosition; 69 | } 70 | 71 | const httpRegex = /^https?:\/\//; 72 | const relativeRegex = /\.\.?\//; 73 | 74 | function getPathCompletions(path: string, fileName: string) { 75 | const lastPath = path.substr(path.lastIndexOf("/") + 1); 76 | const basePath = path.substring(0, path.length - lastPath.length); 77 | 78 | const denoDir = getDenoDir(); 79 | 80 | const importPaths: ImportPath[] = []; 81 | 82 | if (httpRegex.test(basePath)) { 83 | importPaths.push(...getHttpImportPaths(basePath, denoDir)); 84 | } 85 | 86 | if (relativeRegex.test(basePath)) { 87 | importPaths.push(...getRelativeImportPaths(fileName, basePath)); 88 | } 89 | 90 | const completions: CompletionEntry[] = []; 91 | 92 | for (const importPath of importPaths) { 93 | const { 94 | path, 95 | isDir, 96 | } = importPath; 97 | 98 | if (path.startsWith(lastPath)) { 99 | completions.push({ 100 | name: path.slice(lastPath.length), 101 | kind: isDir 102 | ? ScriptElementKind.directory 103 | : ScriptElementKind.scriptElement, 104 | sortText: "", 105 | kindModifiers: isDir ? "" : ".ts", 106 | }); 107 | } 108 | } 109 | 110 | return completions; 111 | } 112 | 113 | function getRelativeImportPaths(fileName: string, relativePath: string) { 114 | const basePath = fileName.substr(0, fileName.lastIndexOf("/")); 115 | const path = join(basePath, relativePath); 116 | const importPaths = getImportPathsFromDir(path) 117 | .filter((importPath) => { 118 | if (importPath.isDir) { 119 | return true; 120 | } 121 | 122 | return importPath.path.endsWith(".ts"); 123 | }); 124 | 125 | return importPaths; 126 | } 127 | 128 | function applyImportMap(path: string, importMap: ImportMaps["imports"]) { 129 | for (const importMapEntry of Object.keys(importMap)) { 130 | if (!path.startsWith(importMapEntry)) { 131 | continue; 132 | } 133 | let importMapPath = importMap[importMapEntry]; 134 | if (importMapPath === null) { 135 | continue; 136 | } 137 | if (typeof importMapPath === "object") { 138 | importMapPath = importMapPath.toString(); 139 | } 140 | 141 | return importMapPath + path.slice(importMapEntry.length); 142 | } 143 | 144 | return path; 145 | } 146 | 147 | function getHttpImportPaths(basePath: string, denoDir: string) { 148 | const url = basePath.replace(httpRegex, ""); 149 | const baseDir = `${denoDir}/gen/https/${url}`; 150 | const importPaths: ImportPath[] = getImportPathsFromDir(baseDir) 151 | .filter((importPath) => { 152 | if (importPath.isDir) { 153 | return true; 154 | } 155 | if ( 156 | importPath.path.endsWith(".js") || importPath.path.endsWith(".js.map") 157 | ) { 158 | return false; 159 | } 160 | 161 | return true; 162 | }) 163 | .map((importPath) => { 164 | if (!importPath.isDir) { 165 | // importPath.path === xxx.ts.meta 166 | importPath.path = importPath.path.slice(0, importPath.path.length - 5); 167 | } 168 | 169 | return importPath; 170 | }); 171 | 172 | return importPaths; 173 | } 174 | 175 | function getImportPathsFromDir(dir: string): ImportPath[] { 176 | try { 177 | const importPaths: ImportPath[] = []; 178 | const importPathFiles = readdirSync(dir); 179 | for (const importPathFile of importPathFiles) { 180 | const fileStats = statSync(`${dir}/${importPathFile}`); 181 | importPaths.push({ 182 | path: importPathFile, 183 | isDir: fileStats.isDirectory(), 184 | }); 185 | } 186 | 187 | return importPaths; 188 | } catch { 189 | return []; 190 | } 191 | } 192 | 193 | function getImportMapCompletions( 194 | importPath: string, 195 | importMap: ImportMaps["imports"], 196 | ) { 197 | const importMapEntries = Object.keys(importMap); 198 | const completions: CompletionEntry[] = []; 199 | 200 | for (const importMapEntry of importMapEntries) { 201 | if (importMapEntry.startsWith(importPath)) { 202 | completions.push({ 203 | name: importMapEntry.slice(importPath.length), 204 | kind: ScriptElementKind.directory, 205 | sortText: "", 206 | }); 207 | } 208 | } 209 | 210 | return completions; 211 | } 212 | -------------------------------------------------------------------------------- /src/tsls_wrappers/resolve_moduel_names.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LanguageService, 3 | FormatCodeSettings, 4 | UserPreferences, 5 | CodeFixAction, 6 | } from "typescript/lib/tsserverlibrary"; 7 | import { errorCodeToFixes } from "../codefix_provider"; 8 | 9 | export default function getCodeFixesAtPositionWrapper(tsLs: LanguageService) { 10 | const getCodeFixesAtPosition = ( 11 | fileName: string, 12 | start: number, 13 | end: number, 14 | errorCodes: readonly number[], 15 | formatOptions: FormatCodeSettings, 16 | preferences: UserPreferences, 17 | ): readonly CodeFixAction[] => { 18 | const codeFixActions = tsLs.getCodeFixesAtPosition( 19 | fileName, 20 | start, 21 | end, 22 | errorCodes, 23 | formatOptions, 24 | preferences, 25 | ); 26 | 27 | for (const errorCode of errorCodes) { 28 | const fixes = errorCodeToFixes.get(errorCode); 29 | if (fixes == null) continue; 30 | 31 | for (const fix of fixes) { 32 | fix.replaceCodeActions(codeFixActions); 33 | } 34 | } 35 | 36 | return codeFixActions; 37 | }; 38 | 39 | return getCodeFixesAtPosition; 40 | } 41 | -------------------------------------------------------------------------------- /src/tsls_wrappers/semantic_diagnostics.ts: -------------------------------------------------------------------------------- 1 | import ts_module, { 2 | LanguageService, 3 | Diagnostic, 4 | } from "typescript/lib/tsserverlibrary"; 5 | import { Logger } from "../logger"; 6 | import { 7 | parseImportMapFromFile, 8 | parseModuleName, 9 | resolveDenoModule, 10 | isHttpURL, 11 | } from "../utils"; 12 | import { ImportMaps } from "import-maps"; 13 | import path from "path"; 14 | 15 | export default function getSemanticDiagnosticsWrapper( 16 | tsLs: LanguageService, 17 | config: any, 18 | logger: Logger, 19 | projectDirectory: string, 20 | ) { 21 | const getSemanticDiagnostics = (filename: string): Diagnostic[] => { 22 | logger.info("getSemanticDiagnostics"); 23 | const diagnostics = tsLs.getSemanticDiagnostics(filename); 24 | 25 | let parsedImportMap: ImportMaps; 26 | 27 | if (!config.enable) { 28 | return diagnostics; 29 | } 30 | 31 | // ref: https://github.com/denoland/deno/blob/da8cb408c878aa6e90542e26173f1f14b5254d29/cli/js/compiler/util.ts#L262 32 | const ignoredDiagnostics = [ 33 | // TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is 34 | // not a module. 35 | 2306, 36 | // TS1375: 'await' expressions are only allowed at the top level of a file 37 | // when that file is a module, but this file has no imports or exports. 38 | // Consider adding an empty 'export {}' to make this file a module. 39 | 1375, 40 | // TS1103: 'for-await-of' statement is only allowed within an async function 41 | // or async generator. 42 | 1103, 43 | // TS2691: An import path cannot end with a '.ts' extension. Consider 44 | // importing 'bad-module' instead. 45 | // !! 2691, 46 | // TS5009: Cannot find the common subdirectory path for the input files. 47 | 5009, 48 | // TS5055: Cannot write file 49 | // 'http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js' 50 | // because it would overwrite input file. 51 | 5055, 52 | // TypeScript is overly opinionated that only CommonJS modules kinds can 53 | // support JSON imports. Allegedly this was fixed in 54 | // Microsoft/TypeScript#26825 but that doesn't seem to be working here, 55 | // so we will ignore complaints about this compiler setting. 56 | 5070, 57 | // TS7016: Could not find a declaration file for module '...'. '...' 58 | // implicitly has an 'any' type. This is due to `allowJs` being off by 59 | // default but importing of a JavaScript module. 60 | 7016, 61 | ]; 62 | 63 | return diagnostics.filter((d: ts_module.Diagnostic) => 64 | !ignoredDiagnostics.includes(d.code) 65 | ).map((d: ts_module.Diagnostic) => { 66 | if (d.code === 2691) { 67 | const moduleName = d.file!.getFullText().substr( 68 | d.start! + 1, 69 | d.length! - 2, 70 | ); 71 | 72 | if (config.importmap != null) { 73 | parsedImportMap = parseImportMapFromFile( 74 | projectDirectory, 75 | config.importmap, 76 | ); 77 | } 78 | 79 | const parsedModuleName = parseModuleName( 80 | moduleName, 81 | d.file?.fileName!, 82 | parsedImportMap, 83 | logger, 84 | ); 85 | 86 | if (parsedModuleName == null) { 87 | d.code = 10001; // InvalidRelativeImport 88 | d.messageText = 89 | `relative import path "${moduleName}" not prefixed with / or ./ or ../`; 90 | return d; 91 | } 92 | 93 | const resolvedModule = resolveDenoModule(parsedModuleName); 94 | 95 | if (resolvedModule != null) { 96 | return d; 97 | } 98 | 99 | if (isHttpURL(parsedModuleName)) { 100 | d.code = 10002; // RemoteModuleNotExist 101 | if (moduleName === parsedModuleName) { 102 | d.messageText = 103 | `The remote module has not been cached locally. Try \`deno cache ${parsedModuleName}\` if it exists`; 104 | } else { 105 | d.messageText = 106 | `The remote module "${moduleName}" has not been cached locally. Try \`deno cache ${parsedModuleName}\` if it exists`; 107 | } 108 | 109 | return d; 110 | } 111 | 112 | if ( 113 | path.isAbsolute(parsedModuleName) || 114 | parsedModuleName.startsWith("./") || 115 | parsedModuleName.startsWith("../") || 116 | parsedModuleName.startsWith("file://") 117 | ) { 118 | d.code = 10003; // LocalModuleNotExist 119 | d.messageText = `Could not find module "${moduleName}" locally`; 120 | return d; 121 | } 122 | 123 | d.code = 10004; // InvalidImport 124 | d.messageText = 125 | `Import module "${moduleName}" must be a relative path or remote HTTP URL`; 126 | } 127 | return d; 128 | }); 129 | }; 130 | 131 | return getSemanticDiagnostics; 132 | } 133 | -------------------------------------------------------------------------------- /src/utils.test.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { URL } from "url"; 3 | 4 | import { 5 | pathExistsSync, 6 | escapeRegExp, 7 | isHttpURL, 8 | hashURL, 9 | normalizeFilepath, 10 | isValidDenoDocument, 11 | isUntitledDocument, 12 | getDenoDir, 13 | getDenoDepsDir, 14 | getPluginPath, 15 | getDenoDtsPath, 16 | isInDenoDir, 17 | } from "./utils"; 18 | 19 | describe("pathExistsSync", () => { 20 | test("pathExistsSync not exist", () => { 21 | expect(pathExistsSync("./path_not_exist")).toBe(false); 22 | }); 23 | test("pathExistsSync exist", () => { 24 | expect(pathExistsSync(__filename)).toBe(true); 25 | }); 26 | }); 27 | 28 | test("escapeRegExp", () => { 29 | expect(escapeRegExp("/User/demo/file/path")).toEqual("/User/demo/file/path"); 30 | 31 | expect( 32 | escapeRegExp("C:\\Users\\runneradmin\\AppData\\Local\\deno\\deps\\"), 33 | ).toEqual( 34 | "C:\\\\Users\\\\runneradmin\\\\AppData\\\\Local\\\\deno\\\\deps\\\\", 35 | ); 36 | }); 37 | 38 | test("isHttpURL", () => { 39 | expect(isHttpURL("/User/demo/file/path")).toBeFalsy(); 40 | expect(isHttpURL("https")).toBeFalsy(); 41 | expect(isHttpURL("https://")).toBeFalsy(); 42 | expect(isHttpURL("https://example")).toBeTruthy(); 43 | expect(isHttpURL("https://example.com")).toBeTruthy(); 44 | expect(isHttpURL("https://**@!)($LFKASD> { 48 | // example from __test_ folder 49 | expect(hashURL(new URL("https://example.com/esm/mod.ts"))).toEqual( 50 | "8afd52da760dab7f2deda4b7453197f50421f310372c5da3f3847ffd062fa1cf", 51 | ); 52 | }); 53 | 54 | test("normalizeFilepath", () => { 55 | if (process.platform === "win32") { 56 | expect(normalizeFilepath("/path/to/file")).toEqual("\\path\\to\\file"); 57 | } else { 58 | expect(normalizeFilepath("/path/to/file")).toEqual("/path/to/file"); 59 | } 60 | 61 | expect(normalizeFilepath("d:\\path\\to\\file")).toEqual("D:\\path\\to\\file"); 62 | }); 63 | 64 | test("isValidDenoDocument", () => { 65 | expect(isValidDenoDocument("foo")).toBe(false); 66 | expect(isValidDenoDocument("bar")).toBe(false); 67 | expect(isValidDenoDocument("javascript")).toBe(true); 68 | expect(isValidDenoDocument("javascriptreact")).toBe(true); 69 | expect(isValidDenoDocument("typescript")).toBe(true); 70 | expect(isValidDenoDocument("typescriptreact")).toBe(true); 71 | }); 72 | 73 | test("isUntitledDocument", () => { 74 | expect(isUntitledDocument("foo")).toBe(false); 75 | expect(isUntitledDocument("./foo")).toBe(false); 76 | expect(isUntitledDocument("../bar")).toBe(false); 77 | expect(isUntitledDocument("untitled: ")).toBe(true); 78 | }); 79 | 80 | test("getDenoDir", () => { 81 | expect(getDenoDir()).not.toBe(undefined); 82 | }); 83 | 84 | test("getDenoDepsDir", () => { 85 | expect(getDenoDepsDir()).not.toBe(undefined); 86 | }); 87 | 88 | test("getPluginPath", () => { 89 | expect(getPluginPath()).not.toBe(undefined); 90 | }); 91 | 92 | test("DenoDir includes DepsDir", () => { 93 | expect(getDenoDepsDir()).toContain(getDenoDir()); 94 | }); 95 | 96 | test("isInDenoDir", () => { 97 | const modPath = path.join(getDenoDir(), "https", "example.com", "/mod.ts"); 98 | expect(isInDenoDir(modPath)).toBe(true); 99 | expect(isInDenoDir("/a/path/to/somewhere")).toBe(false); 100 | }); 101 | 102 | test("getDenoDtsPath", () => { 103 | expect( 104 | [ 105 | path.join(getDenoDir(), "lib.deno.d.ts"), 106 | path.join(getPluginPath(), "lib", "lib.deno.d.ts"), 107 | ], 108 | ).toContain(getDenoDtsPath("lib.deno.d.ts")); 109 | expect( 110 | [ 111 | path.join(getDenoDir(), "lib.webworker.d.ts"), 112 | path.join(getPluginPath(), "lib", "lib.webworker.d.ts"), 113 | ], 114 | ).toContain(getDenoDtsPath("lib.webworker.d.ts")); 115 | }); 116 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import crypto from "crypto"; 4 | import { URL, fileURLToPath } from "url"; 5 | import { 6 | resolve, 7 | ImportMaps, 8 | parseFromString, 9 | } from "import-maps"; 10 | import { Logger } from "logger"; 11 | import { HashMeta } from "./module_resolver/hash_meta"; 12 | import { universalModuleResolver } from "./module_resolver/universal_module_resolver"; 13 | 14 | export function getDenoDir(): string { 15 | // ref https://deno.land/manual.html 16 | // On Linux/Redox: $XDG_CACHE_HOME/deno or $HOME/.cache/deno 17 | // On Windows: %LOCALAPPDATA%/deno (%LOCALAPPDATA% = FOLDERID_LocalAppData) 18 | // On macOS: $HOME/Library/Caches/deno 19 | // If something fails, it falls back to $HOME/.deno 20 | let denoDir = process.env.DENO_DIR; 21 | if (denoDir === undefined) { 22 | switch (process.platform) { 23 | case "win32": 24 | denoDir = `${process.env.LOCALAPPDATA}\\deno`; 25 | break; 26 | case "darwin": 27 | denoDir = `${process.env.HOME}/Library/Caches/deno`; 28 | break; 29 | case "linux": 30 | denoDir = process.env.XDG_CACHE_HOME 31 | ? `${process.env.XDG_CACHE_HOME}/deno` 32 | : `${process.env.HOME}/.cache/deno`; 33 | break; 34 | default: 35 | denoDir = `${process.env.HOME}/.deno`; 36 | } 37 | } 38 | 39 | return denoDir; 40 | } 41 | 42 | export function getDenoDepsDir(): string { 43 | return path.join(getDenoDir(), "deps"); 44 | } 45 | 46 | export function isInDenoDir(filepath: string): boolean { 47 | filepath = normalizeFilepath(filepath); 48 | const denoDir = getDenoDir(); 49 | return filepath.startsWith(denoDir); 50 | } 51 | 52 | export function getPluginPath(): string { 53 | return path.resolve(__dirname, ".."); 54 | } 55 | 56 | export function getDenoDtsPath( 57 | specifier: string, 58 | ): string | undefined { 59 | let file: string = path.resolve(getDenoDir(), specifier); 60 | 61 | if (fs.existsSync(file)) { 62 | return file; 63 | } 64 | 65 | file = path.resolve(getPluginPath(), "lib", specifier); 66 | if (fs.existsSync(file)) { 67 | return file; 68 | } 69 | 70 | return undefined; 71 | } 72 | 73 | export function getModuleWithQueryString( 74 | moduleName: string, 75 | ): string | undefined { 76 | let name = moduleName; 77 | for ( 78 | const index = name.indexOf("?"); 79 | index !== -1; 80 | name = name.substring(index + 1) 81 | ) { 82 | const sub = name.substring(0, index); 83 | if (sub.endsWith(".ts") || sub.endsWith(".tsx")) { 84 | const cutLength = moduleName.length - name.length; 85 | return moduleName.substring(0, index + cutLength) || undefined; 86 | } 87 | } 88 | return undefined; 89 | } 90 | 91 | export function normalizeFilepath(filepath: string): string { 92 | return path.normalize( 93 | filepath 94 | // in Windows, filepath maybe `c:\foo\bar` tut the legal path should be `C:\foo\bar` 95 | .replace(/^([a-z]):\\/, (_, $1) => $1.toUpperCase() + ":\\") 96 | // There are some paths which are unix style, this style does not work on win32 systems 97 | .replace(/\//gm, path.sep), 98 | ); 99 | } 100 | 101 | // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions 102 | // cover filepath string to regexp string 103 | // Because the `\` string is included in the path to Windows 104 | // So we need to translate it once 105 | // `/^C:\Users\runneradmin\AppData\Local\deno\deps\/` -> `/^C:\\Users\\runneradmin\\AppData\\Local\\deno\\deps\\/` 106 | export function escapeRegExp(str: string): string { 107 | return str.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string 108 | } 109 | 110 | export function sleep(ms: number): Promise { 111 | return new Promise((resolve) => { 112 | setTimeout(() => { 113 | resolve(); 114 | }, ms); 115 | }); 116 | } 117 | 118 | export function isHttpURL(str: string): boolean { 119 | if (!str.startsWith("http://") && !str.startsWith("https://")) { 120 | return false; 121 | } 122 | 123 | try { 124 | new URL(str); 125 | return true; 126 | } catch { 127 | return false; 128 | } 129 | } 130 | 131 | // hash a URL with it's pathname and search 132 | export function hashURL(url: URL): string { 133 | return crypto 134 | .createHash("sha256") 135 | .update(url.pathname + url.search) 136 | .digest("hex"); 137 | } 138 | 139 | export function isValidDenoDocument(languageID: string): boolean { 140 | return [ 141 | "javascript", 142 | "javascriptreact", 143 | "typescript", 144 | "typescriptreact", 145 | ].includes(languageID); 146 | } 147 | 148 | export function isUntitledDocument(filename: string): boolean { 149 | // In vscode, tsserver may crash because a temporary document is not saved 150 | return /^untitled:/.test(filename); 151 | } 152 | 153 | export function pathExistsSync(filepath: string): boolean { 154 | try { 155 | fs.statSync(filepath); 156 | return true; 157 | } catch (err) { 158 | return false; 159 | } 160 | } 161 | 162 | export function parseImportMapFromFile(cwd: string, file?: string): ImportMaps { 163 | const importmps: ImportMaps = { 164 | imports: {}, 165 | scopes: {}, 166 | }; 167 | 168 | if (file == null) { 169 | return importmps; 170 | } 171 | 172 | if (!path.isAbsolute(file)) { 173 | file = path.resolve(cwd, file); 174 | } 175 | 176 | const fullFilePath = normalizeFilepath(file); 177 | 178 | if (!pathExistsSync(fullFilePath)) { 179 | return importmps; 180 | } 181 | 182 | const content = fs.readFileSync(fullFilePath, { 183 | encoding: "utf8", 184 | }); 185 | 186 | try { 187 | return parseFromString(content, `file://${cwd}/`); 188 | } catch { 189 | return importmps; 190 | } 191 | } 192 | 193 | export function parseModuleName( 194 | moduleName: string, 195 | containingFile: string, 196 | parsedImportMap?: ImportMaps | null, 197 | logger?: Logger, 198 | ): string | undefined { 199 | if (parsedImportMap != null) { 200 | try { 201 | let scriptURL: URL; 202 | if (isInDenoDir(containingFile)) { 203 | const meta = HashMeta.create(`${containingFile}.metadata.json`); 204 | if (meta && meta.url) { 205 | scriptURL = meta.url; 206 | } else { 207 | scriptURL = new URL("file:///" + path.dirname(containingFile) + "/"); 208 | } 209 | } else { 210 | scriptURL = new URL("file:///" + path.dirname(containingFile) + "/"); 211 | } 212 | 213 | logger && logger.info(`baseUrl: ${scriptURL}`); 214 | 215 | const moduleUrl = resolve( 216 | moduleName, 217 | parsedImportMap, 218 | scriptURL, 219 | ); 220 | 221 | if (moduleUrl.protocol === "file:") { 222 | return fileURLToPath(moduleUrl.href); 223 | } 224 | 225 | if (moduleUrl.protocol === "http:" || moduleUrl.protocol === "https:") { 226 | return moduleUrl.href; 227 | } 228 | 229 | // just support protocol: file, http, https 230 | return undefined; 231 | } catch (e) { 232 | if (logger) logger.info("moduleName: " + moduleName); 233 | if (logger) logger.info("e: " + (e as Error).stack); 234 | return undefined; 235 | } 236 | } 237 | } 238 | 239 | export function resolveDenoModule(moduleName: string) { 240 | return universalModuleResolver.resolve( 241 | moduleName, 242 | ); 243 | } 244 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "declaration": true, 7 | "outDir": "out", 8 | "noImplicitAny": true, 9 | "noUnusedParameters": true, 10 | "noUnusedLocals": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "lib": ["es6"], 14 | "strict": true, 15 | "alwaysStrict": true, 16 | "strictNullChecks": true, 17 | "baseUrl": "./src" 18 | }, 19 | "include": ["./src/**/*.ts", "./types/**/*.d.ts"], 20 | "exclude": ["./lib/*.d.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /types/merge-deep.d.ts: -------------------------------------------------------------------------------- 1 | declare module "merge-deep" { 2 | export default function merge(value1: T, value2: T): T; 3 | } 4 | --------------------------------------------------------------------------------