diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7862458..ceb84334 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,8 @@ -name: "CI" +name: 'CI' on: [pull_request] jobs: build: - name: "Builds and Compiles" + name: 'Builds and Compiles' runs-on: ubuntu-latest steps: diff --git a/.vscode/settings.json b/.vscode/settings.json index 9974d577..a5826cb5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,4 +5,4 @@ "**/node_modules": true, "**/release": true } -} \ No newline at end of file +} diff --git a/LICENSE.md b/LICENSE.md index f8a94f6e..5ae193c9 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,21 +1,21 @@ -The MIT License (MIT) - -Copyright (c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2b3e1ac8..40558795 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,39 @@ -# Monaco TypeScript - -Simple TypeScript and JavaScript language support for the Monaco Editor. - -![typescript](https://cloud.githubusercontent.com/assets/5047891/15926623/5262fe08-2e3d-11e6-9b90-1d43fda07178.gif) - -*Note* that this project focuses on single-file scenarios and that things like project-isolation, cross-file-features like Rename etc. are *outside* the scope of this project and not supported. - -## Issues - -Please file issues concerning `monaco-typescript` in the [`monaco-editor` repository](https://github.com/Microsoft/monaco-editor/issues). - -## Installing - -This npm module is bundled and distributed in the [monaco-editor](https://www.npmjs.com/package/monaco-editor) npm module. - - -## Development - -* `git clone https://github.com/Microsoft/monaco-typescript` -* `cd monaco-typescript` -* `npm install .` -* `npm run compile` -* `npm run watch` -* open `$/monaco-typescript/test/index.html` in your favorite browser. - -## Updating TypeScript - -* change typescript's version in `package.json`. -* execute `npm install .` -* execute `npm run import-typescript` -* adopt new APIs - -## Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - - -## License -[MIT](https://github.com/Microsoft/monaco-typescript/blob/master/LICENSE.md) +# Monaco TypeScript + +Simple TypeScript and JavaScript language support for the Monaco Editor. + +![typescript](https://cloud.githubusercontent.com/assets/5047891/15926623/5262fe08-2e3d-11e6-9b90-1d43fda07178.gif) + +_Note_ that this project focuses on single-file scenarios and that things like project-isolation, cross-file-features like Rename etc. are _outside_ the scope of this project and not supported. + +## Issues + +Please file issues concerning `monaco-typescript` in the [`monaco-editor` repository](https://github.com/Microsoft/monaco-editor/issues). + +## Installing + +This npm module is bundled and distributed in the [monaco-editor](https://www.npmjs.com/package/monaco-editor) npm module. + +## Development + +- `git clone https://github.com/Microsoft/monaco-typescript` +- `cd monaco-typescript` +- `npm install .` +- `npm run compile` +- `npm run watch` +- open `$/monaco-typescript/test/index.html` in your favorite browser. + +## Updating TypeScript + +- change typescript's version in `package.json`. +- execute `npm install .` +- execute `npm run import-typescript` +- adopt new APIs + +## Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## License + +[MIT](https://github.com/Microsoft/monaco-typescript/blob/master/LICENSE.md) diff --git a/package-lock.json b/package-lock.json index 5b49d5b0..ad97fa00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,112 +1,112 @@ { - "name": "monaco-typescript", - "version": "3.7.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@typescript/vfs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.3.0.tgz", - "integrity": "sha512-Bd1LdvQpm0uU2eclcCfO8H8oAGAfEJiKn0acKy/xeZV4sARwXx9MHBMuDX0XDPLmI2JpIm+mFV9Ers65xnoaQg==", - "dev": true, - "requires": { - "debug": "^4.1.1" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "monaco-editor-core": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/monaco-editor-core/-/monaco-editor-core-0.20.0.tgz", - "integrity": "sha512-4mdmfEejTvRZzrEIn70jqqNl3g15vnkRdTkJ8uMK4jiljntlwhiSc5vknZOLt1QM8za16C3tDrSl2mTL9ma2Sg==", - "dev": true - }, - "monaco-languages": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/monaco-languages/-/monaco-languages-1.10.0.tgz", - "integrity": "sha512-ARAws17Xh0K4WsZYkJY6CqHn9EYdYN8CjzK6w/jgXIwU0owzCdUWxzu+FNJ/LeDLcKxL/YK3phcwGFj9IqX2yw==", - "dev": true - }, - "monaco-plugin-helpers": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/monaco-plugin-helpers/-/monaco-plugin-helpers-1.0.3.tgz", - "integrity": "sha512-6AYI3ONAy8ki74qG2JqtFrLdiJHQlgeO5l4Rwr0OMyIpGXhc94y5rZuFxOtgGkxgSrZfHSwOt/MulUNZ/mOQOw==", - "dev": true, - "requires": { - "typescript": "^2.7.2" - }, - "dependencies": { - "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", - "dev": true - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "requirejs": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", - "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "terser": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.0.tgz", - "integrity": "sha512-XTT3D3AwxC54KywJijmY2mxZ8nJiEjBHVYzq8l9OaYuRFWeQNBwvipuzzYEP4e+/AVcd1hqG/CqgsdIRyT45Fg==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - } - }, - "typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", - "dev": true - } - } + "name": "monaco-typescript", + "version": "3.7.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@typescript/vfs": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.3.0.tgz", + "integrity": "sha512-Bd1LdvQpm0uU2eclcCfO8H8oAGAfEJiKn0acKy/xeZV4sARwXx9MHBMuDX0XDPLmI2JpIm+mFV9Ers65xnoaQg==", + "dev": true, + "requires": { + "debug": "^4.1.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "monaco-editor-core": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/monaco-editor-core/-/monaco-editor-core-0.20.0.tgz", + "integrity": "sha512-4mdmfEejTvRZzrEIn70jqqNl3g15vnkRdTkJ8uMK4jiljntlwhiSc5vknZOLt1QM8za16C3tDrSl2mTL9ma2Sg==", + "dev": true + }, + "monaco-languages": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/monaco-languages/-/monaco-languages-1.10.0.tgz", + "integrity": "sha512-ARAws17Xh0K4WsZYkJY6CqHn9EYdYN8CjzK6w/jgXIwU0owzCdUWxzu+FNJ/LeDLcKxL/YK3phcwGFj9IqX2yw==", + "dev": true + }, + "monaco-plugin-helpers": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/monaco-plugin-helpers/-/monaco-plugin-helpers-1.0.3.tgz", + "integrity": "sha512-6AYI3ONAy8ki74qG2JqtFrLdiJHQlgeO5l4Rwr0OMyIpGXhc94y5rZuFxOtgGkxgSrZfHSwOt/MulUNZ/mOQOw==", + "dev": true, + "requires": { + "typescript": "^2.7.2" + }, + "dependencies": { + "typescript": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "requirejs": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", + "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "terser": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.0.tgz", + "integrity": "sha512-XTT3D3AwxC54KywJijmY2mxZ8nJiEjBHVYzq8l9OaYuRFWeQNBwvipuzzYEP4e+/AVcd1hqG/CqgsdIRyT45Fg==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + } + }, + "typescript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", + "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "dev": true + } + } } diff --git a/package.json b/package.json index ddf6b84f..70dcfd76 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,40 @@ { - "name": "monaco-typescript", - "version": "3.7.0", - "description": "TypeScript and JavaScript language support for Monaco Editor", - "scripts": { - "compile-amd": "mcopy ./src/lib/typescriptServices-amd.js ./release/dev/lib/typescriptServices.js && tsc -p ./src/tsconfig.json", - "compile-esm": "mcopy ./src/lib/typescriptServices.js ./release/esm/lib/typescriptServices.js && tsc -p ./src/tsconfig.esm.json", - "compile": "mrmdir ./release && npm run compile-amd && npm run compile-esm", - "watch": "tsc -p ./src --watch", - "prepublishOnly": "npm run compile && node ./scripts/bundle && mcopy ./src/monaco.d.ts ./release/monaco.d.ts", - "import-typescript": "node ./scripts/importTypescript", - "prettier": "prettier --write ." - }, - "author": "Microsoft Corporation", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/Microsoft/monaco-typescript" - }, - "bugs": { - "url": "https://github.com/Microsoft/monaco-typescript/issues" - }, - "devDependencies": { - "@typescript/vfs": "^1.3.0", - "husky": "^4.3.0", - "monaco-editor-core": "^0.20.0", - "monaco-languages": "^1.10.0", - "monaco-plugin-helpers": "^1.0.3", - "prettier": "^2.1.1", - "pretty-quick": "^3.0.0", - "requirejs": "^2.3.6", - "terser": "^5.3.0", - "typescript": "^4.0.2" - } + "name": "monaco-typescript", + "version": "3.7.0", + "description": "TypeScript and JavaScript language support for Monaco Editor", + "scripts": { + "compile-amd": "mcopy ./src/lib/typescriptServices-amd.js ./release/dev/lib/typescriptServices.js && tsc -p ./src/tsconfig.json", + "compile-esm": "mcopy ./src/lib/typescriptServices.js ./release/esm/lib/typescriptServices.js && tsc -p ./src/tsconfig.esm.json", + "compile": "mrmdir ./release && npm run compile-amd && npm run compile-esm", + "watch": "tsc -p ./src --watch", + "prepublishOnly": "npm run compile && node ./scripts/bundle && mcopy ./src/monaco.d.ts ./release/monaco.d.ts", + "import-typescript": "node ./scripts/importTypescript", + "prettier": "prettier --write ." + }, + "author": "Microsoft Corporation", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/monaco-typescript" + }, + "bugs": { + "url": "https://github.com/Microsoft/monaco-typescript/issues" + }, + "devDependencies": { + "@typescript/vfs": "^1.3.0", + "husky": "^4.3.0", + "monaco-editor-core": "^0.20.0", + "monaco-languages": "^1.10.0", + "monaco-plugin-helpers": "^1.0.3", + "prettier": "^2.1.1", + "pretty-quick": "^3.0.0", + "requirejs": "^2.3.6", + "terser": "^5.3.0", + "typescript": "^4.0.2" + }, + "husky": { + "hooks": { + "pre-commit": "pretty-quick --staged" + } + } } diff --git a/scripts/bundle.js b/scripts/bundle.js index 58eb20af..b15988c1 100644 --- a/scripts/bundle.js +++ b/scripts/bundle.js @@ -30,22 +30,25 @@ bundleOne('tsMode'); bundleOne('tsWorker'); function bundleOne(moduleId, exclude) { - requirejs.optimize({ - baseUrl: 'release/dev/', - name: 'vs/language/typescript/' + moduleId, - out: 'release/min/' + moduleId + '.js', - exclude: exclude, - paths: { - 'vs/language/typescript': REPO_ROOT + '/release/dev' + requirejs.optimize( + { + baseUrl: 'release/dev/', + name: 'vs/language/typescript/' + moduleId, + out: 'release/min/' + moduleId + '.js', + exclude: exclude, + paths: { + 'vs/language/typescript': REPO_ROOT + '/release/dev' + }, + optimize: 'none' }, - optimize: 'none' - }, async function(buildResponse) { - const filePath = path.join(REPO_ROOT, 'release/min/' + moduleId + '.js'); - const fileContents = fs.readFileSync(filePath).toString(); - console.log(); - console.log(`Minifying ${filePath}...`); - const result = await terser.minify(fileContents); - console.log(`Done minifying ${filePath}.`); - fs.writeFileSync(filePath, BUNDLED_FILE_HEADER + result.code); - }) + async function (buildResponse) { + const filePath = path.join(REPO_ROOT, 'release/min/' + moduleId + '.js'); + const fileContents = fs.readFileSync(filePath).toString(); + console.log(); + console.log(`Minifying ${filePath}...`); + const result = await terser.minify(fileContents); + console.log(`Done minifying ${filePath}.`); + fs.writeFileSync(filePath, BUNDLED_FILE_HEADER + result.code); + } + ); } diff --git a/scripts/importTypescript.js b/scripts/importTypescript.js index ed74e788..a36c217b 100644 --- a/scripts/importTypescript.js +++ b/scripts/importTypescript.js @@ -12,7 +12,10 @@ const generatedNote = `// // `; -const TYPESCRIPT_LIB_SOURCE = path.join(__dirname, '../node_modules/typescript/lib'); +const TYPESCRIPT_LIB_SOURCE = path.join( + __dirname, + '../node_modules/typescript/lib' +); const TYPESCRIPT_LIB_DESTINATION = path.join(__dirname, '../src/lib'); (function () { @@ -23,8 +26,11 @@ const TYPESCRIPT_LIB_DESTINATION = path.join(__dirname, '../src/lib'); } importLibs(); - const npmLsOutput = JSON.parse(child_process.execSync("npm ls typescript --depth=0 --json=true").toString()); - const typeScriptDependencyVersion = npmLsOutput.dependencies.typescript.version; + const npmLsOutput = JSON.parse( + child_process.execSync('npm ls typescript --depth=0 --json=true').toString() + ); + const typeScriptDependencyVersion = + npmLsOutput.dependencies.typescript.version; fs.writeFileSync( path.join(TYPESCRIPT_LIB_DESTINATION, 'typescriptServicesMetadata.ts'), @@ -32,36 +38,55 @@ const TYPESCRIPT_LIB_DESTINATION = path.join(__dirname, '../src/lib'); export const typescriptVersion = "${typeScriptDependencyVersion}";\n` ); - var tsServices = fs.readFileSync(path.join(TYPESCRIPT_LIB_SOURCE, 'typescriptServices.js')).toString(); + var tsServices = fs + .readFileSync(path.join(TYPESCRIPT_LIB_SOURCE, 'typescriptServices.js')) + .toString(); // Ensure we never run into the node system... // (this also removes require calls that trick webpack into shimming those modules...) - tsServices = ( - tsServices.replace(/\n ts\.sys =([^]*)\n \}\)\(\);/m, `\n // MONACOCHANGE\n ts.sys = undefined;\n // END MONACOCHANGE`) + tsServices = tsServices.replace( + /\n ts\.sys =([^]*)\n \}\)\(\);/m, + `\n // MONACOCHANGE\n ts.sys = undefined;\n // END MONACOCHANGE` ); // Eliminate more require() calls... - tsServices = tsServices.replace(/^( +)etwModule = require\(.*$/m, '$1// MONACOCHANGE\n$1etwModule = undefined;\n$1// END MONACOCHANGE'); - tsServices = tsServices.replace(/^( +)var result = ts\.sys\.require\(.*$/m, '$1// MONACOCHANGE\n$1var result = undefined;\n$1// END MONACOCHANGE'); + tsServices = tsServices.replace( + /^( +)etwModule = require\(.*$/m, + '$1// MONACOCHANGE\n$1etwModule = undefined;\n$1// END MONACOCHANGE' + ); + tsServices = tsServices.replace( + /^( +)var result = ts\.sys\.require\(.*$/m, + '$1// MONACOCHANGE\n$1var result = undefined;\n$1// END MONACOCHANGE' + ); // Flag any new require calls (outside comments) so they can be corrected preemptively. // To avoid missing cases (or using an even more complex regex), temporarily remove comments // about require() and then check for lines actually calling require(). // \/[*/] matches the start of a comment (single or multi-line). // ^\s+\*[^/] matches (presumably) a later line of a multi-line comment. - const tsServicesNoCommentedRequire = tsServices.replace(/(\/[*/]|^\s+\*[^/]).*\brequire\(.*/gm, ''); - const linesWithRequire = tsServicesNoCommentedRequire.match(/^.*?\brequire\(.*$/gm) + const tsServicesNoCommentedRequire = tsServices.replace( + /(\/[*/]|^\s+\*[^/]).*\brequire\(.*/gm, + '' + ); + const linesWithRequire = tsServicesNoCommentedRequire.match( + /^.*?\brequire\(.*$/gm + ); // Allow error messages to include references to require() in their strings - const runtimeRequires = linesWithRequire && linesWithRequire.filter(l => !l.includes(": diag(")) + const runtimeRequires = + linesWithRequire && linesWithRequire.filter((l) => !l.includes(': diag(')); if (runtimeRequires && runtimeRequires.length && linesWithRequire) { - console.error('Found new require() calls on the following lines. These should be removed to avoid breaking webpack builds.\n'); + console.error( + 'Found new require() calls on the following lines. These should be removed to avoid breaking webpack builds.\n' + ); console.error(linesWithRequire.join('\n')); process.exit(1); } - var tsServices_amd = generatedNote + tsServices + + var tsServices_amd = + generatedNote + + tsServices + ` // MONACOCHANGE // Defining the entire module name because r.js has an issue and cannot bundle this file @@ -69,9 +94,14 @@ export const typescriptVersion = "${typeScriptDependencyVersion}";\n` define("vs/language/typescript/lib/typescriptServices", [], function() { return ts; }); // END MONACOCHANGE `; - fs.writeFileSync(path.join(TYPESCRIPT_LIB_DESTINATION, 'typescriptServices-amd.js'), stripSourceMaps(tsServices_amd)); + fs.writeFileSync( + path.join(TYPESCRIPT_LIB_DESTINATION, 'typescriptServices-amd.js'), + stripSourceMaps(tsServices_amd) + ); - var tsServices_esm = generatedNote + tsServices + + var tsServices_esm = + generatedNote + + tsServices + ` // MONACOCHANGE export var createClassifier = ts.createClassifier; @@ -85,17 +115,23 @@ export var ScriptTarget = ts.ScriptTarget; export var TokenClass = ts.TokenClass; // END MONACOCHANGE `; - fs.writeFileSync(path.join(TYPESCRIPT_LIB_DESTINATION, 'typescriptServices.js'), stripSourceMaps(tsServices_esm)); + fs.writeFileSync( + path.join(TYPESCRIPT_LIB_DESTINATION, 'typescriptServices.js'), + stripSourceMaps(tsServices_esm) + ); - var dtsServices = fs.readFileSync(path.join(TYPESCRIPT_LIB_SOURCE, 'typescriptServices.d.ts')).toString(); - dtsServices += - ` + var dtsServices = fs + .readFileSync(path.join(TYPESCRIPT_LIB_SOURCE, 'typescriptServices.d.ts')) + .toString(); + dtsServices += ` // MONACOCHANGE export = ts; // END MONACOCHANGE `; - fs.writeFileSync(path.join(TYPESCRIPT_LIB_DESTINATION, 'typescriptServices.d.ts'), generatedNote + dtsServices); - + fs.writeFileSync( + path.join(TYPESCRIPT_LIB_DESTINATION, 'typescriptServices.d.ts'), + generatedNote + dtsServices + ); })(); function importLibs() { @@ -112,9 +148,7 @@ ${generatedNote} /** Contains all the lib files */ export const libFileMap: Record = {} -` -; - +`; var strIndexResult = `/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. @@ -123,10 +157,10 @@ ${generatedNote} /** Contains all the lib files */ export const libFileSet: Record = {} -` -; - - var dtsFiles = fs.readdirSync(TYPESCRIPT_LIB_SOURCE).filter(f => f.includes("lib.")); +`; + var dtsFiles = fs + .readdirSync(TYPESCRIPT_LIB_SOURCE) + .filter((f) => f.includes('lib.')); while (dtsFiles.length > 0) { var name = dtsFiles.shift(); var output = readLibFile(name).replace(/\r\n/g, '\n'); @@ -134,8 +168,14 @@ export const libFileSet: Record = {} strIndexResult += `libFileSet['${name}'] = true;\n`; } - fs.writeFileSync(path.join(TYPESCRIPT_LIB_DESTINATION, 'lib.ts'), strLibResult); - fs.writeFileSync(path.join(TYPESCRIPT_LIB_DESTINATION, 'lib.index.ts'), strIndexResult); + fs.writeFileSync( + path.join(TYPESCRIPT_LIB_DESTINATION, 'lib.ts'), + strLibResult + ); + fs.writeFileSync( + path.join(TYPESCRIPT_LIB_DESTINATION, 'lib.index.ts'), + strIndexResult + ); } /** @@ -153,7 +193,10 @@ function escapeText(text) { var _backslash = '\\'.charCodeAt(0); var _doubleQuote = '"'.charCodeAt(0); - var startPos = 0, chrCode, replaceWith = null, resultPieces = []; + var startPos = 0, + chrCode, + replaceWith = null, + resultPieces = []; for (var i = 0, len = text.length; i < len; i++) { chrCode = text.charCodeAt(i); diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index b3fe20ce..909525fb 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -1,902 +1,1128 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { LanguageServiceDefaultsImpl } from './monaco.contribution'; -import * as ts from './lib/typescriptServices'; -import { TypeScriptWorker } from './tsWorker'; -import { libFileSet } from "./lib/lib.index" - -import Uri = monaco.Uri; -import Position = monaco.Position; -import Range = monaco.Range; -import CancellationToken = monaco.CancellationToken; -import IDisposable = monaco.IDisposable; - -//#region utils copied from typescript to prevent loading the entire typescriptServices --- - -enum IndentStyle { - None = 0, - Block = 1, - Smart = 2 -} - -export function flattenDiagnosticMessageText(diag: string | ts.DiagnosticMessageChain | undefined, newLine: string, indent = 0): string { - if (typeof diag === "string") { - return diag; - } - else if (diag === undefined) { - return ""; - } - let result = ""; - if (indent) { - result += newLine; - - for (let i = 0; i < indent; i++) { - result += " "; - } - } - result += diag.messageText; - indent++; - if (diag.next) { - for (const kid of diag.next) { - result += flattenDiagnosticMessageText(kid, newLine, indent); - } - } - return result; -} - -function displayPartsToString(displayParts: ts.SymbolDisplayPart[] | undefined): string { - if (displayParts) { - return displayParts.map((displayPart) => displayPart.text).join(""); - } - return ""; -} - -//#endregion - -export abstract class Adapter { - - constructor(protected _worker: (...uris: Uri[]) => Promise) { - } - - // protected _positionToOffset(model: monaco.editor.ITextModel, position: monaco.IPosition): number { - // return model.getOffsetAt(position); - // } - - // protected _offsetToPosition(model: monaco.editor.ITextModel, offset: number): monaco.IPosition { - // return model.getPositionAt(offset); - // } - - protected _textSpanToRange(model: monaco.editor.ITextModel, span: ts.TextSpan): monaco.IRange { - let p1 = model.getPositionAt(span.start); - let p2 = model.getPositionAt(span.start + span.length); - let { lineNumber: startLineNumber, column: startColumn } = p1; - let { lineNumber: endLineNumber, column: endColumn } = p2; - return { startLineNumber, startColumn, endLineNumber, endColumn }; - } -} - -// --- lib files - -export class LibFiles { - - private _libFiles: Record; - private _hasFetchedLibFiles: boolean; - private _fetchLibFilesPromise: Promise | null; - - constructor( - private readonly _worker: (...uris: Uri[]) => Promise - ) { - this._libFiles = {}; - this._hasFetchedLibFiles = false; - this._fetchLibFilesPromise = null; - } - - public isLibFile(uri: Uri | null): boolean { - if (!uri) { - return false; - } - if (uri.path.indexOf("/lib.") === 0) { - return !!libFileSet[uri.path.slice(1)]; - } - return false; - } - - public getOrCreateModel(uri: Uri): monaco.editor.ITextModel | null { - const model = monaco.editor.getModel(uri); - if (model) { - return model; - } - if (this.isLibFile(uri) && this._hasFetchedLibFiles) { - return monaco.editor.createModel(this._libFiles[uri.path.slice(1)], "javascript", uri); - } - return null; - } - - private _containsLibFile(uris: (Uri | null)[]): boolean { - for (let uri of uris) { - if (this.isLibFile(uri)) { - return true; - } - } - return false; - } - - public async fetchLibFilesIfNecessary(uris: (Uri | null)[]): Promise { - if (!this._containsLibFile(uris)) { - // no lib files necessary - return; - } - await this._fetchLibFiles(); - } - - private _fetchLibFiles(): Promise { - if (!this._fetchLibFilesPromise) { - this._fetchLibFilesPromise = ( - this._worker() - .then(w => w.getLibFiles()) - .then((libFiles) => { - this._hasFetchedLibFiles = true; - this._libFiles = libFiles; - }) - ); - } - return this._fetchLibFilesPromise; - } -} - -// --- diagnostics --- --- - -enum DiagnosticCategory { - Warning = 0, - Error = 1, - Suggestion = 2, - Message = 3 -} - -export class DiagnosticsAdapter extends Adapter { - - private _disposables: IDisposable[] = []; - private _listener: { [uri: string]: IDisposable } = Object.create(null); - - constructor( - private readonly _libFiles: LibFiles, - private _defaults: LanguageServiceDefaultsImpl, - private _selector: string, - worker: (...uris: Uri[]) => Promise - ) { - super(worker); - - const onModelAdd = (model: monaco.editor.IModel): void => { - if (model.getModeId() !== _selector) { - return; - } - - let handle: number; - const changeSubscription = model.onDidChangeContent(() => { - clearTimeout(handle); - handle = setTimeout(() => this._doValidate(model), 500); - }); - - this._listener[model.uri.toString()] = { - dispose() { - changeSubscription.dispose(); - clearTimeout(handle); - } - }; - - this._doValidate(model); - }; - - const onModelRemoved = (model: monaco.editor.IModel): void => { - monaco.editor.setModelMarkers(model, this._selector, []); - const key = model.uri.toString(); - if (this._listener[key]) { - this._listener[key].dispose(); - delete this._listener[key]; - } - }; - - this._disposables.push(monaco.editor.onDidCreateModel(onModelAdd)); - this._disposables.push(monaco.editor.onWillDisposeModel(onModelRemoved)); - this._disposables.push(monaco.editor.onDidChangeModelLanguage(event => { - onModelRemoved(event.model); - onModelAdd(event.model); - })); - - this._disposables.push({ - dispose() { - for (const model of monaco.editor.getModels()) { - onModelRemoved(model); - } - } - }); - - const recomputeDiagostics = () => { - // redo diagnostics when options change - for (const model of monaco.editor.getModels()) { - onModelRemoved(model); - onModelAdd(model); - } - }; - this._disposables.push(this._defaults.onDidChange(recomputeDiagostics)); - this._disposables.push(this._defaults.onDidExtraLibsChange(recomputeDiagostics)); - - monaco.editor.getModels().forEach(onModelAdd); - } - - public dispose(): void { - this._disposables.forEach(d => d && d.dispose()); - this._disposables = []; - } - - private async _doValidate(model: monaco.editor.ITextModel): Promise { - const worker = await this._worker(model.uri); - - if (model.isDisposed()) { - // model was disposed in the meantime - return; - } - - const promises: Promise[] = []; - const { noSyntaxValidation, noSemanticValidation, noSuggestionDiagnostics } = this._defaults.getDiagnosticsOptions(); - if (!noSyntaxValidation) { - promises.push(worker.getSyntacticDiagnostics(model.uri.toString())); - } - if (!noSemanticValidation) { - promises.push(worker.getSemanticDiagnostics(model.uri.toString())); - } - if (!noSuggestionDiagnostics) { - promises.push(worker.getSuggestionDiagnostics(model.uri.toString())); - } - - const allDiagnostics = await Promise.all(promises); - - if (!allDiagnostics || model.isDisposed()) { - // model was disposed in the meantime - return; - } - - const diagnostics = allDiagnostics - .reduce((p, c) => c.concat(p), []) - .filter(d => (this._defaults.getDiagnosticsOptions().diagnosticCodesToIgnore || []).indexOf(d.code) === -1); - - // Fetch lib files if necessary - const relatedUris = diagnostics - .map(d => d.relatedInformation || []) - .reduce((p, c) => c.concat(p), []) - .map(relatedInformation => relatedInformation.file ? monaco.Uri.parse(relatedInformation.file.fileName) : null); - - await this._libFiles.fetchLibFilesIfNecessary(relatedUris); - - if (model.isDisposed()) { - // model was disposed in the meantime - return; - } - - monaco.editor.setModelMarkers(model, this._selector, diagnostics.map(d => this._convertDiagnostics(model, d))); - } - - private _convertDiagnostics(model: monaco.editor.ITextModel, diag: ts.Diagnostic): monaco.editor.IMarkerData { - const diagStart = diag.start || 0; - const diagLength = diag.length || 1; - const { lineNumber: startLineNumber, column: startColumn } = model.getPositionAt(diagStart); - const { lineNumber: endLineNumber, column: endColumn } = model.getPositionAt(diagStart + diagLength); - - return { - severity: this._tsDiagnosticCategoryToMarkerSeverity(diag.category), - startLineNumber, - startColumn, - endLineNumber, - endColumn, - message: flattenDiagnosticMessageText(diag.messageText, '\n'), - code: diag.code.toString(), - tags: diag.reportsUnnecessary ? [monaco.MarkerTag.Unnecessary] : [], - relatedInformation: this._convertRelatedInformation(model, diag.relatedInformation), - }; - } - - private _convertRelatedInformation(model: monaco.editor.ITextModel, relatedInformation?: ts.DiagnosticRelatedInformation[]): monaco.editor.IRelatedInformation[] | undefined { - if (!relatedInformation) { - return; - } - - const result: monaco.editor.IRelatedInformation[] = []; - relatedInformation.forEach((info) => { - let relatedResource: monaco.editor.ITextModel | null = model; - if (info.file) { - const relatedResourceUri = monaco.Uri.parse(info.file.fileName); - relatedResource = this._libFiles.getOrCreateModel(relatedResourceUri); - } - - if (!relatedResource) { - return; - } - const infoStart = info.start || 0; - const infoLength = info.length || 1; - const { lineNumber: startLineNumber, column: startColumn } = relatedResource.getPositionAt(infoStart); - const { lineNumber: endLineNumber, column: endColumn } = relatedResource.getPositionAt(infoStart + infoLength); - - result.push({ - resource: relatedResource.uri, - startLineNumber, - startColumn, - endLineNumber, - endColumn, - message: flattenDiagnosticMessageText(info.messageText, '\n') - }); - }); - return result; - } - - private _tsDiagnosticCategoryToMarkerSeverity(category: ts.DiagnosticCategory): monaco.MarkerSeverity { - switch (category) { - case DiagnosticCategory.Error: return monaco.MarkerSeverity.Error - case DiagnosticCategory.Message: return monaco.MarkerSeverity.Info - case DiagnosticCategory.Warning: return monaco.MarkerSeverity.Warning - case DiagnosticCategory.Suggestion: return monaco.MarkerSeverity.Hint - } - return monaco.MarkerSeverity.Info; - } -} - -// --- suggest ------ - -interface MyCompletionItem extends monaco.languages.CompletionItem { - label: string; - uri: Uri; - position: Position; -} - -export class SuggestAdapter extends Adapter implements monaco.languages.CompletionItemProvider { - - public get triggerCharacters(): string[] { - return ['.']; - } - - public async provideCompletionItems(model: monaco.editor.ITextModel, position: Position, _context: monaco.languages.CompletionContext, token: CancellationToken): Promise { - const wordInfo = model.getWordUntilPosition(position); - const wordRange = new Range(position.lineNumber, wordInfo.startColumn, position.lineNumber, wordInfo.endColumn); - const resource = model.uri; - const offset = model.getOffsetAt(position); - - const worker = await this._worker(resource); - const info = await worker.getCompletionsAtPosition(resource.toString(), offset); - - if (!info || model.isDisposed()) { - return; - } - - const suggestions: MyCompletionItem[] = info.entries.map(entry => { - let range = wordRange; - if (entry.replacementSpan) { - const p1 = model.getPositionAt(entry.replacementSpan.start); - const p2 = model.getPositionAt(entry.replacementSpan.start + entry.replacementSpan.length); - range = new Range(p1.lineNumber, p1.column, p2.lineNumber, p2.column); - } - - return { - uri: resource, - position: position, - range: range, - label: entry.name, - insertText: entry.name, - sortText: entry.sortText, - kind: SuggestAdapter.convertKind(entry.kind) - }; - }); - - return { - suggestions - }; - } - - public async resolveCompletionItem(model: monaco.editor.ITextModel, _position: Position, item: monaco.languages.CompletionItem, token: CancellationToken): Promise { - const myItem = item; - const resource = myItem.uri; - const position = myItem.position; - const offset = model.getOffsetAt(position); - - const worker = await this._worker(resource); - const details = await worker.getCompletionEntryDetails(resource.toString(), offset, myItem.label); - if (!details || model.isDisposed()) { - return myItem; - } - return { - uri: resource, - position: position, - label: details.name, - kind: SuggestAdapter.convertKind(details.kind), - detail: displayPartsToString(details.displayParts), - documentation: { - value: displayPartsToString(details.documentation) - } - }; - } - - private static convertKind(kind: string): monaco.languages.CompletionItemKind { - switch (kind) { - case Kind.primitiveType: - case Kind.keyword: - return monaco.languages.CompletionItemKind.Keyword; - case Kind.variable: - case Kind.localVariable: - return monaco.languages.CompletionItemKind.Variable; - case Kind.memberVariable: - case Kind.memberGetAccessor: - case Kind.memberSetAccessor: - return monaco.languages.CompletionItemKind.Field; - case Kind.function: - case Kind.memberFunction: - case Kind.constructSignature: - case Kind.callSignature: - case Kind.indexSignature: - return monaco.languages.CompletionItemKind.Function; - case Kind.enum: - return monaco.languages.CompletionItemKind.Enum; - case Kind.module: - return monaco.languages.CompletionItemKind.Module; - case Kind.class: - return monaco.languages.CompletionItemKind.Class; - case Kind.interface: - return monaco.languages.CompletionItemKind.Interface; - case Kind.warning: - return monaco.languages.CompletionItemKind.File; - } - - return monaco.languages.CompletionItemKind.Property; - } -} - -export class SignatureHelpAdapter extends Adapter implements monaco.languages.SignatureHelpProvider { - - public signatureHelpTriggerCharacters = ['(', ',']; - - public async provideSignatureHelp(model: monaco.editor.ITextModel, position: Position, token: CancellationToken): Promise { - const resource = model.uri; - const offset = model.getOffsetAt(position); - const worker = await this._worker(resource); - const info = await worker.getSignatureHelpItems(resource.toString(), offset); - - if (!info || model.isDisposed()) { - return; - } - - const ret: monaco.languages.SignatureHelp = { - activeSignature: info.selectedItemIndex, - activeParameter: info.argumentIndex, - signatures: [] - }; - - info.items.forEach(item => { - - const signature: monaco.languages.SignatureInformation = { - label: '', - parameters: [] - }; - - signature.documentation = { - value: displayPartsToString(item.documentation) - }; - signature.label += displayPartsToString(item.prefixDisplayParts); - item.parameters.forEach((p, i, a) => { - const label = displayPartsToString(p.displayParts); - const parameter: monaco.languages.ParameterInformation = { - label: label, - documentation: { - value: displayPartsToString(p.documentation) - } - }; - signature.label += label; - signature.parameters.push(parameter); - if (i < a.length - 1) { - signature.label += displayPartsToString(item.separatorDisplayParts); - } - }); - signature.label += displayPartsToString(item.suffixDisplayParts); - ret.signatures.push(signature); - }); - - return { - value: ret, - dispose() { } - }; - } -} - -// --- hover ------ - -export class QuickInfoAdapter extends Adapter implements monaco.languages.HoverProvider { - - public async provideHover(model: monaco.editor.ITextModel, position: Position, token: CancellationToken): Promise { - const resource = model.uri; - const offset = model.getOffsetAt(position); - const worker = await this._worker(resource); - const info = await worker.getQuickInfoAtPosition(resource.toString(), offset); - - if (!info || model.isDisposed()) { - return; - } - - const documentation = displayPartsToString(info.documentation); - const tags = info.tags ? info.tags.map(tag => { - const label = `*@${tag.name}*`; - if (!tag.text) { - return label; - } - return label + (tag.text.match(/\r\n|\n/g) ? ' \n' + tag.text : ` - ${tag.text}`); - }).join(' \n\n') : ''; - const contents = displayPartsToString(info.displayParts); - return { - range: this._textSpanToRange(model, info.textSpan), - contents: [{ - value: '```js\n' + contents + '\n```\n' - }, { - value: documentation + (tags ? '\n\n' + tags : '') - }] - }; - } -} - -// --- occurrences ------ - -export class OccurrencesAdapter extends Adapter implements monaco.languages.DocumentHighlightProvider { - - public async provideDocumentHighlights(model: monaco.editor.ITextModel, position: Position, token: CancellationToken): Promise { - const resource = model.uri; - const offset = model.getOffsetAt(position) - const worker = await this._worker(resource); - const entries = await worker.getOccurrencesAtPosition(resource.toString(), offset); - - if (!entries || model.isDisposed()) { - return; - } - - return entries.map(entry => { - return { - range: this._textSpanToRange(model, entry.textSpan), - kind: entry.isWriteAccess ? monaco.languages.DocumentHighlightKind.Write : monaco.languages.DocumentHighlightKind.Text - }; - }); - } -} - -// --- definition ------ - -export class DefinitionAdapter extends Adapter { - - constructor( - private readonly _libFiles: LibFiles, - worker: (...uris: Uri[]) => Promise - ) { - super(worker); - } - - public async provideDefinition(model: monaco.editor.ITextModel, position: Position, token: CancellationToken): Promise { - const resource = model.uri; - const offset = model.getOffsetAt(position); - const worker = await this._worker(resource); - const entries = await worker.getDefinitionAtPosition(resource.toString(), offset); - - if (!entries || model.isDisposed()) { - return; - } - - // Fetch lib files if necessary - await this._libFiles.fetchLibFilesIfNecessary(entries.map(entry => Uri.parse(entry.fileName))); - - if (model.isDisposed()) { - return; - } - - const result: monaco.languages.Location[] = []; - for (let entry of entries) { - const uri = Uri.parse(entry.fileName); - const refModel = this._libFiles.getOrCreateModel(uri); - if (refModel) { - result.push({ - uri: uri, - range: this._textSpanToRange(refModel, entry.textSpan) - }); - } - } - return result; - } -} - -// --- references ------ - -export class ReferenceAdapter extends Adapter implements monaco.languages.ReferenceProvider { - - constructor( - private readonly _libFiles: LibFiles, - worker: (...uris: Uri[]) => Promise - ) { - super(worker); - } - - public async provideReferences(model: monaco.editor.ITextModel, position: Position, context: monaco.languages.ReferenceContext, token: CancellationToken): Promise { - const resource = model.uri; - const offset = model.getOffsetAt(position); - const worker = await this._worker(resource); - const entries = await worker.getReferencesAtPosition(resource.toString(), offset); - - if (!entries || model.isDisposed()) { - return; - } - - // Fetch lib files if necessary - await this._libFiles.fetchLibFilesIfNecessary(entries.map(entry => Uri.parse(entry.fileName))); - - if (model.isDisposed()) { - return; - } - - const result: monaco.languages.Location[] = []; - for (let entry of entries) { - const uri = Uri.parse(entry.fileName); - const refModel = this._libFiles.getOrCreateModel(uri); - if (refModel) { - result.push({ - uri: uri, - range: this._textSpanToRange(refModel, entry.textSpan) - }); - } - } - return result; - } -} - -// --- outline ------ - -export class OutlineAdapter extends Adapter implements monaco.languages.DocumentSymbolProvider { - - public async provideDocumentSymbols(model: monaco.editor.ITextModel, token: CancellationToken): Promise { - const resource = model.uri; - const worker = await this._worker(resource); - const items = await worker.getNavigationBarItems(resource.toString()); - - if (!items || model.isDisposed()) { - return; - } - - const convert = (bucket: monaco.languages.DocumentSymbol[], item: ts.NavigationBarItem, containerLabel?: string): void => { - let result: monaco.languages.DocumentSymbol = { - name: item.text, - detail: '', - kind: (outlineTypeTable[item.kind] || monaco.languages.SymbolKind.Variable), - range: this._textSpanToRange(model, item.spans[0]), - selectionRange: this._textSpanToRange(model, item.spans[0]), - tags: [], - containerName: containerLabel - }; - - if (item.childItems && item.childItems.length > 0) { - for (let child of item.childItems) { - convert(bucket, child, result.name); - } - } - - bucket.push(result); - } - - let result: monaco.languages.DocumentSymbol[] = []; - items.forEach(item => convert(result, item)); - return result; - } -} - -export class Kind { - public static unknown: string = ''; - public static keyword: string = 'keyword'; - public static script: string = 'script'; - public static module: string = 'module'; - public static class: string = 'class'; - public static interface: string = 'interface'; - public static type: string = 'type'; - public static enum: string = 'enum'; - public static variable: string = 'var'; - public static localVariable: string = 'local var'; - public static function: string = 'function'; - public static localFunction: string = 'local function'; - public static memberFunction: string = 'method'; - public static memberGetAccessor: string = 'getter'; - public static memberSetAccessor: string = 'setter'; - public static memberVariable: string = 'property'; - public static constructorImplementation: string = 'constructor'; - public static callSignature: string = 'call'; - public static indexSignature: string = 'index'; - public static constructSignature: string = 'construct'; - public static parameter: string = 'parameter'; - public static typeParameter: string = 'type parameter'; - public static primitiveType: string = 'primitive type'; - public static label: string = 'label'; - public static alias: string = 'alias'; - public static const: string = 'const'; - public static let: string = 'let'; - public static warning: string = 'warning'; -} - -let outlineTypeTable: { [kind: string]: monaco.languages.SymbolKind } = Object.create(null); -outlineTypeTable[Kind.module] = monaco.languages.SymbolKind.Module; -outlineTypeTable[Kind.class] = monaco.languages.SymbolKind.Class; -outlineTypeTable[Kind.enum] = monaco.languages.SymbolKind.Enum; -outlineTypeTable[Kind.interface] = monaco.languages.SymbolKind.Interface; -outlineTypeTable[Kind.memberFunction] = monaco.languages.SymbolKind.Method; -outlineTypeTable[Kind.memberVariable] = monaco.languages.SymbolKind.Property; -outlineTypeTable[Kind.memberGetAccessor] = monaco.languages.SymbolKind.Property; -outlineTypeTable[Kind.memberSetAccessor] = monaco.languages.SymbolKind.Property; -outlineTypeTable[Kind.variable] = monaco.languages.SymbolKind.Variable; -outlineTypeTable[Kind.const] = monaco.languages.SymbolKind.Variable; -outlineTypeTable[Kind.localVariable] = monaco.languages.SymbolKind.Variable; -outlineTypeTable[Kind.variable] = monaco.languages.SymbolKind.Variable; -outlineTypeTable[Kind.function] = monaco.languages.SymbolKind.Function; -outlineTypeTable[Kind.localFunction] = monaco.languages.SymbolKind.Function; - -// --- formatting ---- - -export abstract class FormatHelper extends Adapter { - protected static _convertOptions(options: monaco.languages.FormattingOptions): ts.FormatCodeOptions { - return { - ConvertTabsToSpaces: options.insertSpaces, - TabSize: options.tabSize, - IndentSize: options.tabSize, - IndentStyle: IndentStyle.Smart, - NewLineCharacter: '\n', - InsertSpaceAfterCommaDelimiter: true, - InsertSpaceAfterSemicolonInForStatements: true, - InsertSpaceBeforeAndAfterBinaryOperators: true, - InsertSpaceAfterKeywordsInControlFlowStatements: true, - InsertSpaceAfterFunctionKeywordForAnonymousFunctions: true, - InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - PlaceOpenBraceOnNewLineForControlBlocks: false, - PlaceOpenBraceOnNewLineForFunctions: false - }; - } - - protected _convertTextChanges(model: monaco.editor.ITextModel, change: ts.TextChange): monaco.languages.TextEdit { - return { - text: change.newText, - range: this._textSpanToRange(model, change.span) - }; - } -} - -export class FormatAdapter extends FormatHelper implements monaco.languages.DocumentRangeFormattingEditProvider { - - public async provideDocumentRangeFormattingEdits(model: monaco.editor.ITextModel, range: Range, options: monaco.languages.FormattingOptions, token: CancellationToken): Promise { - const resource = model.uri; - const startOffset = model.getOffsetAt({ lineNumber: range.startLineNumber, column: range.startColumn }); - const endOffset = model.getOffsetAt({ lineNumber: range.endLineNumber, column: range.endColumn }); - const worker = await this._worker(resource); - const edits = await worker.getFormattingEditsForRange(resource.toString(), startOffset, endOffset, FormatHelper._convertOptions(options)); - - if (!edits || model.isDisposed()) { - return; - } - - return edits.map(edit => this._convertTextChanges(model, edit)); - } -} - -export class FormatOnTypeAdapter extends FormatHelper implements monaco.languages.OnTypeFormattingEditProvider { - - get autoFormatTriggerCharacters() { - return [';', '}', '\n']; - } - - public async provideOnTypeFormattingEdits(model: monaco.editor.ITextModel, position: Position, ch: string, options: monaco.languages.FormattingOptions, token: CancellationToken): Promise { - const resource = model.uri; - const offset = model.getOffsetAt(position); - const worker = await this._worker(resource); - const edits = await worker.getFormattingEditsAfterKeystroke(resource.toString(), offset, ch, FormatHelper._convertOptions(options)); - - if (!edits || model.isDisposed()) { - return; - } - - return edits.map(edit => this._convertTextChanges(model, edit)); - } -} - -// --- code actions ------ - -export class CodeActionAdaptor extends FormatHelper implements monaco.languages.CodeActionProvider { - - public async provideCodeActions(model: monaco.editor.ITextModel, range: Range, context: monaco.languages.CodeActionContext, token: CancellationToken): Promise { - const resource = model.uri; - const start = model.getOffsetAt({ lineNumber: range.startLineNumber, column: range.startColumn }); - const end = model.getOffsetAt({ lineNumber: range.endLineNumber, column: range.endColumn }); - const formatOptions = FormatHelper._convertOptions(model.getOptions()); - const errorCodes = context.markers.filter(m => m.code).map(m => m.code).map(Number); - const worker = await this._worker(resource); - const codeFixes = await worker.getCodeFixesAtPosition(resource.toString(), start, end, errorCodes, formatOptions); - - if (!codeFixes || model.isDisposed()) { - return { actions: [], dispose: () => { } }; - } - - const actions = codeFixes.filter(fix => { - // Removes any 'make a new file'-type code fix - return fix.changes.filter(change => change.isNewFile).length === 0; - }).map(fix => { - return this._tsCodeFixActionToMonacoCodeAction(model, context, fix); - }); - - return { - actions: actions, - dispose: () => { } - }; - } - - - private _tsCodeFixActionToMonacoCodeAction(model: monaco.editor.ITextModel, context: monaco.languages.CodeActionContext, codeFix: ts.CodeFixAction): monaco.languages.CodeAction { - const edits: monaco.languages.WorkspaceTextEdit[] = []; - for (const change of codeFix.changes) { - for (const textChange of change.textChanges) { - edits.push({ - resource: model.uri, - edit: { - range: this._textSpanToRange(model, textChange.span), - text: textChange.newText - } - }); - } - } - - const action: monaco.languages.CodeAction = { - title: codeFix.description, - edit: { edits: edits }, - diagnostics: context.markers, - kind: "quickfix" - }; - - return action; - } -} -// --- rename ---- - -export class RenameAdapter extends Adapter implements monaco.languages.RenameProvider { - - public async provideRenameEdits(model: monaco.editor.ITextModel, position: Position, newName: string, token: CancellationToken): Promise { - const resource = model.uri; - const fileName = resource.toString(); - const offset = model.getOffsetAt(position); - const worker = await this._worker(resource); - - const renameInfo = await worker.getRenameInfo(fileName, offset, { allowRenameOfImportPath: false }); - if (renameInfo.canRename === false) { // use explicit comparison so that the discriminated union gets resolved properly - return { - edits: [], - rejectReason: renameInfo.localizedErrorMessage - }; - } - if (renameInfo.fileToRename !== undefined) { - throw new Error("Renaming files is not supported."); - } - - const renameLocations = await worker.findRenameLocations(fileName, offset, /*strings*/ false, /*comments*/ false, /*prefixAndSuffix*/ false); - - if (!renameLocations || model.isDisposed()) { - return; - } - - const edits: monaco.languages.WorkspaceTextEdit[] = []; - for (const renameLocation of renameLocations) { - edits.push({ - resource: monaco.Uri.parse(renameLocation.fileName), - edit: { - range: this._textSpanToRange(model, renameLocation.textSpan), - text: newName - } - }); - } - - return { edits }; - } -} +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { LanguageServiceDefaultsImpl } from './monaco.contribution'; +import * as ts from './lib/typescriptServices'; +import { TypeScriptWorker } from './tsWorker'; +import { libFileSet } from './lib/lib.index'; + +import Uri = monaco.Uri; +import Position = monaco.Position; +import Range = monaco.Range; +import CancellationToken = monaco.CancellationToken; +import IDisposable = monaco.IDisposable; + +//#region utils copied from typescript to prevent loading the entire typescriptServices --- + +enum IndentStyle { + None = 0, + Block = 1, + Smart = 2 +} + +export function flattenDiagnosticMessageText( + diag: string | ts.DiagnosticMessageChain | undefined, + newLine: string, + indent = 0 +): string { + if (typeof diag === 'string') { + return diag; + } else if (diag === undefined) { + return ''; + } + let result = ''; + if (indent) { + result += newLine; + + for (let i = 0; i < indent; i++) { + result += ' '; + } + } + result += diag.messageText; + indent++; + if (diag.next) { + for (const kid of diag.next) { + result += flattenDiagnosticMessageText(kid, newLine, indent); + } + } + return result; +} + +function displayPartsToString( + displayParts: ts.SymbolDisplayPart[] | undefined +): string { + if (displayParts) { + return displayParts.map((displayPart) => displayPart.text).join(''); + } + return ''; +} + +//#endregion + +export abstract class Adapter { + constructor( + protected _worker: (...uris: Uri[]) => Promise + ) {} + + // protected _positionToOffset(model: monaco.editor.ITextModel, position: monaco.IPosition): number { + // return model.getOffsetAt(position); + // } + + // protected _offsetToPosition(model: monaco.editor.ITextModel, offset: number): monaco.IPosition { + // return model.getPositionAt(offset); + // } + + protected _textSpanToRange( + model: monaco.editor.ITextModel, + span: ts.TextSpan + ): monaco.IRange { + let p1 = model.getPositionAt(span.start); + let p2 = model.getPositionAt(span.start + span.length); + let { lineNumber: startLineNumber, column: startColumn } = p1; + let { lineNumber: endLineNumber, column: endColumn } = p2; + return { startLineNumber, startColumn, endLineNumber, endColumn }; + } +} + +// --- lib files + +export class LibFiles { + private _libFiles: Record; + private _hasFetchedLibFiles: boolean; + private _fetchLibFilesPromise: Promise | null; + + constructor( + private readonly _worker: (...uris: Uri[]) => Promise + ) { + this._libFiles = {}; + this._hasFetchedLibFiles = false; + this._fetchLibFilesPromise = null; + } + + public isLibFile(uri: Uri | null): boolean { + if (!uri) { + return false; + } + if (uri.path.indexOf('/lib.') === 0) { + return !!libFileSet[uri.path.slice(1)]; + } + return false; + } + + public getOrCreateModel(uri: Uri): monaco.editor.ITextModel | null { + const model = monaco.editor.getModel(uri); + if (model) { + return model; + } + if (this.isLibFile(uri) && this._hasFetchedLibFiles) { + return monaco.editor.createModel( + this._libFiles[uri.path.slice(1)], + 'javascript', + uri + ); + } + return null; + } + + private _containsLibFile(uris: (Uri | null)[]): boolean { + for (let uri of uris) { + if (this.isLibFile(uri)) { + return true; + } + } + return false; + } + + public async fetchLibFilesIfNecessary(uris: (Uri | null)[]): Promise { + if (!this._containsLibFile(uris)) { + // no lib files necessary + return; + } + await this._fetchLibFiles(); + } + + private _fetchLibFiles(): Promise { + if (!this._fetchLibFilesPromise) { + this._fetchLibFilesPromise = this._worker() + .then((w) => w.getLibFiles()) + .then((libFiles) => { + this._hasFetchedLibFiles = true; + this._libFiles = libFiles; + }); + } + return this._fetchLibFilesPromise; + } +} + +// --- diagnostics --- --- + +enum DiagnosticCategory { + Warning = 0, + Error = 1, + Suggestion = 2, + Message = 3 +} + +export class DiagnosticsAdapter extends Adapter { + private _disposables: IDisposable[] = []; + private _listener: { [uri: string]: IDisposable } = Object.create(null); + + constructor( + private readonly _libFiles: LibFiles, + private _defaults: LanguageServiceDefaultsImpl, + private _selector: string, + worker: (...uris: Uri[]) => Promise + ) { + super(worker); + + const onModelAdd = (model: monaco.editor.IModel): void => { + if (model.getModeId() !== _selector) { + return; + } + + let handle: number; + const changeSubscription = model.onDidChangeContent(() => { + clearTimeout(handle); + handle = setTimeout(() => this._doValidate(model), 500); + }); + + this._listener[model.uri.toString()] = { + dispose() { + changeSubscription.dispose(); + clearTimeout(handle); + } + }; + + this._doValidate(model); + }; + + const onModelRemoved = (model: monaco.editor.IModel): void => { + monaco.editor.setModelMarkers(model, this._selector, []); + const key = model.uri.toString(); + if (this._listener[key]) { + this._listener[key].dispose(); + delete this._listener[key]; + } + }; + + this._disposables.push(monaco.editor.onDidCreateModel(onModelAdd)); + this._disposables.push(monaco.editor.onWillDisposeModel(onModelRemoved)); + this._disposables.push( + monaco.editor.onDidChangeModelLanguage((event) => { + onModelRemoved(event.model); + onModelAdd(event.model); + }) + ); + + this._disposables.push({ + dispose() { + for (const model of monaco.editor.getModels()) { + onModelRemoved(model); + } + } + }); + + const recomputeDiagostics = () => { + // redo diagnostics when options change + for (const model of monaco.editor.getModels()) { + onModelRemoved(model); + onModelAdd(model); + } + }; + this._disposables.push(this._defaults.onDidChange(recomputeDiagostics)); + this._disposables.push( + this._defaults.onDidExtraLibsChange(recomputeDiagostics) + ); + + monaco.editor.getModels().forEach(onModelAdd); + } + + public dispose(): void { + this._disposables.forEach((d) => d && d.dispose()); + this._disposables = []; + } + + private async _doValidate(model: monaco.editor.ITextModel): Promise { + const worker = await this._worker(model.uri); + + if (model.isDisposed()) { + // model was disposed in the meantime + return; + } + + const promises: Promise[] = []; + const { + noSyntaxValidation, + noSemanticValidation, + noSuggestionDiagnostics + } = this._defaults.getDiagnosticsOptions(); + if (!noSyntaxValidation) { + promises.push(worker.getSyntacticDiagnostics(model.uri.toString())); + } + if (!noSemanticValidation) { + promises.push(worker.getSemanticDiagnostics(model.uri.toString())); + } + if (!noSuggestionDiagnostics) { + promises.push(worker.getSuggestionDiagnostics(model.uri.toString())); + } + + const allDiagnostics = await Promise.all(promises); + + if (!allDiagnostics || model.isDisposed()) { + // model was disposed in the meantime + return; + } + + const diagnostics = allDiagnostics + .reduce((p, c) => c.concat(p), []) + .filter( + (d) => + ( + this._defaults.getDiagnosticsOptions().diagnosticCodesToIgnore || [] + ).indexOf(d.code) === -1 + ); + + // Fetch lib files if necessary + const relatedUris = diagnostics + .map((d) => d.relatedInformation || []) + .reduce((p, c) => c.concat(p), []) + .map((relatedInformation) => + relatedInformation.file + ? monaco.Uri.parse(relatedInformation.file.fileName) + : null + ); + + await this._libFiles.fetchLibFilesIfNecessary(relatedUris); + + if (model.isDisposed()) { + // model was disposed in the meantime + return; + } + + monaco.editor.setModelMarkers( + model, + this._selector, + diagnostics.map((d) => this._convertDiagnostics(model, d)) + ); + } + + private _convertDiagnostics( + model: monaco.editor.ITextModel, + diag: ts.Diagnostic + ): monaco.editor.IMarkerData { + const diagStart = diag.start || 0; + const diagLength = diag.length || 1; + const { + lineNumber: startLineNumber, + column: startColumn + } = model.getPositionAt(diagStart); + const { + lineNumber: endLineNumber, + column: endColumn + } = model.getPositionAt(diagStart + diagLength); + + return { + severity: this._tsDiagnosticCategoryToMarkerSeverity(diag.category), + startLineNumber, + startColumn, + endLineNumber, + endColumn, + message: flattenDiagnosticMessageText(diag.messageText, '\n'), + code: diag.code.toString(), + tags: diag.reportsUnnecessary ? [monaco.MarkerTag.Unnecessary] : [], + relatedInformation: this._convertRelatedInformation( + model, + diag.relatedInformation + ) + }; + } + + private _convertRelatedInformation( + model: monaco.editor.ITextModel, + relatedInformation?: ts.DiagnosticRelatedInformation[] + ): monaco.editor.IRelatedInformation[] | undefined { + if (!relatedInformation) { + return; + } + + const result: monaco.editor.IRelatedInformation[] = []; + relatedInformation.forEach((info) => { + let relatedResource: monaco.editor.ITextModel | null = model; + if (info.file) { + const relatedResourceUri = monaco.Uri.parse(info.file.fileName); + relatedResource = this._libFiles.getOrCreateModel(relatedResourceUri); + } + + if (!relatedResource) { + return; + } + const infoStart = info.start || 0; + const infoLength = info.length || 1; + const { + lineNumber: startLineNumber, + column: startColumn + } = relatedResource.getPositionAt(infoStart); + const { + lineNumber: endLineNumber, + column: endColumn + } = relatedResource.getPositionAt(infoStart + infoLength); + + result.push({ + resource: relatedResource.uri, + startLineNumber, + startColumn, + endLineNumber, + endColumn, + message: flattenDiagnosticMessageText(info.messageText, '\n') + }); + }); + return result; + } + + private _tsDiagnosticCategoryToMarkerSeverity( + category: ts.DiagnosticCategory + ): monaco.MarkerSeverity { + switch (category) { + case DiagnosticCategory.Error: + return monaco.MarkerSeverity.Error; + case DiagnosticCategory.Message: + return monaco.MarkerSeverity.Info; + case DiagnosticCategory.Warning: + return monaco.MarkerSeverity.Warning; + case DiagnosticCategory.Suggestion: + return monaco.MarkerSeverity.Hint; + } + return monaco.MarkerSeverity.Info; + } +} + +// --- suggest ------ + +interface MyCompletionItem extends monaco.languages.CompletionItem { + label: string; + uri: Uri; + position: Position; +} + +export class SuggestAdapter + extends Adapter + implements monaco.languages.CompletionItemProvider { + public get triggerCharacters(): string[] { + return ['.']; + } + + public async provideCompletionItems( + model: monaco.editor.ITextModel, + position: Position, + _context: monaco.languages.CompletionContext, + token: CancellationToken + ): Promise { + const wordInfo = model.getWordUntilPosition(position); + const wordRange = new Range( + position.lineNumber, + wordInfo.startColumn, + position.lineNumber, + wordInfo.endColumn + ); + const resource = model.uri; + const offset = model.getOffsetAt(position); + + const worker = await this._worker(resource); + const info = await worker.getCompletionsAtPosition( + resource.toString(), + offset + ); + + if (!info || model.isDisposed()) { + return; + } + + const suggestions: MyCompletionItem[] = info.entries.map((entry) => { + let range = wordRange; + if (entry.replacementSpan) { + const p1 = model.getPositionAt(entry.replacementSpan.start); + const p2 = model.getPositionAt( + entry.replacementSpan.start + entry.replacementSpan.length + ); + range = new Range(p1.lineNumber, p1.column, p2.lineNumber, p2.column); + } + + return { + uri: resource, + position: position, + range: range, + label: entry.name, + insertText: entry.name, + sortText: entry.sortText, + kind: SuggestAdapter.convertKind(entry.kind) + }; + }); + + return { + suggestions + }; + } + + public async resolveCompletionItem( + model: monaco.editor.ITextModel, + _position: Position, + item: monaco.languages.CompletionItem, + token: CancellationToken + ): Promise { + const myItem = item; + const resource = myItem.uri; + const position = myItem.position; + const offset = model.getOffsetAt(position); + + const worker = await this._worker(resource); + const details = await worker.getCompletionEntryDetails( + resource.toString(), + offset, + myItem.label + ); + if (!details || model.isDisposed()) { + return myItem; + } + return { + uri: resource, + position: position, + label: details.name, + kind: SuggestAdapter.convertKind(details.kind), + detail: displayPartsToString(details.displayParts), + documentation: { + value: displayPartsToString(details.documentation) + } + }; + } + + private static convertKind( + kind: string + ): monaco.languages.CompletionItemKind { + switch (kind) { + case Kind.primitiveType: + case Kind.keyword: + return monaco.languages.CompletionItemKind.Keyword; + case Kind.variable: + case Kind.localVariable: + return monaco.languages.CompletionItemKind.Variable; + case Kind.memberVariable: + case Kind.memberGetAccessor: + case Kind.memberSetAccessor: + return monaco.languages.CompletionItemKind.Field; + case Kind.function: + case Kind.memberFunction: + case Kind.constructSignature: + case Kind.callSignature: + case Kind.indexSignature: + return monaco.languages.CompletionItemKind.Function; + case Kind.enum: + return monaco.languages.CompletionItemKind.Enum; + case Kind.module: + return monaco.languages.CompletionItemKind.Module; + case Kind.class: + return monaco.languages.CompletionItemKind.Class; + case Kind.interface: + return monaco.languages.CompletionItemKind.Interface; + case Kind.warning: + return monaco.languages.CompletionItemKind.File; + } + + return monaco.languages.CompletionItemKind.Property; + } +} + +export class SignatureHelpAdapter + extends Adapter + implements monaco.languages.SignatureHelpProvider { + public signatureHelpTriggerCharacters = ['(', ',']; + + public async provideSignatureHelp( + model: monaco.editor.ITextModel, + position: Position, + token: CancellationToken + ): Promise { + const resource = model.uri; + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + const info = await worker.getSignatureHelpItems( + resource.toString(), + offset + ); + + if (!info || model.isDisposed()) { + return; + } + + const ret: monaco.languages.SignatureHelp = { + activeSignature: info.selectedItemIndex, + activeParameter: info.argumentIndex, + signatures: [] + }; + + info.items.forEach((item) => { + const signature: monaco.languages.SignatureInformation = { + label: '', + parameters: [] + }; + + signature.documentation = { + value: displayPartsToString(item.documentation) + }; + signature.label += displayPartsToString(item.prefixDisplayParts); + item.parameters.forEach((p, i, a) => { + const label = displayPartsToString(p.displayParts); + const parameter: monaco.languages.ParameterInformation = { + label: label, + documentation: { + value: displayPartsToString(p.documentation) + } + }; + signature.label += label; + signature.parameters.push(parameter); + if (i < a.length - 1) { + signature.label += displayPartsToString(item.separatorDisplayParts); + } + }); + signature.label += displayPartsToString(item.suffixDisplayParts); + ret.signatures.push(signature); + }); + + return { + value: ret, + dispose() {} + }; + } +} + +// --- hover ------ + +export class QuickInfoAdapter + extends Adapter + implements monaco.languages.HoverProvider { + public async provideHover( + model: monaco.editor.ITextModel, + position: Position, + token: CancellationToken + ): Promise { + const resource = model.uri; + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + const info = await worker.getQuickInfoAtPosition( + resource.toString(), + offset + ); + + if (!info || model.isDisposed()) { + return; + } + + const documentation = displayPartsToString(info.documentation); + const tags = info.tags + ? info.tags + .map((tag) => { + const label = `*@${tag.name}*`; + if (!tag.text) { + return label; + } + return ( + label + + (tag.text.match(/\r\n|\n/g) ? ' \n' + tag.text : ` - ${tag.text}`) + ); + }) + .join(' \n\n') + : ''; + const contents = displayPartsToString(info.displayParts); + return { + range: this._textSpanToRange(model, info.textSpan), + contents: [ + { + value: '```js\n' + contents + '\n```\n' + }, + { + value: documentation + (tags ? '\n\n' + tags : '') + } + ] + }; + } +} + +// --- occurrences ------ + +export class OccurrencesAdapter + extends Adapter + implements monaco.languages.DocumentHighlightProvider { + public async provideDocumentHighlights( + model: monaco.editor.ITextModel, + position: Position, + token: CancellationToken + ): Promise { + const resource = model.uri; + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + const entries = await worker.getOccurrencesAtPosition( + resource.toString(), + offset + ); + + if (!entries || model.isDisposed()) { + return; + } + + return entries.map((entry) => { + return { + range: this._textSpanToRange(model, entry.textSpan), + kind: entry.isWriteAccess + ? monaco.languages.DocumentHighlightKind.Write + : monaco.languages.DocumentHighlightKind.Text + }; + }); + } +} + +// --- definition ------ + +export class DefinitionAdapter extends Adapter { + constructor( + private readonly _libFiles: LibFiles, + worker: (...uris: Uri[]) => Promise + ) { + super(worker); + } + + public async provideDefinition( + model: monaco.editor.ITextModel, + position: Position, + token: CancellationToken + ): Promise { + const resource = model.uri; + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + const entries = await worker.getDefinitionAtPosition( + resource.toString(), + offset + ); + + if (!entries || model.isDisposed()) { + return; + } + + // Fetch lib files if necessary + await this._libFiles.fetchLibFilesIfNecessary( + entries.map((entry) => Uri.parse(entry.fileName)) + ); + + if (model.isDisposed()) { + return; + } + + const result: monaco.languages.Location[] = []; + for (let entry of entries) { + const uri = Uri.parse(entry.fileName); + const refModel = this._libFiles.getOrCreateModel(uri); + if (refModel) { + result.push({ + uri: uri, + range: this._textSpanToRange(refModel, entry.textSpan) + }); + } + } + return result; + } +} + +// --- references ------ + +export class ReferenceAdapter + extends Adapter + implements monaco.languages.ReferenceProvider { + constructor( + private readonly _libFiles: LibFiles, + worker: (...uris: Uri[]) => Promise + ) { + super(worker); + } + + public async provideReferences( + model: monaco.editor.ITextModel, + position: Position, + context: monaco.languages.ReferenceContext, + token: CancellationToken + ): Promise { + const resource = model.uri; + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + const entries = await worker.getReferencesAtPosition( + resource.toString(), + offset + ); + + if (!entries || model.isDisposed()) { + return; + } + + // Fetch lib files if necessary + await this._libFiles.fetchLibFilesIfNecessary( + entries.map((entry) => Uri.parse(entry.fileName)) + ); + + if (model.isDisposed()) { + return; + } + + const result: monaco.languages.Location[] = []; + for (let entry of entries) { + const uri = Uri.parse(entry.fileName); + const refModel = this._libFiles.getOrCreateModel(uri); + if (refModel) { + result.push({ + uri: uri, + range: this._textSpanToRange(refModel, entry.textSpan) + }); + } + } + return result; + } +} + +// --- outline ------ + +export class OutlineAdapter + extends Adapter + implements monaco.languages.DocumentSymbolProvider { + public async provideDocumentSymbols( + model: monaco.editor.ITextModel, + token: CancellationToken + ): Promise { + const resource = model.uri; + const worker = await this._worker(resource); + const items = await worker.getNavigationBarItems(resource.toString()); + + if (!items || model.isDisposed()) { + return; + } + + const convert = ( + bucket: monaco.languages.DocumentSymbol[], + item: ts.NavigationBarItem, + containerLabel?: string + ): void => { + let result: monaco.languages.DocumentSymbol = { + name: item.text, + detail: '', + kind: ( + (outlineTypeTable[item.kind] || monaco.languages.SymbolKind.Variable) + ), + range: this._textSpanToRange(model, item.spans[0]), + selectionRange: this._textSpanToRange(model, item.spans[0]), + tags: [], + containerName: containerLabel + }; + + if (item.childItems && item.childItems.length > 0) { + for (let child of item.childItems) { + convert(bucket, child, result.name); + } + } + + bucket.push(result); + }; + + let result: monaco.languages.DocumentSymbol[] = []; + items.forEach((item) => convert(result, item)); + return result; + } +} + +export class Kind { + public static unknown: string = ''; + public static keyword: string = 'keyword'; + public static script: string = 'script'; + public static module: string = 'module'; + public static class: string = 'class'; + public static interface: string = 'interface'; + public static type: string = 'type'; + public static enum: string = 'enum'; + public static variable: string = 'var'; + public static localVariable: string = 'local var'; + public static function: string = 'function'; + public static localFunction: string = 'local function'; + public static memberFunction: string = 'method'; + public static memberGetAccessor: string = 'getter'; + public static memberSetAccessor: string = 'setter'; + public static memberVariable: string = 'property'; + public static constructorImplementation: string = 'constructor'; + public static callSignature: string = 'call'; + public static indexSignature: string = 'index'; + public static constructSignature: string = 'construct'; + public static parameter: string = 'parameter'; + public static typeParameter: string = 'type parameter'; + public static primitiveType: string = 'primitive type'; + public static label: string = 'label'; + public static alias: string = 'alias'; + public static const: string = 'const'; + public static let: string = 'let'; + public static warning: string = 'warning'; +} + +let outlineTypeTable: { + [kind: string]: monaco.languages.SymbolKind; +} = Object.create(null); +outlineTypeTable[Kind.module] = monaco.languages.SymbolKind.Module; +outlineTypeTable[Kind.class] = monaco.languages.SymbolKind.Class; +outlineTypeTable[Kind.enum] = monaco.languages.SymbolKind.Enum; +outlineTypeTable[Kind.interface] = monaco.languages.SymbolKind.Interface; +outlineTypeTable[Kind.memberFunction] = monaco.languages.SymbolKind.Method; +outlineTypeTable[Kind.memberVariable] = monaco.languages.SymbolKind.Property; +outlineTypeTable[Kind.memberGetAccessor] = monaco.languages.SymbolKind.Property; +outlineTypeTable[Kind.memberSetAccessor] = monaco.languages.SymbolKind.Property; +outlineTypeTable[Kind.variable] = monaco.languages.SymbolKind.Variable; +outlineTypeTable[Kind.const] = monaco.languages.SymbolKind.Variable; +outlineTypeTable[Kind.localVariable] = monaco.languages.SymbolKind.Variable; +outlineTypeTable[Kind.variable] = monaco.languages.SymbolKind.Variable; +outlineTypeTable[Kind.function] = monaco.languages.SymbolKind.Function; +outlineTypeTable[Kind.localFunction] = monaco.languages.SymbolKind.Function; + +// --- formatting ---- + +export abstract class FormatHelper extends Adapter { + protected static _convertOptions( + options: monaco.languages.FormattingOptions + ): ts.FormatCodeOptions { + return { + ConvertTabsToSpaces: options.insertSpaces, + TabSize: options.tabSize, + IndentSize: options.tabSize, + IndentStyle: IndentStyle.Smart, + NewLineCharacter: '\n', + InsertSpaceAfterCommaDelimiter: true, + InsertSpaceAfterSemicolonInForStatements: true, + InsertSpaceBeforeAndAfterBinaryOperators: true, + InsertSpaceAfterKeywordsInControlFlowStatements: true, + InsertSpaceAfterFunctionKeywordForAnonymousFunctions: true, + InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + PlaceOpenBraceOnNewLineForControlBlocks: false, + PlaceOpenBraceOnNewLineForFunctions: false + }; + } + + protected _convertTextChanges( + model: monaco.editor.ITextModel, + change: ts.TextChange + ): monaco.languages.TextEdit { + return { + text: change.newText, + range: this._textSpanToRange(model, change.span) + }; + } +} + +export class FormatAdapter + extends FormatHelper + implements monaco.languages.DocumentRangeFormattingEditProvider { + public async provideDocumentRangeFormattingEdits( + model: monaco.editor.ITextModel, + range: Range, + options: monaco.languages.FormattingOptions, + token: CancellationToken + ): Promise { + const resource = model.uri; + const startOffset = model.getOffsetAt({ + lineNumber: range.startLineNumber, + column: range.startColumn + }); + const endOffset = model.getOffsetAt({ + lineNumber: range.endLineNumber, + column: range.endColumn + }); + const worker = await this._worker(resource); + const edits = await worker.getFormattingEditsForRange( + resource.toString(), + startOffset, + endOffset, + FormatHelper._convertOptions(options) + ); + + if (!edits || model.isDisposed()) { + return; + } + + return edits.map((edit) => this._convertTextChanges(model, edit)); + } +} + +export class FormatOnTypeAdapter + extends FormatHelper + implements monaco.languages.OnTypeFormattingEditProvider { + get autoFormatTriggerCharacters() { + return [';', '}', '\n']; + } + + public async provideOnTypeFormattingEdits( + model: monaco.editor.ITextModel, + position: Position, + ch: string, + options: monaco.languages.FormattingOptions, + token: CancellationToken + ): Promise { + const resource = model.uri; + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + const edits = await worker.getFormattingEditsAfterKeystroke( + resource.toString(), + offset, + ch, + FormatHelper._convertOptions(options) + ); + + if (!edits || model.isDisposed()) { + return; + } + + return edits.map((edit) => this._convertTextChanges(model, edit)); + } +} + +// --- code actions ------ + +export class CodeActionAdaptor + extends FormatHelper + implements monaco.languages.CodeActionProvider { + public async provideCodeActions( + model: monaco.editor.ITextModel, + range: Range, + context: monaco.languages.CodeActionContext, + token: CancellationToken + ): Promise { + const resource = model.uri; + const start = model.getOffsetAt({ + lineNumber: range.startLineNumber, + column: range.startColumn + }); + const end = model.getOffsetAt({ + lineNumber: range.endLineNumber, + column: range.endColumn + }); + const formatOptions = FormatHelper._convertOptions(model.getOptions()); + const errorCodes = context.markers + .filter((m) => m.code) + .map((m) => m.code) + .map(Number); + const worker = await this._worker(resource); + const codeFixes = await worker.getCodeFixesAtPosition( + resource.toString(), + start, + end, + errorCodes, + formatOptions + ); + + if (!codeFixes || model.isDisposed()) { + return { actions: [], dispose: () => {} }; + } + + const actions = codeFixes + .filter((fix) => { + // Removes any 'make a new file'-type code fix + return fix.changes.filter((change) => change.isNewFile).length === 0; + }) + .map((fix) => { + return this._tsCodeFixActionToMonacoCodeAction(model, context, fix); + }); + + return { + actions: actions, + dispose: () => {} + }; + } + + private _tsCodeFixActionToMonacoCodeAction( + model: monaco.editor.ITextModel, + context: monaco.languages.CodeActionContext, + codeFix: ts.CodeFixAction + ): monaco.languages.CodeAction { + const edits: monaco.languages.WorkspaceTextEdit[] = []; + for (const change of codeFix.changes) { + for (const textChange of change.textChanges) { + edits.push({ + resource: model.uri, + edit: { + range: this._textSpanToRange(model, textChange.span), + text: textChange.newText + } + }); + } + } + + const action: monaco.languages.CodeAction = { + title: codeFix.description, + edit: { edits: edits }, + diagnostics: context.markers, + kind: 'quickfix' + }; + + return action; + } +} +// --- rename ---- + +export class RenameAdapter + extends Adapter + implements monaco.languages.RenameProvider { + public async provideRenameEdits( + model: monaco.editor.ITextModel, + position: Position, + newName: string, + token: CancellationToken + ): Promise< + (monaco.languages.WorkspaceEdit & monaco.languages.Rejection) | undefined + > { + const resource = model.uri; + const fileName = resource.toString(); + const offset = model.getOffsetAt(position); + const worker = await this._worker(resource); + + const renameInfo = await worker.getRenameInfo(fileName, offset, { + allowRenameOfImportPath: false + }); + if (renameInfo.canRename === false) { + // use explicit comparison so that the discriminated union gets resolved properly + return { + edits: [], + rejectReason: renameInfo.localizedErrorMessage + }; + } + if (renameInfo.fileToRename !== undefined) { + throw new Error('Renaming files is not supported.'); + } + + const renameLocations = await worker.findRenameLocations( + fileName, + offset, + /*strings*/ false, + /*comments*/ false, + /*prefixAndSuffix*/ false + ); + + if (!renameLocations || model.isDisposed()) { + return; + } + + const edits: monaco.languages.WorkspaceTextEdit[] = []; + for (const renameLocation of renameLocations) { + edits.push({ + resource: monaco.Uri.parse(renameLocation.fileName), + edit: { + range: this._textSpanToRange(model, renameLocation.textSpan), + text: newName + } + }); + } + + return { edits }; + } +} diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index a26b7e2a..c1a72402 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -1,260 +1,282 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as mode from './tsMode'; -import { typescriptVersion } from './lib/typescriptServicesMetadata'; // do not import the whole typescriptServices here - -import Emitter = monaco.Emitter; -import IEvent = monaco.IEvent; -import IDisposable = monaco.IDisposable; - -// --- TypeScript configuration and defaults --------- - -export interface IExtraLib { - content: string; - version: number; -} - -export interface IExtraLibs { - [path: string]: IExtraLib; -} - -export class LanguageServiceDefaultsImpl implements monaco.languages.typescript.LanguageServiceDefaults { - - private _onDidChange = new Emitter(); - private _onDidExtraLibsChange = new Emitter(); - - private _extraLibs: IExtraLibs; - private _eagerModelSync: boolean; - private _compilerOptions!: monaco.languages.typescript.CompilerOptions; - private _diagnosticsOptions!: monaco.languages.typescript.DiagnosticsOptions; - private _workerOptions!: monaco.languages.typescript.WorkerOptions; - private _onDidExtraLibsChangeTimeout: number; - - constructor(compilerOptions: monaco.languages.typescript.CompilerOptions, diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions, workerOptions: monaco.languages.typescript.WorkerOptions) { - this._extraLibs = Object.create(null); - this._eagerModelSync = false; - this.setCompilerOptions(compilerOptions); - this.setDiagnosticsOptions(diagnosticsOptions); - this.setWorkerOptions(workerOptions) - this._onDidExtraLibsChangeTimeout = -1; - } - - get onDidChange(): IEvent { - return this._onDidChange.event; - } - - get onDidExtraLibsChange(): IEvent { - return this._onDidExtraLibsChange.event; - } - - get workerOptions(): monaco.languages.typescript.WorkerOptions { - return this._workerOptions - } - - getExtraLibs(): IExtraLibs { - return this._extraLibs; - } - - addExtraLib(content: string, _filePath?: string): IDisposable { - let filePath: string; - if (typeof _filePath === 'undefined') { - filePath = `ts:extralib-${Math.random().toString(36).substring(2, 15)}`; - } else { - filePath = _filePath; - } - - if (this._extraLibs[filePath] && this._extraLibs[filePath].content === content) { - // no-op, there already exists an extra lib with this content - return { - dispose: () => { } - }; - } - - let myVersion = 1; - if (this._extraLibs[filePath]) { - myVersion = this._extraLibs[filePath].version + 1; - } - - this._extraLibs[filePath] = { - content: content, - version: myVersion, - }; - this._fireOnDidExtraLibsChangeSoon(); - - return { - dispose: () => { - let extraLib = this._extraLibs[filePath]; - if (!extraLib) { - return; - } - if (extraLib.version !== myVersion) { - return; - } - - delete this._extraLibs[filePath]; - this._fireOnDidExtraLibsChangeSoon(); - } - }; - } - - setExtraLibs(libs: { content: string; filePath?: string }[]): void { - // clear out everything - this._extraLibs = Object.create(null); - - if (libs && libs.length > 0) { - for (const lib of libs) { - const filePath = lib.filePath || `ts:extralib-${Math.random().toString(36).substring(2, 15)}`; - const content = lib.content; - this._extraLibs[filePath] = { - content: content, - version: 1 - }; - } - } - - this._fireOnDidExtraLibsChangeSoon(); - } - - private _fireOnDidExtraLibsChangeSoon(): void { - if (this._onDidExtraLibsChangeTimeout !== -1) { - // already scheduled - return; - } - this._onDidExtraLibsChangeTimeout = setTimeout(() => { - this._onDidExtraLibsChangeTimeout = -1; - this._onDidExtraLibsChange.fire(undefined); - }, 0); - } - - getCompilerOptions(): monaco.languages.typescript.CompilerOptions { - return this._compilerOptions; - } - - setCompilerOptions(options: monaco.languages.typescript.CompilerOptions): void { - this._compilerOptions = options || Object.create(null); - this._onDidChange.fire(undefined); - } - - getDiagnosticsOptions(): monaco.languages.typescript.DiagnosticsOptions { - return this._diagnosticsOptions; - } - - setDiagnosticsOptions(options: monaco.languages.typescript.DiagnosticsOptions): void { - this._diagnosticsOptions = options || Object.create(null); - this._onDidChange.fire(undefined); - } - - setWorkerOptions(options: monaco.languages.typescript.WorkerOptions): void { - this._workerOptions = options || Object.create(null); - this._onDidChange.fire(undefined); - } - - setMaximumWorkerIdleTime(value: number): void { - } - - setEagerModelSync(value: boolean) { - // doesn't fire an event since no - // worker restart is required here - this._eagerModelSync = value; - } - - getEagerModelSync() { - return this._eagerModelSync; - } -} - -//#region enums copied from typescript to prevent loading the entire typescriptServices --- - -enum ModuleKind { - None = 0, - CommonJS = 1, - AMD = 2, - UMD = 3, - System = 4, - ES2015 = 5, - ESNext = 99 -} - -enum JsxEmit { - None = 0, - Preserve = 1, - React = 2, - ReactNative = 3 -} - -enum NewLineKind { - CarriageReturnLineFeed = 0, - LineFeed = 1 -} - -enum ScriptTarget { - ES3 = 0, - ES5 = 1, - ES2015 = 2, - ES2016 = 3, - ES2017 = 4, - ES2018 = 5, - ES2019 = 6, - ES2020 = 7, - ESNext = 99, - JSON = 100, - Latest = ESNext, -} - -enum ModuleResolutionKind { - Classic = 1, - NodeJs = 2 -} -//#endregion - -const typescriptDefaults = new LanguageServiceDefaultsImpl( - { allowNonTsExtensions: true, target: ScriptTarget.Latest }, - { noSemanticValidation: false, noSyntaxValidation: false }, - {}); - -const javascriptDefaults = new LanguageServiceDefaultsImpl( - { allowNonTsExtensions: true, allowJs: true, target: ScriptTarget.Latest }, - { noSemanticValidation: true, noSyntaxValidation: false }, - {}); - -function getTypeScriptWorker(): Promise<(...uris: monaco.Uri[]) => Promise> { - return getMode().then(mode => mode.getTypeScriptWorker()); -} - -function getJavaScriptWorker(): Promise<(...uris: monaco.Uri[]) => Promise> { - return getMode().then(mode => mode.getJavaScriptWorker()); -} - -// Export API -function createAPI(): typeof monaco.languages.typescript { - return { - ModuleKind: ModuleKind, - JsxEmit: JsxEmit, - NewLineKind: NewLineKind, - ScriptTarget: ScriptTarget, - ModuleResolutionKind: ModuleResolutionKind, - typescriptVersion, - typescriptDefaults: typescriptDefaults, - javascriptDefaults: javascriptDefaults, - getTypeScriptWorker: getTypeScriptWorker, - getJavaScriptWorker: getJavaScriptWorker - } -} -monaco.languages.typescript = createAPI(); - -// --- Registration to monaco editor --- - -function getMode(): Promise { - return import('./tsMode'); -} - -monaco.languages.onLanguage('typescript', () => { - return getMode().then(mode => mode.setupTypeScript(typescriptDefaults)); -}); -monaco.languages.onLanguage('javascript', () => { - return getMode().then(mode => mode.setupJavaScript(javascriptDefaults)); -}); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as mode from './tsMode'; +import { typescriptVersion } from './lib/typescriptServicesMetadata'; // do not import the whole typescriptServices here + +import Emitter = monaco.Emitter; +import IEvent = monaco.IEvent; +import IDisposable = monaco.IDisposable; + +// --- TypeScript configuration and defaults --------- + +export interface IExtraLib { + content: string; + version: number; +} + +export interface IExtraLibs { + [path: string]: IExtraLib; +} + +export class LanguageServiceDefaultsImpl + implements monaco.languages.typescript.LanguageServiceDefaults { + private _onDidChange = new Emitter(); + private _onDidExtraLibsChange = new Emitter(); + + private _extraLibs: IExtraLibs; + private _eagerModelSync: boolean; + private _compilerOptions!: monaco.languages.typescript.CompilerOptions; + private _diagnosticsOptions!: monaco.languages.typescript.DiagnosticsOptions; + private _workerOptions!: monaco.languages.typescript.WorkerOptions; + private _onDidExtraLibsChangeTimeout: number; + + constructor( + compilerOptions: monaco.languages.typescript.CompilerOptions, + diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions, + workerOptions: monaco.languages.typescript.WorkerOptions + ) { + this._extraLibs = Object.create(null); + this._eagerModelSync = false; + this.setCompilerOptions(compilerOptions); + this.setDiagnosticsOptions(diagnosticsOptions); + this.setWorkerOptions(workerOptions); + this._onDidExtraLibsChangeTimeout = -1; + } + + get onDidChange(): IEvent { + return this._onDidChange.event; + } + + get onDidExtraLibsChange(): IEvent { + return this._onDidExtraLibsChange.event; + } + + get workerOptions(): monaco.languages.typescript.WorkerOptions { + return this._workerOptions; + } + + getExtraLibs(): IExtraLibs { + return this._extraLibs; + } + + addExtraLib(content: string, _filePath?: string): IDisposable { + let filePath: string; + if (typeof _filePath === 'undefined') { + filePath = `ts:extralib-${Math.random().toString(36).substring(2, 15)}`; + } else { + filePath = _filePath; + } + + if ( + this._extraLibs[filePath] && + this._extraLibs[filePath].content === content + ) { + // no-op, there already exists an extra lib with this content + return { + dispose: () => {} + }; + } + + let myVersion = 1; + if (this._extraLibs[filePath]) { + myVersion = this._extraLibs[filePath].version + 1; + } + + this._extraLibs[filePath] = { + content: content, + version: myVersion + }; + this._fireOnDidExtraLibsChangeSoon(); + + return { + dispose: () => { + let extraLib = this._extraLibs[filePath]; + if (!extraLib) { + return; + } + if (extraLib.version !== myVersion) { + return; + } + + delete this._extraLibs[filePath]; + this._fireOnDidExtraLibsChangeSoon(); + } + }; + } + + setExtraLibs(libs: { content: string; filePath?: string }[]): void { + // clear out everything + this._extraLibs = Object.create(null); + + if (libs && libs.length > 0) { + for (const lib of libs) { + const filePath = + lib.filePath || + `ts:extralib-${Math.random().toString(36).substring(2, 15)}`; + const content = lib.content; + this._extraLibs[filePath] = { + content: content, + version: 1 + }; + } + } + + this._fireOnDidExtraLibsChangeSoon(); + } + + private _fireOnDidExtraLibsChangeSoon(): void { + if (this._onDidExtraLibsChangeTimeout !== -1) { + // already scheduled + return; + } + this._onDidExtraLibsChangeTimeout = setTimeout(() => { + this._onDidExtraLibsChangeTimeout = -1; + this._onDidExtraLibsChange.fire(undefined); + }, 0); + } + + getCompilerOptions(): monaco.languages.typescript.CompilerOptions { + return this._compilerOptions; + } + + setCompilerOptions( + options: monaco.languages.typescript.CompilerOptions + ): void { + this._compilerOptions = options || Object.create(null); + this._onDidChange.fire(undefined); + } + + getDiagnosticsOptions(): monaco.languages.typescript.DiagnosticsOptions { + return this._diagnosticsOptions; + } + + setDiagnosticsOptions( + options: monaco.languages.typescript.DiagnosticsOptions + ): void { + this._diagnosticsOptions = options || Object.create(null); + this._onDidChange.fire(undefined); + } + + setWorkerOptions(options: monaco.languages.typescript.WorkerOptions): void { + this._workerOptions = options || Object.create(null); + this._onDidChange.fire(undefined); + } + + setMaximumWorkerIdleTime(value: number): void {} + + setEagerModelSync(value: boolean) { + // doesn't fire an event since no + // worker restart is required here + this._eagerModelSync = value; + } + + getEagerModelSync() { + return this._eagerModelSync; + } +} + +//#region enums copied from typescript to prevent loading the entire typescriptServices --- + +enum ModuleKind { + None = 0, + CommonJS = 1, + AMD = 2, + UMD = 3, + System = 4, + ES2015 = 5, + ESNext = 99 +} + +enum JsxEmit { + None = 0, + Preserve = 1, + React = 2, + ReactNative = 3 +} + +enum NewLineKind { + CarriageReturnLineFeed = 0, + LineFeed = 1 +} + +enum ScriptTarget { + ES3 = 0, + ES5 = 1, + ES2015 = 2, + ES2016 = 3, + ES2017 = 4, + ES2018 = 5, + ES2019 = 6, + ES2020 = 7, + ESNext = 99, + JSON = 100, + Latest = ESNext +} + +enum ModuleResolutionKind { + Classic = 1, + NodeJs = 2 +} +//#endregion + +const typescriptDefaults = new LanguageServiceDefaultsImpl( + { allowNonTsExtensions: true, target: ScriptTarget.Latest }, + { noSemanticValidation: false, noSyntaxValidation: false }, + {} +); + +const javascriptDefaults = new LanguageServiceDefaultsImpl( + { allowNonTsExtensions: true, allowJs: true, target: ScriptTarget.Latest }, + { noSemanticValidation: true, noSyntaxValidation: false }, + {} +); + +function getTypeScriptWorker(): Promise< + ( + ...uris: monaco.Uri[] + ) => Promise +> { + return getMode().then((mode) => mode.getTypeScriptWorker()); +} + +function getJavaScriptWorker(): Promise< + ( + ...uris: monaco.Uri[] + ) => Promise +> { + return getMode().then((mode) => mode.getJavaScriptWorker()); +} + +// Export API +function createAPI(): typeof monaco.languages.typescript { + return { + ModuleKind: ModuleKind, + JsxEmit: JsxEmit, + NewLineKind: NewLineKind, + ScriptTarget: ScriptTarget, + ModuleResolutionKind: ModuleResolutionKind, + typescriptVersion, + typescriptDefaults: typescriptDefaults, + javascriptDefaults: javascriptDefaults, + getTypeScriptWorker: getTypeScriptWorker, + getJavaScriptWorker: getJavaScriptWorker + }; +} +monaco.languages.typescript = createAPI(); + +// --- Registration to monaco editor --- + +function getMode(): Promise { + return import('./tsMode'); +} + +monaco.languages.onLanguage('typescript', () => { + return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults)); +}); +monaco.languages.onLanguage('javascript', () => { + return getMode().then((mode) => mode.setupJavaScript(javascriptDefaults)); +}); diff --git a/src/monaco.d.ts b/src/monaco.d.ts index d63c60d6..2be40dce 100644 --- a/src/monaco.d.ts +++ b/src/monaco.d.ts @@ -1,397 +1,458 @@ - -declare module monaco.languages.typescript { - - enum ModuleKind { - None = 0, - CommonJS = 1, - AMD = 2, - UMD = 3, - System = 4, - ES2015 = 5, - ESNext = 99 - } - - enum JsxEmit { - None = 0, - Preserve = 1, - React = 2, - ReactNative = 3 - } - enum NewLineKind { - CarriageReturnLineFeed = 0, - LineFeed = 1 - } - - enum ScriptTarget { - ES3 = 0, - ES5 = 1, - ES2015 = 2, - ES2016 = 3, - ES2017 = 4, - ES2018 = 5, - ES2019 = 6, - ES2020 = 7, - ESNext = 99, - JSON = 100, - Latest = ESNext, - } - - export enum ModuleResolutionKind { - Classic = 1, - NodeJs = 2 - } - - interface MapLike { - [index: string]: T; - } - - type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike | null | undefined; - interface CompilerOptions { - allowJs?: boolean; - allowSyntheticDefaultImports?: boolean; - allowUmdGlobalAccess?: boolean; - allowUnreachableCode?: boolean; - allowUnusedLabels?: boolean; - alwaysStrict?: boolean; - baseUrl?: string; - charset?: string; - checkJs?: boolean; - declaration?: boolean; - declarationMap?: boolean; - emitDeclarationOnly?: boolean; - declarationDir?: string; - disableSizeLimit?: boolean; - disableSourceOfProjectReferenceRedirect?: boolean; - downlevelIteration?: boolean; - emitBOM?: boolean; - emitDecoratorMetadata?: boolean; - experimentalDecorators?: boolean; - forceConsistentCasingInFileNames?: boolean; - importHelpers?: boolean; - inlineSourceMap?: boolean; - inlineSources?: boolean; - isolatedModules?: boolean; - jsx?: JsxEmit; - keyofStringsOnly?: boolean; - lib?: string[]; - locale?: string; - mapRoot?: string; - maxNodeModuleJsDepth?: number; - module?: ModuleKind; - moduleResolution?: ModuleResolutionKind; - newLine?: NewLineKind; - noEmit?: boolean; - noEmitHelpers?: boolean; - noEmitOnError?: boolean; - noErrorTruncation?: boolean; - noFallthroughCasesInSwitch?: boolean; - noImplicitAny?: boolean; - noImplicitReturns?: boolean; - noImplicitThis?: boolean; - noStrictGenericChecks?: boolean; - noUnusedLocals?: boolean; - noUnusedParameters?: boolean; - noImplicitUseStrict?: boolean; - noLib?: boolean; - noResolve?: boolean; - out?: string; - outDir?: string; - outFile?: string; - paths?: MapLike; - preserveConstEnums?: boolean; - preserveSymlinks?: boolean; - project?: string; - reactNamespace?: string; - jsxFactory?: string; - composite?: boolean; - removeComments?: boolean; - rootDir?: string; - rootDirs?: string[]; - skipLibCheck?: boolean; - skipDefaultLibCheck?: boolean; - sourceMap?: boolean; - sourceRoot?: string; - strict?: boolean; - strictFunctionTypes?: boolean; - strictBindCallApply?: boolean; - strictNullChecks?: boolean; - strictPropertyInitialization?: boolean; - stripInternal?: boolean; - suppressExcessPropertyErrors?: boolean; - suppressImplicitAnyIndexErrors?: boolean; - target?: ScriptTarget; - traceResolution?: boolean; - resolveJsonModule?: boolean; - types?: string[]; - /** Paths used to compute primary types search locations */ - typeRoots?: string[]; - esModuleInterop?: boolean; - useDefineForClassFields?: boolean; - [option: string]: CompilerOptionsValue | undefined; - } - - export interface DiagnosticsOptions { - noSemanticValidation?: boolean; - noSyntaxValidation?: boolean; - noSuggestionDiagnostics?: boolean; - diagnosticCodesToIgnore?: number[]; - } - - export interface WorkerOptions { - /** A full HTTP path to a JavaScript file which adds a function `customTSWorkerFactory` to the self inside a web-worker */ - customWorkerPath?: string; - } - - interface IExtraLib { - content: string; - version: number; - } - - interface IExtraLibs { - [path: string]: IExtraLib; - } - - /** - * A linked list of formatted diagnostic messages to be used as part of a multiline message. - * It is built from the bottom up, leaving the head to be the "main" diagnostic. - */ - interface DiagnosticMessageChain { - messageText: string; - /** Diagnostic category: warning = 0, error = 1, suggestion = 2, message = 3 */ - category: 0 | 1 | 2 | 3; - code: number; - next?: DiagnosticMessageChain[]; - } - interface Diagnostic extends DiagnosticRelatedInformation { - /** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */ - reportsUnnecessary?: {}; - source?: string; - relatedInformation?: DiagnosticRelatedInformation[]; - } - interface DiagnosticRelatedInformation { - /** Diagnostic category: warning = 0, error = 1, suggestion = 2, message = 3 */ - category: 0 | 1 | 2 | 3; - code: number; - /** TypeScriptWorker removes this to avoid serializing circular JSON structures. */ - file: undefined; - start: number | undefined; - length: number | undefined; - messageText: string | DiagnosticMessageChain; - } - - interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - } - interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - } - - export interface LanguageServiceDefaults { - /** - * Event fired when compiler options or diagnostics options are changed. - */ - readonly onDidChange: IEvent; - - /** - * Event fired when extra libraries registered with the language service change. - */ - readonly onDidExtraLibsChange: IEvent; - - /** - * Get the current extra libs registered with the language service. - */ - getExtraLibs(): IExtraLibs; - - /** - * Add an additional source file to the language service. Use this - * for typescript (definition) files that won't be loaded as editor - * documents, like `jquery.d.ts`. - * - * @param content The file content - * @param filePath An optional file path - * @returns A disposable which will remove the file from the - * language service upon disposal. - */ - addExtraLib(content: string, filePath?: string): IDisposable; - - /** - * Remove all existing extra libs and set the additional source - * files to the language service. Use this for typescript definition - * files that won't be loaded as editor documents, like `jquery.d.ts`. - * @param libs An array of entries to register. - */ - setExtraLibs(libs: { content: string; filePath?: string }[]): void; - - /** - * Get current TypeScript compiler options for the language service. - */ - getCompilerOptions(): CompilerOptions; - - /** - * Set TypeScript compiler options. - */ - setCompilerOptions(options: CompilerOptions): void; - - /** - * Get the current diagnostics options for the language service. - */ - getDiagnosticsOptions(): DiagnosticsOptions; - - /** - * Configure whether syntactic and/or semantic validation should - * be performed - */ - setDiagnosticsOptions(options: DiagnosticsOptions): void; - - /** - * No-op. - */ - setMaximumWorkerIdleTime(value: number): void; - - /** - * Configure if all existing models should be eagerly sync'd - * to the worker on start or restart. - */ - setEagerModelSync(value: boolean): void; - - /** - * Get the current setting for whether all existing models should be eagerly sync'd - * to the worker on start or restart. - */ - getEagerModelSync(): boolean; - } - - export interface TypeScriptWorker { - /** - * Get diagnostic messages for any syntax issues in the given file. - */ - getSyntacticDiagnostics(fileName: string): Promise; - - /** - * Get diagnostic messages for any semantic issues in the given file. - */ - getSemanticDiagnostics(fileName: string): Promise; - - /** - * Get diagnostic messages for any suggestions related to the given file. - */ - getSuggestionDiagnostics(fileName: string): Promise; - - /** - * Get the content of a given file. - */ - getScriptText(fileName: string): Promise; - - /** - * Get diagnostic messages related to the current compiler options. - * @param fileName Not used - */ - getCompilerOptionsDiagnostics(fileName: string): Promise; - - /** - * Get code completions for the given file and position. - * @returns `Promise` - */ - getCompletionsAtPosition(fileName: string, position: number): Promise; - - /** - * Get code completion details for the given file, position, and entry. - * @returns `Promise` - */ - getCompletionEntryDetails(fileName: string, position: number, entry: string): Promise; - - /** - * Get signature help items for the item at the given file and position. - * @returns `Promise` - */ - getSignatureHelpItems(fileName: string, position: number): Promise; - - /** - * Get quick info for the item at the given position in the file. - * @returns `Promise` - */ - getQuickInfoAtPosition(fileName: string, position: number): Promise; - - /** - * Get other ranges which are related to the item at the given position in the file (often used for highlighting). - * @returns `Promise | undefined>` - */ - getOccurrencesAtPosition(fileName: string, position: number): Promise | undefined>; - - /** - * Get the definition of the item at the given position in the file. - * @returns `Promise | undefined>` - */ - getDefinitionAtPosition(fileName: string, position: number): Promise | undefined>; - - /** - * Get references to the item at the given position in the file. - * @returns `Promise` - */ - getReferencesAtPosition(fileName: string, position: number): Promise; - - /** - * Get outline entries for the item at the given position in the file. - * @returns `Promise` - */ - getNavigationBarItems(fileName: string): Promise; - - /** - * Get changes which should be applied to format the given file. - * @param options `typescript.FormatCodeOptions` - * @returns `Promise` - */ - getFormattingEditsForDocument(fileName: string, options: any): Promise; - - /** - * Get changes which should be applied to format the given range in the file. - * @param options `typescript.FormatCodeOptions` - * @returns `Promise` - */ - getFormattingEditsForRange(fileName: string, start: number, end: number, options: any): Promise; - - /** - * Get formatting changes which should be applied after the given keystroke. - * @param options `typescript.FormatCodeOptions` - * @returns `Promise` - */ - getFormattingEditsAfterKeystroke(fileName: string, postion: number, ch: string, options: any): Promise; - - /** - * Get other occurrences which should be updated when renaming the item at the given file and position. - * @returns `Promise` - */ - findRenameLocations(fileName: string, positon: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename: boolean): Promise; - - /** - * Get edits which should be applied to rename the item at the given file and position (or a failure reason). - * @param options `typescript.RenameInfoOptions` - * @returns `Promise` - */ - getRenameInfo(fileName: string, positon: number, options: any): Promise; - - /** - * Get transpiled output for the given file. - * @returns `typescript.EmitOutput` - */ - getEmitOutput(fileName: string): Promise; - - /** - * Get possible code fixes at the given position in the file. - * @param formatOptions `typescript.FormatCodeOptions` - * @returns `Promise>` - */ - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: any): Promise>; - } - - export const typescriptVersion: string; - - export const typescriptDefaults: LanguageServiceDefaults; - export const javascriptDefaults: LanguageServiceDefaults; - - export const getTypeScriptWorker: () => Promise<(...uris: Uri[]) => Promise>; - export const getJavaScriptWorker: () => Promise<(...uris: Uri[]) => Promise>; -} +declare module monaco.languages.typescript { + enum ModuleKind { + None = 0, + CommonJS = 1, + AMD = 2, + UMD = 3, + System = 4, + ES2015 = 5, + ESNext = 99 + } + + enum JsxEmit { + None = 0, + Preserve = 1, + React = 2, + ReactNative = 3 + } + enum NewLineKind { + CarriageReturnLineFeed = 0, + LineFeed = 1 + } + + enum ScriptTarget { + ES3 = 0, + ES5 = 1, + ES2015 = 2, + ES2016 = 3, + ES2017 = 4, + ES2018 = 5, + ES2019 = 6, + ES2020 = 7, + ESNext = 99, + JSON = 100, + Latest = ESNext + } + + export enum ModuleResolutionKind { + Classic = 1, + NodeJs = 2 + } + + interface MapLike { + [index: string]: T; + } + + type CompilerOptionsValue = + | string + | number + | boolean + | (string | number)[] + | string[] + | MapLike + | null + | undefined; + interface CompilerOptions { + allowJs?: boolean; + allowSyntheticDefaultImports?: boolean; + allowUmdGlobalAccess?: boolean; + allowUnreachableCode?: boolean; + allowUnusedLabels?: boolean; + alwaysStrict?: boolean; + baseUrl?: string; + charset?: string; + checkJs?: boolean; + declaration?: boolean; + declarationMap?: boolean; + emitDeclarationOnly?: boolean; + declarationDir?: string; + disableSizeLimit?: boolean; + disableSourceOfProjectReferenceRedirect?: boolean; + downlevelIteration?: boolean; + emitBOM?: boolean; + emitDecoratorMetadata?: boolean; + experimentalDecorators?: boolean; + forceConsistentCasingInFileNames?: boolean; + importHelpers?: boolean; + inlineSourceMap?: boolean; + inlineSources?: boolean; + isolatedModules?: boolean; + jsx?: JsxEmit; + keyofStringsOnly?: boolean; + lib?: string[]; + locale?: string; + mapRoot?: string; + maxNodeModuleJsDepth?: number; + module?: ModuleKind; + moduleResolution?: ModuleResolutionKind; + newLine?: NewLineKind; + noEmit?: boolean; + noEmitHelpers?: boolean; + noEmitOnError?: boolean; + noErrorTruncation?: boolean; + noFallthroughCasesInSwitch?: boolean; + noImplicitAny?: boolean; + noImplicitReturns?: boolean; + noImplicitThis?: boolean; + noStrictGenericChecks?: boolean; + noUnusedLocals?: boolean; + noUnusedParameters?: boolean; + noImplicitUseStrict?: boolean; + noLib?: boolean; + noResolve?: boolean; + out?: string; + outDir?: string; + outFile?: string; + paths?: MapLike; + preserveConstEnums?: boolean; + preserveSymlinks?: boolean; + project?: string; + reactNamespace?: string; + jsxFactory?: string; + composite?: boolean; + removeComments?: boolean; + rootDir?: string; + rootDirs?: string[]; + skipLibCheck?: boolean; + skipDefaultLibCheck?: boolean; + sourceMap?: boolean; + sourceRoot?: string; + strict?: boolean; + strictFunctionTypes?: boolean; + strictBindCallApply?: boolean; + strictNullChecks?: boolean; + strictPropertyInitialization?: boolean; + stripInternal?: boolean; + suppressExcessPropertyErrors?: boolean; + suppressImplicitAnyIndexErrors?: boolean; + target?: ScriptTarget; + traceResolution?: boolean; + resolveJsonModule?: boolean; + types?: string[]; + /** Paths used to compute primary types search locations */ + typeRoots?: string[]; + esModuleInterop?: boolean; + useDefineForClassFields?: boolean; + [option: string]: CompilerOptionsValue | undefined; + } + + export interface DiagnosticsOptions { + noSemanticValidation?: boolean; + noSyntaxValidation?: boolean; + noSuggestionDiagnostics?: boolean; + diagnosticCodesToIgnore?: number[]; + } + + export interface WorkerOptions { + /** A full HTTP path to a JavaScript file which adds a function `customTSWorkerFactory` to the self inside a web-worker */ + customWorkerPath?: string; + } + + interface IExtraLib { + content: string; + version: number; + } + + interface IExtraLibs { + [path: string]: IExtraLib; + } + + /** + * A linked list of formatted diagnostic messages to be used as part of a multiline message. + * It is built from the bottom up, leaving the head to be the "main" diagnostic. + */ + interface DiagnosticMessageChain { + messageText: string; + /** Diagnostic category: warning = 0, error = 1, suggestion = 2, message = 3 */ + category: 0 | 1 | 2 | 3; + code: number; + next?: DiagnosticMessageChain[]; + } + interface Diagnostic extends DiagnosticRelatedInformation { + /** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */ + reportsUnnecessary?: {}; + source?: string; + relatedInformation?: DiagnosticRelatedInformation[]; + } + interface DiagnosticRelatedInformation { + /** Diagnostic category: warning = 0, error = 1, suggestion = 2, message = 3 */ + category: 0 | 1 | 2 | 3; + code: number; + /** TypeScriptWorker removes this to avoid serializing circular JSON structures. */ + file: undefined; + start: number | undefined; + length: number | undefined; + messageText: string | DiagnosticMessageChain; + } + + interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + } + interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + } + + export interface LanguageServiceDefaults { + /** + * Event fired when compiler options or diagnostics options are changed. + */ + readonly onDidChange: IEvent; + + /** + * Event fired when extra libraries registered with the language service change. + */ + readonly onDidExtraLibsChange: IEvent; + + /** + * Get the current extra libs registered with the language service. + */ + getExtraLibs(): IExtraLibs; + + /** + * Add an additional source file to the language service. Use this + * for typescript (definition) files that won't be loaded as editor + * documents, like `jquery.d.ts`. + * + * @param content The file content + * @param filePath An optional file path + * @returns A disposable which will remove the file from the + * language service upon disposal. + */ + addExtraLib(content: string, filePath?: string): IDisposable; + + /** + * Remove all existing extra libs and set the additional source + * files to the language service. Use this for typescript definition + * files that won't be loaded as editor documents, like `jquery.d.ts`. + * @param libs An array of entries to register. + */ + setExtraLibs(libs: { content: string; filePath?: string }[]): void; + + /** + * Get current TypeScript compiler options for the language service. + */ + getCompilerOptions(): CompilerOptions; + + /** + * Set TypeScript compiler options. + */ + setCompilerOptions(options: CompilerOptions): void; + + /** + * Get the current diagnostics options for the language service. + */ + getDiagnosticsOptions(): DiagnosticsOptions; + + /** + * Configure whether syntactic and/or semantic validation should + * be performed + */ + setDiagnosticsOptions(options: DiagnosticsOptions): void; + + /** + * No-op. + */ + setMaximumWorkerIdleTime(value: number): void; + + /** + * Configure if all existing models should be eagerly sync'd + * to the worker on start or restart. + */ + setEagerModelSync(value: boolean): void; + + /** + * Get the current setting for whether all existing models should be eagerly sync'd + * to the worker on start or restart. + */ + getEagerModelSync(): boolean; + } + + export interface TypeScriptWorker { + /** + * Get diagnostic messages for any syntax issues in the given file. + */ + getSyntacticDiagnostics(fileName: string): Promise; + + /** + * Get diagnostic messages for any semantic issues in the given file. + */ + getSemanticDiagnostics(fileName: string): Promise; + + /** + * Get diagnostic messages for any suggestions related to the given file. + */ + getSuggestionDiagnostics(fileName: string): Promise; + + /** + * Get the content of a given file. + */ + getScriptText(fileName: string): Promise; + + /** + * Get diagnostic messages related to the current compiler options. + * @param fileName Not used + */ + getCompilerOptionsDiagnostics(fileName: string): Promise; + + /** + * Get code completions for the given file and position. + * @returns `Promise` + */ + getCompletionsAtPosition( + fileName: string, + position: number + ): Promise; + + /** + * Get code completion details for the given file, position, and entry. + * @returns `Promise` + */ + getCompletionEntryDetails( + fileName: string, + position: number, + entry: string + ): Promise; + + /** + * Get signature help items for the item at the given file and position. + * @returns `Promise` + */ + getSignatureHelpItems( + fileName: string, + position: number + ): Promise; + + /** + * Get quick info for the item at the given position in the file. + * @returns `Promise` + */ + getQuickInfoAtPosition( + fileName: string, + position: number + ): Promise; + + /** + * Get other ranges which are related to the item at the given position in the file (often used for highlighting). + * @returns `Promise | undefined>` + */ + getOccurrencesAtPosition( + fileName: string, + position: number + ): Promise | undefined>; + + /** + * Get the definition of the item at the given position in the file. + * @returns `Promise | undefined>` + */ + getDefinitionAtPosition( + fileName: string, + position: number + ): Promise | undefined>; + + /** + * Get references to the item at the given position in the file. + * @returns `Promise` + */ + getReferencesAtPosition( + fileName: string, + position: number + ): Promise; + + /** + * Get outline entries for the item at the given position in the file. + * @returns `Promise` + */ + getNavigationBarItems(fileName: string): Promise; + + /** + * Get changes which should be applied to format the given file. + * @param options `typescript.FormatCodeOptions` + * @returns `Promise` + */ + getFormattingEditsForDocument( + fileName: string, + options: any + ): Promise; + + /** + * Get changes which should be applied to format the given range in the file. + * @param options `typescript.FormatCodeOptions` + * @returns `Promise` + */ + getFormattingEditsForRange( + fileName: string, + start: number, + end: number, + options: any + ): Promise; + + /** + * Get formatting changes which should be applied after the given keystroke. + * @param options `typescript.FormatCodeOptions` + * @returns `Promise` + */ + getFormattingEditsAfterKeystroke( + fileName: string, + postion: number, + ch: string, + options: any + ): Promise; + + /** + * Get other occurrences which should be updated when renaming the item at the given file and position. + * @returns `Promise` + */ + findRenameLocations( + fileName: string, + positon: number, + findInStrings: boolean, + findInComments: boolean, + providePrefixAndSuffixTextForRename: boolean + ): Promise; + + /** + * Get edits which should be applied to rename the item at the given file and position (or a failure reason). + * @param options `typescript.RenameInfoOptions` + * @returns `Promise` + */ + getRenameInfo( + fileName: string, + positon: number, + options: any + ): Promise; + + /** + * Get transpiled output for the given file. + * @returns `typescript.EmitOutput` + */ + getEmitOutput(fileName: string): Promise; + + /** + * Get possible code fixes at the given position in the file. + * @param formatOptions `typescript.FormatCodeOptions` + * @returns `Promise>` + */ + getCodeFixesAtPosition( + fileName: string, + start: number, + end: number, + errorCodes: number[], + formatOptions: any + ): Promise>; + } + + export const typescriptVersion: string; + + export const typescriptDefaults: LanguageServiceDefaults; + export const javascriptDefaults: LanguageServiceDefaults; + + export const getTypeScriptWorker: () => Promise< + (...uris: Uri[]) => Promise + >; + export const getJavaScriptWorker: () => Promise< + (...uris: Uri[]) => Promise + >; +} diff --git a/src/ts.worker.ts b/src/ts.worker.ts index 880b083f..ed202ec3 100644 --- a/src/ts.worker.ts +++ b/src/ts.worker.ts @@ -9,7 +9,9 @@ import { TypeScriptWorker, ICreateData } from './tsWorker'; self.onmessage = () => { // ignore the first message - worker.initialize((ctx: monaco.worker.IWorkerContext, createData: ICreateData) => { - return new TypeScriptWorker(ctx, createData) - }); + worker.initialize( + (ctx: monaco.worker.IWorkerContext, createData: ICreateData) => { + return new TypeScriptWorker(ctx, createData); + } + ); }; diff --git a/src/tsMode.ts b/src/tsMode.ts index aef2a6ea..6ea613a6 100644 --- a/src/tsMode.ts +++ b/src/tsMode.ts @@ -1,74 +1,107 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { WorkerManager } from './workerManager'; -import { TypeScriptWorker } from './tsWorker'; -import { LanguageServiceDefaultsImpl } from './monaco.contribution'; -import * as languageFeatures from './languageFeatures'; - -import Uri = monaco.Uri; - -let javaScriptWorker: (...uris: Uri[]) => Promise; -let typeScriptWorker: (...uris: Uri[]) => Promise; - -export function setupTypeScript(defaults: LanguageServiceDefaultsImpl): void { - typeScriptWorker = setupMode( - defaults, - 'typescript' - ); -} - -export function setupJavaScript(defaults: LanguageServiceDefaultsImpl): void { - javaScriptWorker = setupMode( - defaults, - 'javascript' - ); -} - -export function getJavaScriptWorker(): Promise<(...uris: Uri[]) => Promise> { - return new Promise((resolve, reject) => { - if (!javaScriptWorker) { - return reject("JavaScript not registered!"); - } - - resolve(javaScriptWorker); - }); -} - -export function getTypeScriptWorker(): Promise<(...uris: Uri[]) => Promise> { - return new Promise((resolve, reject) => { - if (!typeScriptWorker) { - return reject("TypeScript not registered!"); - } - - resolve(typeScriptWorker); - }); -} - -function setupMode(defaults: LanguageServiceDefaultsImpl, modeId: string): (...uris: Uri[]) => Promise { - - const client = new WorkerManager(modeId, defaults); - const worker = (...uris: Uri[]): Promise => { - return client.getLanguageServiceWorker(...uris); - }; - - const libFiles = new languageFeatures.LibFiles(worker); - - monaco.languages.registerCompletionItemProvider(modeId, new languageFeatures.SuggestAdapter(worker)); - monaco.languages.registerSignatureHelpProvider(modeId, new languageFeatures.SignatureHelpAdapter(worker)); - monaco.languages.registerHoverProvider(modeId, new languageFeatures.QuickInfoAdapter(worker)); - monaco.languages.registerDocumentHighlightProvider(modeId, new languageFeatures.OccurrencesAdapter(worker)); - monaco.languages.registerDefinitionProvider(modeId, new languageFeatures.DefinitionAdapter(libFiles, worker)); - monaco.languages.registerReferenceProvider(modeId, new languageFeatures.ReferenceAdapter(libFiles, worker)); - monaco.languages.registerDocumentSymbolProvider(modeId, new languageFeatures.OutlineAdapter(worker)); - monaco.languages.registerDocumentRangeFormattingEditProvider(modeId, new languageFeatures.FormatAdapter(worker)); - monaco.languages.registerOnTypeFormattingEditProvider(modeId, new languageFeatures.FormatOnTypeAdapter(worker)); - monaco.languages.registerCodeActionProvider(modeId, new languageFeatures.CodeActionAdaptor(worker)); - monaco.languages.registerRenameProvider(modeId, new languageFeatures.RenameAdapter(worker)); - new languageFeatures.DiagnosticsAdapter(libFiles, defaults, modeId, worker); - - return worker; -} +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { WorkerManager } from './workerManager'; +import { TypeScriptWorker } from './tsWorker'; +import { LanguageServiceDefaultsImpl } from './monaco.contribution'; +import * as languageFeatures from './languageFeatures'; + +import Uri = monaco.Uri; + +let javaScriptWorker: (...uris: Uri[]) => Promise; +let typeScriptWorker: (...uris: Uri[]) => Promise; + +export function setupTypeScript(defaults: LanguageServiceDefaultsImpl): void { + typeScriptWorker = setupMode(defaults, 'typescript'); +} + +export function setupJavaScript(defaults: LanguageServiceDefaultsImpl): void { + javaScriptWorker = setupMode(defaults, 'javascript'); +} + +export function getJavaScriptWorker(): Promise< + (...uris: Uri[]) => Promise +> { + return new Promise((resolve, reject) => { + if (!javaScriptWorker) { + return reject('JavaScript not registered!'); + } + + resolve(javaScriptWorker); + }); +} + +export function getTypeScriptWorker(): Promise< + (...uris: Uri[]) => Promise +> { + return new Promise((resolve, reject) => { + if (!typeScriptWorker) { + return reject('TypeScript not registered!'); + } + + resolve(typeScriptWorker); + }); +} + +function setupMode( + defaults: LanguageServiceDefaultsImpl, + modeId: string +): (...uris: Uri[]) => Promise { + const client = new WorkerManager(modeId, defaults); + const worker = (...uris: Uri[]): Promise => { + return client.getLanguageServiceWorker(...uris); + }; + + const libFiles = new languageFeatures.LibFiles(worker); + + monaco.languages.registerCompletionItemProvider( + modeId, + new languageFeatures.SuggestAdapter(worker) + ); + monaco.languages.registerSignatureHelpProvider( + modeId, + new languageFeatures.SignatureHelpAdapter(worker) + ); + monaco.languages.registerHoverProvider( + modeId, + new languageFeatures.QuickInfoAdapter(worker) + ); + monaco.languages.registerDocumentHighlightProvider( + modeId, + new languageFeatures.OccurrencesAdapter(worker) + ); + monaco.languages.registerDefinitionProvider( + modeId, + new languageFeatures.DefinitionAdapter(libFiles, worker) + ); + monaco.languages.registerReferenceProvider( + modeId, + new languageFeatures.ReferenceAdapter(libFiles, worker) + ); + monaco.languages.registerDocumentSymbolProvider( + modeId, + new languageFeatures.OutlineAdapter(worker) + ); + monaco.languages.registerDocumentRangeFormattingEditProvider( + modeId, + new languageFeatures.FormatAdapter(worker) + ); + monaco.languages.registerOnTypeFormattingEditProvider( + modeId, + new languageFeatures.FormatOnTypeAdapter(worker) + ); + monaco.languages.registerCodeActionProvider( + modeId, + new languageFeatures.CodeActionAdaptor(worker) + ); + monaco.languages.registerRenameProvider( + modeId, + new languageFeatures.RenameAdapter(worker) + ); + new languageFeatures.DiagnosticsAdapter(libFiles, defaults, modeId, worker); + + return worker; +} diff --git a/src/tsWorker.ts b/src/tsWorker.ts index aba62fbc..f4aacbe4 100644 --- a/src/tsWorker.ts +++ b/src/tsWorker.ts @@ -1,286 +1,432 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as ts from './lib/typescriptServices'; -import { libFileMap } from './lib/lib'; -import { IExtraLibs } from './monaco.contribution'; - -import IWorkerContext = monaco.worker.IWorkerContext; - - -export class TypeScriptWorker implements ts.LanguageServiceHost, monaco.languages.typescript.TypeScriptWorker { - - // --- model sync ----------------------- - - private _ctx: IWorkerContext; - private _extraLibs: IExtraLibs = Object.create(null); - private _languageService = ts.createLanguageService(this); - private _compilerOptions: ts.CompilerOptions; - - constructor(ctx: IWorkerContext, createData: ICreateData) { - this._ctx = ctx; - this._compilerOptions = createData.compilerOptions; - this._extraLibs = createData.extraLibs; - } - - // --- language service host --------------- - - getCompilationSettings(): ts.CompilerOptions { - return this._compilerOptions; - } - - getScriptFileNames(): string[] { - let models = this._ctx.getMirrorModels().map(model => model.uri.toString()); - return models.concat(Object.keys(this._extraLibs)); - } - - private _getModel(fileName: string): monaco.worker.IMirrorModel | null { - let models = this._ctx.getMirrorModels(); - for (let i = 0; i < models.length; i++) { - if (models[i].uri.toString() === fileName) { - return models[i]; - } - } - return null; - } - - getScriptVersion(fileName: string): string { - let model = this._getModel(fileName); - if (model) { - return model.version.toString(); - } else if (this.isDefaultLibFileName(fileName)) { - // default lib is static - return '1'; - } else if (fileName in this._extraLibs) { - return String(this._extraLibs[fileName].version); - } - return ''; - } - - getScriptText(fileName: string): Promise { - return Promise.resolve(this._getScriptText(fileName)); - } - - _getScriptText(fileName: string): string | undefined { - let text: string; - let model = this._getModel(fileName); - if (model) { - // a true editor model - text = model.getValue(); - } else if (fileName in libFileMap) { - text = libFileMap[fileName]; - - } else if (fileName in this._extraLibs) { - // extra lib - text = this._extraLibs[fileName].content; - } else { - return; - } - - return text; - } - - getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { - const text = this._getScriptText(fileName); - if (text === undefined) { - return; - } - - return { - getText: (start, end) => text.substring(start, end), - getLength: () => text.length, - getChangeRange: () => undefined - }; - } - - getScriptKind?(fileName: string): ts.ScriptKind { - const suffix = fileName.substr(fileName.lastIndexOf('.') + 1); - switch (suffix) { - case 'ts': return ts.ScriptKind.TS; - case 'tsx': return ts.ScriptKind.TSX; - case 'js': return ts.ScriptKind.JS; - case 'jsx': return ts.ScriptKind.JSX; - default: return this.getCompilationSettings().allowJs - ? ts.ScriptKind.JS - : ts.ScriptKind.TS; - } - } - - getCurrentDirectory(): string { - return ''; - } - - getDefaultLibFileName(options: ts.CompilerOptions): string { - switch (options.target) { - case 99 /* ESNext */: - const esnext = "lib.esnext.full.d.ts"; - if (esnext in libFileMap || esnext in this._extraLibs) return esnext - case 7 /* ES2020 */: - case 6 /* ES2019 */: - case 5 /* ES2018 */: - case 4 /* ES2017 */: - case 3 /* ES2016 */: - case 2 /* ES2015 */: - default: - // Support a dynamic lookup for the ES20XX version based on the target - // which is safe unless TC39 changes their numbering system - const eslib = `lib.es${2013 + (options.target || 99)}.full.d.ts`; - // Note: This also looks in _extraLibs, If you want - // to add support for additional target options, you will need to - // add the extra dts files to _extraLibs via the API. - if (eslib in libFileMap || eslib in this._extraLibs) { - return eslib; - } - - return "lib.es6.d.ts"; // We don't use lib.es2015.full.d.ts due to breaking change. - case 1: - case 0: - return "lib.d.ts"; - } - } - - isDefaultLibFileName(fileName: string): boolean { - return fileName === this.getDefaultLibFileName(this._compilerOptions); - } - - getLibFiles(): Promise> { - return Promise.resolve(libFileMap); - } - - // --- language features - - private static clearFiles(diagnostics: ts.Diagnostic[]): monaco.languages.typescript.Diagnostic[] { - // Clear the `file` field, which cannot be JSON'yfied because it - // contains cyclic data structures. - diagnostics.forEach(diag => { - diag.file = undefined; - const related = diag.relatedInformation; - if (related) { - related.forEach(diag2 => diag2.file = undefined); - } - }); - return diagnostics; - } - - getSyntacticDiagnostics(fileName: string): Promise { - const diagnostics = this._languageService.getSyntacticDiagnostics(fileName); - return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics)); - } - - getSemanticDiagnostics(fileName: string): Promise { - const diagnostics = this._languageService.getSemanticDiagnostics(fileName); - return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics)); - } - - getSuggestionDiagnostics(fileName: string): Promise { - const diagnostics = this._languageService.getSuggestionDiagnostics(fileName); - return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics)); - } - - getCompilerOptionsDiagnostics(fileName: string): Promise { - const diagnostics = this._languageService.getCompilerOptionsDiagnostics(); - return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics)); - } - - getCompletionsAtPosition(fileName: string, position: number): Promise { - return Promise.resolve(this._languageService.getCompletionsAtPosition(fileName, position, undefined)); - } - - getCompletionEntryDetails(fileName: string, position: number, entry: string): Promise { - return Promise.resolve(this._languageService.getCompletionEntryDetails(fileName, position, entry, undefined, undefined, undefined)); - } - - getSignatureHelpItems(fileName: string, position: number): Promise { - return Promise.resolve(this._languageService.getSignatureHelpItems(fileName, position, undefined)); - } - - getQuickInfoAtPosition(fileName: string, position: number): Promise { - return Promise.resolve(this._languageService.getQuickInfoAtPosition(fileName, position)); - } - - getOccurrencesAtPosition(fileName: string, position: number): Promise | undefined> { - return Promise.resolve(this._languageService.getOccurrencesAtPosition(fileName, position)); - } - - getDefinitionAtPosition(fileName: string, position: number): Promise | undefined> { - return Promise.resolve(this._languageService.getDefinitionAtPosition(fileName, position)); - } - - getReferencesAtPosition(fileName: string, position: number): Promise { - return Promise.resolve(this._languageService.getReferencesAtPosition(fileName, position)); - } - - getNavigationBarItems(fileName: string): Promise { - return Promise.resolve(this._languageService.getNavigationBarItems(fileName)); - } - - getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): Promise { - return Promise.resolve(this._languageService.getFormattingEditsForDocument(fileName, options)); - } - - getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): Promise { - return Promise.resolve(this._languageService.getFormattingEditsForRange(fileName, start, end, options)); - } - - getFormattingEditsAfterKeystroke(fileName: string, postion: number, ch: string, options: ts.FormatCodeOptions): Promise { - return Promise.resolve(this._languageService.getFormattingEditsAfterKeystroke(fileName, postion, ch, options)); - } - - findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename: boolean): Promise { - return Promise.resolve(this._languageService.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename)); - } - - getRenameInfo(fileName: string, position: number, options: ts.RenameInfoOptions): Promise { - return Promise.resolve(this._languageService.getRenameInfo(fileName, position, options)); - } - - getEmitOutput(fileName: string): Promise { - return Promise.resolve(this._languageService.getEmitOutput(fileName)); - } - - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: ts.FormatCodeOptions): Promise> { - const preferences = {} - return Promise.resolve(this._languageService.getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences)); - } - - updateExtraLibs(extraLibs: IExtraLibs) { - this._extraLibs = extraLibs; - } -} - -export interface ICreateData { - compilerOptions: ts.CompilerOptions; - extraLibs: IExtraLibs; - customWorkerPath?: string -} - -/** The shape of the factory */ -export interface CustomTSWebWorkerFactory { - (TSWorkerClass: typeof TypeScriptWorker, tsc: typeof ts, libs: Record): typeof TypeScriptWorker -} - -export function create(ctx: IWorkerContext, createData: ICreateData): TypeScriptWorker { - let TSWorkerClass = TypeScriptWorker - if (createData.customWorkerPath) { - // @ts-ignore - This is available in a webworker - if (typeof importScripts === "undefined") { - console.warn("Monaco is not using webworkers for background tasks, and that is needed to support the customWorkerPath flag") - } else { - // @ts-ignore - This is available in a webworker - importScripts(createData.customWorkerPath) - - // @ts-ignore - This should come from the above eval - const workerFactoryFunc: CustomTSWebWorkerFactory | undefined = self.customTSWorkerFactory - if (!workerFactoryFunc) { - throw new Error(`The script at ${createData.customWorkerPath} does not add customTSWorkerFactory to self`) - } - - TSWorkerClass = workerFactoryFunc(TypeScriptWorker, ts, libFileMap) - } - } - - return new TSWorkerClass(ctx, createData); -} +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as ts from './lib/typescriptServices'; +import { libFileMap } from './lib/lib'; +import { IExtraLibs } from './monaco.contribution'; + +import IWorkerContext = monaco.worker.IWorkerContext; + +export class TypeScriptWorker + implements + ts.LanguageServiceHost, + monaco.languages.typescript.TypeScriptWorker { + // --- model sync ----------------------- + + private _ctx: IWorkerContext; + private _extraLibs: IExtraLibs = Object.create(null); + private _languageService = ts.createLanguageService(this); + private _compilerOptions: ts.CompilerOptions; + + constructor(ctx: IWorkerContext, createData: ICreateData) { + this._ctx = ctx; + this._compilerOptions = createData.compilerOptions; + this._extraLibs = createData.extraLibs; + } + + // --- language service host --------------- + + getCompilationSettings(): ts.CompilerOptions { + return this._compilerOptions; + } + + getScriptFileNames(): string[] { + let models = this._ctx + .getMirrorModels() + .map((model) => model.uri.toString()); + return models.concat(Object.keys(this._extraLibs)); + } + + private _getModel(fileName: string): monaco.worker.IMirrorModel | null { + let models = this._ctx.getMirrorModels(); + for (let i = 0; i < models.length; i++) { + if (models[i].uri.toString() === fileName) { + return models[i]; + } + } + return null; + } + + getScriptVersion(fileName: string): string { + let model = this._getModel(fileName); + if (model) { + return model.version.toString(); + } else if (this.isDefaultLibFileName(fileName)) { + // default lib is static + return '1'; + } else if (fileName in this._extraLibs) { + return String(this._extraLibs[fileName].version); + } + return ''; + } + + getScriptText(fileName: string): Promise { + return Promise.resolve(this._getScriptText(fileName)); + } + + _getScriptText(fileName: string): string | undefined { + let text: string; + let model = this._getModel(fileName); + if (model) { + // a true editor model + text = model.getValue(); + } else if (fileName in libFileMap) { + text = libFileMap[fileName]; + } else if (fileName in this._extraLibs) { + // extra lib + text = this._extraLibs[fileName].content; + } else { + return; + } + + return text; + } + + getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { + const text = this._getScriptText(fileName); + if (text === undefined) { + return; + } + + return { + getText: (start, end) => text.substring(start, end), + getLength: () => text.length, + getChangeRange: () => undefined + }; + } + + getScriptKind?(fileName: string): ts.ScriptKind { + const suffix = fileName.substr(fileName.lastIndexOf('.') + 1); + switch (suffix) { + case 'ts': + return ts.ScriptKind.TS; + case 'tsx': + return ts.ScriptKind.TSX; + case 'js': + return ts.ScriptKind.JS; + case 'jsx': + return ts.ScriptKind.JSX; + default: + return this.getCompilationSettings().allowJs + ? ts.ScriptKind.JS + : ts.ScriptKind.TS; + } + } + + getCurrentDirectory(): string { + return ''; + } + + getDefaultLibFileName(options: ts.CompilerOptions): string { + switch (options.target) { + case 99 /* ESNext */: + const esnext = 'lib.esnext.full.d.ts'; + if (esnext in libFileMap || esnext in this._extraLibs) return esnext; + case 7 /* ES2020 */: + case 6 /* ES2019 */: + case 5 /* ES2018 */: + case 4 /* ES2017 */: + case 3 /* ES2016 */: + case 2 /* ES2015 */: + default: + // Support a dynamic lookup for the ES20XX version based on the target + // which is safe unless TC39 changes their numbering system + const eslib = `lib.es${2013 + (options.target || 99)}.full.d.ts`; + // Note: This also looks in _extraLibs, If you want + // to add support for additional target options, you will need to + // add the extra dts files to _extraLibs via the API. + if (eslib in libFileMap || eslib in this._extraLibs) { + return eslib; + } + + return 'lib.es6.d.ts'; // We don't use lib.es2015.full.d.ts due to breaking change. + case 1: + case 0: + return 'lib.d.ts'; + } + } + + isDefaultLibFileName(fileName: string): boolean { + return fileName === this.getDefaultLibFileName(this._compilerOptions); + } + + getLibFiles(): Promise> { + return Promise.resolve(libFileMap); + } + + // --- language features + + private static clearFiles( + diagnostics: ts.Diagnostic[] + ): monaco.languages.typescript.Diagnostic[] { + // Clear the `file` field, which cannot be JSON'yfied because it + // contains cyclic data structures. + diagnostics.forEach((diag) => { + diag.file = undefined; + const related = diag.relatedInformation; + if (related) { + related.forEach((diag2) => (diag2.file = undefined)); + } + }); + return diagnostics; + } + + getSyntacticDiagnostics( + fileName: string + ): Promise { + const diagnostics = this._languageService.getSyntacticDiagnostics(fileName); + return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics)); + } + + getSemanticDiagnostics( + fileName: string + ): Promise { + const diagnostics = this._languageService.getSemanticDiagnostics(fileName); + return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics)); + } + + getSuggestionDiagnostics( + fileName: string + ): Promise { + const diagnostics = this._languageService.getSuggestionDiagnostics( + fileName + ); + return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics)); + } + + getCompilerOptionsDiagnostics( + fileName: string + ): Promise { + const diagnostics = this._languageService.getCompilerOptionsDiagnostics(); + return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics)); + } + + getCompletionsAtPosition( + fileName: string, + position: number + ): Promise { + return Promise.resolve( + this._languageService.getCompletionsAtPosition( + fileName, + position, + undefined + ) + ); + } + + getCompletionEntryDetails( + fileName: string, + position: number, + entry: string + ): Promise { + return Promise.resolve( + this._languageService.getCompletionEntryDetails( + fileName, + position, + entry, + undefined, + undefined, + undefined + ) + ); + } + + getSignatureHelpItems( + fileName: string, + position: number + ): Promise { + return Promise.resolve( + this._languageService.getSignatureHelpItems(fileName, position, undefined) + ); + } + + getQuickInfoAtPosition( + fileName: string, + position: number + ): Promise { + return Promise.resolve( + this._languageService.getQuickInfoAtPosition(fileName, position) + ); + } + + getOccurrencesAtPosition( + fileName: string, + position: number + ): Promise | undefined> { + return Promise.resolve( + this._languageService.getOccurrencesAtPosition(fileName, position) + ); + } + + getDefinitionAtPosition( + fileName: string, + position: number + ): Promise | undefined> { + return Promise.resolve( + this._languageService.getDefinitionAtPosition(fileName, position) + ); + } + + getReferencesAtPosition( + fileName: string, + position: number + ): Promise { + return Promise.resolve( + this._languageService.getReferencesAtPosition(fileName, position) + ); + } + + getNavigationBarItems(fileName: string): Promise { + return Promise.resolve( + this._languageService.getNavigationBarItems(fileName) + ); + } + + getFormattingEditsForDocument( + fileName: string, + options: ts.FormatCodeOptions + ): Promise { + return Promise.resolve( + this._languageService.getFormattingEditsForDocument(fileName, options) + ); + } + + getFormattingEditsForRange( + fileName: string, + start: number, + end: number, + options: ts.FormatCodeOptions + ): Promise { + return Promise.resolve( + this._languageService.getFormattingEditsForRange( + fileName, + start, + end, + options + ) + ); + } + + getFormattingEditsAfterKeystroke( + fileName: string, + postion: number, + ch: string, + options: ts.FormatCodeOptions + ): Promise { + return Promise.resolve( + this._languageService.getFormattingEditsAfterKeystroke( + fileName, + postion, + ch, + options + ) + ); + } + + findRenameLocations( + fileName: string, + position: number, + findInStrings: boolean, + findInComments: boolean, + providePrefixAndSuffixTextForRename: boolean + ): Promise { + return Promise.resolve( + this._languageService.findRenameLocations( + fileName, + position, + findInStrings, + findInComments, + providePrefixAndSuffixTextForRename + ) + ); + } + + getRenameInfo( + fileName: string, + position: number, + options: ts.RenameInfoOptions + ): Promise { + return Promise.resolve( + this._languageService.getRenameInfo(fileName, position, options) + ); + } + + getEmitOutput(fileName: string): Promise { + return Promise.resolve(this._languageService.getEmitOutput(fileName)); + } + + getCodeFixesAtPosition( + fileName: string, + start: number, + end: number, + errorCodes: number[], + formatOptions: ts.FormatCodeOptions + ): Promise> { + const preferences = {}; + return Promise.resolve( + this._languageService.getCodeFixesAtPosition( + fileName, + start, + end, + errorCodes, + formatOptions, + preferences + ) + ); + } + + updateExtraLibs(extraLibs: IExtraLibs) { + this._extraLibs = extraLibs; + } +} + +export interface ICreateData { + compilerOptions: ts.CompilerOptions; + extraLibs: IExtraLibs; + customWorkerPath?: string; +} + +/** The shape of the factory */ +export interface CustomTSWebWorkerFactory { + ( + TSWorkerClass: typeof TypeScriptWorker, + tsc: typeof ts, + libs: Record + ): typeof TypeScriptWorker; +} + +declare global { + var importScripts: (path: string) => void | undefined; + var customTSWorkerFactory: CustomTSWebWorkerFactory | undefined; +} + +export function create( + ctx: IWorkerContext, + createData: ICreateData +): TypeScriptWorker { + let TSWorkerClass = TypeScriptWorker; + if (createData.customWorkerPath) { + if (typeof importScripts === 'undefined') { + console.warn( + 'Monaco is not using webworkers for background tasks, and that is needed to support the customWorkerPath flag' + ); + } else { + importScripts(createData.customWorkerPath); + + const workerFactoryFunc: CustomTSWebWorkerFactory | undefined = + self.customTSWorkerFactory; + if (!workerFactoryFunc) { + throw new Error( + `The script at ${createData.customWorkerPath} does not add customTSWorkerFactory to self` + ); + } + + TSWorkerClass = workerFactoryFunc(TypeScriptWorker, ts, libFileMap); + } + } + + return new TSWorkerClass(ctx, createData); +} diff --git a/src/tsconfig.esm.json b/src/tsconfig.esm.json index 03783034..aa682be5 100644 --- a/src/tsconfig.esm.json +++ b/src/tsconfig.esm.json @@ -1,22 +1,18 @@ { - "compilerOptions": { - "module": "esnext", - "moduleResolution": "node", - "outDir": "../release/esm", - "target": "es5", - "lib": [ - "dom", - "es5", - "es2015.collection", - "es2015.iterable", - "es2015.promise" - ], - "strict": true - }, - "include": [ - "**/*.ts" - ], - "files": [ - "../node_modules/monaco-editor-core/monaco.d.ts" - ] + "compilerOptions": { + "module": "esnext", + "moduleResolution": "node", + "outDir": "../release/esm", + "target": "es5", + "lib": [ + "dom", + "es5", + "es2015.collection", + "es2015.iterable", + "es2015.promise" + ], + "strict": true + }, + "include": ["**/*.ts"], + "files": ["../node_modules/monaco-editor-core/monaco.d.ts"] } diff --git a/src/tsconfig.json b/src/tsconfig.json index b2a6ca66..8160c130 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,22 +1,18 @@ { - "compilerOptions": { - "module": "amd", - "moduleResolution": "node", - "outDir": "../release/dev", - "target": "es5", - "lib": [ - "dom", - "es5", - "es2015.collection", - "es2015.iterable", - "es2015.promise" - ], - "strict": true - }, - "include": [ - "**/*.ts" - ], - "files": [ - "../node_modules/monaco-editor-core/monaco.d.ts" - ] + "compilerOptions": { + "module": "amd", + "moduleResolution": "node", + "outDir": "../release/dev", + "target": "es5", + "lib": [ + "dom", + "es5", + "es2015.collection", + "es2015.iterable", + "es2015.promise" + ], + "strict": true + }, + "include": ["**/*.ts"], + "files": ["../node_modules/monaco-editor-core/monaco.d.ts"] } diff --git a/src/workerManager.ts b/src/workerManager.ts index fe6e2455..48f6bf83 100644 --- a/src/workerManager.ts +++ b/src/workerManager.ts @@ -1,110 +1,117 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { LanguageServiceDefaultsImpl } from './monaco.contribution'; -import { TypeScriptWorker } from './tsWorker'; - -import IDisposable = monaco.IDisposable; -import Uri = monaco.Uri; - -export class WorkerManager { - - private _modeId: string; - private _defaults: LanguageServiceDefaultsImpl; - private _configChangeListener: IDisposable; - private _updateExtraLibsToken: number; - private _extraLibsChangeListener: IDisposable; - - private _worker: monaco.editor.MonacoWebWorker | null; - private _client: Promise | null; - - constructor(modeId: string, defaults: LanguageServiceDefaultsImpl) { - this._modeId = modeId; - this._defaults = defaults; - this._worker = null; - this._client = null; - this._configChangeListener = this._defaults.onDidChange(() => this._stopWorker()); - this._updateExtraLibsToken = 0; - this._extraLibsChangeListener = this._defaults.onDidExtraLibsChange(() => this._updateExtraLibs()); - } - - private _stopWorker(): void { - if (this._worker) { - this._worker.dispose(); - this._worker = null; - } - this._client = null; - } - - dispose(): void { - this._configChangeListener.dispose(); - this._extraLibsChangeListener.dispose(); - this._stopWorker(); - } - - private async _updateExtraLibs(): Promise { - if (!this._worker) { - return; - } - const myToken = ++this._updateExtraLibsToken; - const proxy = await this._worker.getProxy(); - if (this._updateExtraLibsToken !== myToken) { - // avoid multiple calls - return; - } - proxy.updateExtraLibs(this._defaults.getExtraLibs()); - } - - private _getClient(): Promise { - if (!this._client) { - this._worker = monaco.editor.createWebWorker({ - - // module that exports the create() method and returns a `TypeScriptWorker` instance - moduleId: 'vs/language/typescript/tsWorker', - - label: this._modeId, - - keepIdleModels: true, - - // passed in to the create() method - createData: { - compilerOptions: this._defaults.getCompilerOptions(), - extraLibs: this._defaults.getExtraLibs(), - customWorkerPath: this._defaults.workerOptions.customWorkerPath - } - }); - - let p = >this._worker.getProxy(); - - if (this._defaults.getEagerModelSync()) { - p = p.then(worker => { - if (this._worker) { - return this._worker.withSyncedResources(monaco.editor.getModels() - .filter(model => model.getModeId() === this._modeId) - .map(model => model.uri) - ); - } - return worker; - }); - } - - this._client = p; - } - - return this._client; - } - - getLanguageServiceWorker(...resources: Uri[]): Promise { - let _client: TypeScriptWorker; - return this._getClient().then((client) => { - _client = client - }).then(_ => { - if (this._worker) { - return this._worker.withSyncedResources(resources) - } - }).then(_ => _client); - } -} +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { LanguageServiceDefaultsImpl } from './monaco.contribution'; +import { TypeScriptWorker } from './tsWorker'; + +import IDisposable = monaco.IDisposable; +import Uri = monaco.Uri; + +export class WorkerManager { + private _modeId: string; + private _defaults: LanguageServiceDefaultsImpl; + private _configChangeListener: IDisposable; + private _updateExtraLibsToken: number; + private _extraLibsChangeListener: IDisposable; + + private _worker: monaco.editor.MonacoWebWorker | null; + private _client: Promise | null; + + constructor(modeId: string, defaults: LanguageServiceDefaultsImpl) { + this._modeId = modeId; + this._defaults = defaults; + this._worker = null; + this._client = null; + this._configChangeListener = this._defaults.onDidChange(() => + this._stopWorker() + ); + this._updateExtraLibsToken = 0; + this._extraLibsChangeListener = this._defaults.onDidExtraLibsChange(() => + this._updateExtraLibs() + ); + } + + private _stopWorker(): void { + if (this._worker) { + this._worker.dispose(); + this._worker = null; + } + this._client = null; + } + + dispose(): void { + this._configChangeListener.dispose(); + this._extraLibsChangeListener.dispose(); + this._stopWorker(); + } + + private async _updateExtraLibs(): Promise { + if (!this._worker) { + return; + } + const myToken = ++this._updateExtraLibsToken; + const proxy = await this._worker.getProxy(); + if (this._updateExtraLibsToken !== myToken) { + // avoid multiple calls + return; + } + proxy.updateExtraLibs(this._defaults.getExtraLibs()); + } + + private _getClient(): Promise { + if (!this._client) { + this._worker = monaco.editor.createWebWorker({ + // module that exports the create() method and returns a `TypeScriptWorker` instance + moduleId: 'vs/language/typescript/tsWorker', + + label: this._modeId, + + keepIdleModels: true, + + // passed in to the create() method + createData: { + compilerOptions: this._defaults.getCompilerOptions(), + extraLibs: this._defaults.getExtraLibs(), + customWorkerPath: this._defaults.workerOptions.customWorkerPath + } + }); + + let p = >this._worker.getProxy(); + + if (this._defaults.getEagerModelSync()) { + p = p.then((worker) => { + if (this._worker) { + return this._worker.withSyncedResources( + monaco.editor + .getModels() + .filter((model) => model.getModeId() === this._modeId) + .map((model) => model.uri) + ); + } + return worker; + }); + } + + this._client = p; + } + + return this._client; + } + + getLanguageServiceWorker(...resources: Uri[]): Promise { + let _client: TypeScriptWorker; + return this._getClient() + .then((client) => { + _client = client; + }) + .then((_) => { + if (this._worker) { + return this._worker.withSyncedResources(resources); + } + }) + .then((_) => _client); + } +} diff --git a/test/custom-worker.html b/test/custom-worker.html index 74bfdbb1..e21e2918 100644 --- a/test/custom-worker.html +++ b/test/custom-worker.html @@ -8,215 +8,230 @@ - - - - - - + + + + + + +

Monaco Editor TypeScript test page

+ +
+

Custom webworker

+ + -

Monaco Editor TypeScript test page

- -
-

Custom webworker

- - + + + + - - - - + - + document.getElementById('getAST').onclick = async () => { + const model = editor.getModel(); + const worker = await monaco.languages.typescript.getTypeScriptWorker(); + const thisWorker = await worker(model.uri); + const ast = await thisWorker.printAST(model.uri.toString()); + console.log(ast); + }; + }); + + diff --git a/test/custom-worker.js b/test/custom-worker.js index 3eeacd1d..3652f3c9 100644 --- a/test/custom-worker.js +++ b/test/custom-worker.js @@ -2,59 +2,58 @@ // which can do work on a bg thread. // This version of the vfs edits the global scope (in the case of a webworker, this is 'self') -importScripts("https://unpkg.com/@typescript/vfs@1.3.0/dist/vfs.globals.js") +importScripts('https://unpkg.com/@typescript/vfs@1.3.0/dist/vfs.globals.js'); /** @type { import("@typescript/vfs") } */ -const tsvfs = globalThis.tsvfs +const tsvfs = globalThis.tsvfs; /** @type {import("../src/tsWorker").CustomTSWebWorkerFactory }*/ const worker = (TypeScriptWorker, ts, libFileMap) => { - return class MonacoTSWorker extends TypeScriptWorker { - - // Adds a custom function to the webworker - async getDTSEmitForFile(fileName) { - const result = await this.getEmitOutput(fileName) - const firstDTS = result.outputFiles.find(o => o.name.endsWith(".d.ts")) - return (firstDTS && firstDTS.text) || "" - } - - async printAST(fileName) { - console.log("Creating virtual TS project") - const compilerOptions = this.getCompilationSettings() - const fsMap = new Map() - for (const key of Object.keys(libFileMap)) { - fsMap.set(key, "/" + libFileMap[key]) - } - - const thisCode = await this.getScriptText(fileName) - fsMap.set("index.ts", thisCode) - - console.log("Starting up TS program") - const system = tsvfs.createSystem(fsMap) - const host = tsvfs.createVirtualCompilerHost(system, compilerOptions, ts) - - const program = ts.createProgram({ - rootNames: [...fsMap.keys()], - options: compilerOptions, - host: host.compilerHost, - }) - - // Now I can look at the AST for the .ts file too - const mainSrcFile = program.getSourceFile("index.ts") - let miniAST = "SourceFile" - - const recurse = (parent, depth) => { - if (depth > 5) return - ts.forEachChild(parent, node => { - const spaces = " ".repeat(depth + 1) - miniAST += `\n${spaces}${ts.SyntaxKind[node.kind]}` - recurse(node, depth + 1) - }) - } - recurse(mainSrcFile, 0) - return miniAST - } - } -} - -self.customTSWorkerFactory = worker + return class MonacoTSWorker extends TypeScriptWorker { + // Adds a custom function to the webworker + async getDTSEmitForFile(fileName) { + const result = await this.getEmitOutput(fileName); + const firstDTS = result.outputFiles.find((o) => o.name.endsWith('.d.ts')); + return (firstDTS && firstDTS.text) || ''; + } + + async printAST(fileName) { + console.log('Creating virtual TS project'); + const compilerOptions = this.getCompilationSettings(); + const fsMap = new Map(); + for (const key of Object.keys(libFileMap)) { + fsMap.set(key, '/' + libFileMap[key]); + } + + const thisCode = await this.getScriptText(fileName); + fsMap.set('index.ts', thisCode); + + console.log('Starting up TS program'); + const system = tsvfs.createSystem(fsMap); + const host = tsvfs.createVirtualCompilerHost(system, compilerOptions, ts); + + const program = ts.createProgram({ + rootNames: [...fsMap.keys()], + options: compilerOptions, + host: host.compilerHost + }); + + // Now I can look at the AST for the .ts file too + const mainSrcFile = program.getSourceFile('index.ts'); + let miniAST = 'SourceFile'; + + const recurse = (parent, depth) => { + if (depth > 5) return; + ts.forEachChild(parent, (node) => { + const spaces = ' '.repeat(depth + 1); + miniAST += `\n${spaces}${ts.SyntaxKind[node.kind]}`; + recurse(node, depth + 1); + }); + }; + recurse(mainSrcFile, 0); + return miniAST; + } + }; +}; + +self.customTSWorkerFactory = worker; diff --git a/test/index.html b/test/index.html index c9052f80..7d9a8388 100644 --- a/test/index.html +++ b/test/index.html @@ -1,205 +1,224 @@ - - - - - - - - - -

Monaco Editor TypeScript test page

- -
-

Compiler settings

-
- - - - - - - - - - + + + + + + + + +

Monaco Editor TypeScript test page

+ +
+

Compiler settings

+
+ + + + + + + + + +