tsWorker.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. 'use strict';
  6. import * as ts from './lib/typescriptServices';
  7. import { libFileMap } from './lib/lib';
  8. import {
  9. Diagnostic,
  10. IExtraLibs,
  11. TypeScriptWorker as ITypeScriptWorker
  12. } from './monaco.contribution';
  13. import { worker } from './fillers/monaco-editor-core';
  14. export class TypeScriptWorker implements ts.LanguageServiceHost, ITypeScriptWorker {
  15. // --- model sync -----------------------
  16. private _ctx: worker.IWorkerContext;
  17. private _extraLibs: IExtraLibs = Object.create(null);
  18. private _languageService = ts.createLanguageService(this);
  19. private _compilerOptions: ts.CompilerOptions;
  20. constructor(ctx: worker.IWorkerContext, createData: ICreateData) {
  21. this._ctx = ctx;
  22. this._compilerOptions = createData.compilerOptions;
  23. this._extraLibs = createData.extraLibs;
  24. }
  25. // --- language service host ---------------
  26. getCompilationSettings(): ts.CompilerOptions {
  27. return this._compilerOptions;
  28. }
  29. getScriptFileNames(): string[] {
  30. let models = this._ctx.getMirrorModels().map((model) => model.uri.toString());
  31. return models.concat(Object.keys(this._extraLibs));
  32. }
  33. private _getModel(fileName: string): worker.IMirrorModel | null {
  34. let models = this._ctx.getMirrorModels();
  35. for (let i = 0; i < models.length; i++) {
  36. if (models[i].uri.toString() === fileName) {
  37. return models[i];
  38. }
  39. }
  40. return null;
  41. }
  42. getScriptVersion(fileName: string): string {
  43. let model = this._getModel(fileName);
  44. if (model) {
  45. return model.version.toString();
  46. } else if (this.isDefaultLibFileName(fileName)) {
  47. // default lib is static
  48. return '1';
  49. } else if (fileName in this._extraLibs) {
  50. return String(this._extraLibs[fileName].version);
  51. }
  52. return '';
  53. }
  54. async getScriptText(fileName: string): Promise<string | undefined> {
  55. return this._getScriptText(fileName);
  56. }
  57. _getScriptText(fileName: string): string | undefined {
  58. let text: string;
  59. let model = this._getModel(fileName);
  60. const libizedFileName = 'lib.' + fileName + '.d.ts';
  61. if (model) {
  62. // a true editor model
  63. text = model.getValue();
  64. } else if (fileName in libFileMap) {
  65. text = libFileMap[fileName];
  66. } else if (libizedFileName in libFileMap) {
  67. text = libFileMap[libizedFileName];
  68. } else if (fileName in this._extraLibs) {
  69. // extra lib
  70. text = this._extraLibs[fileName].content;
  71. } else {
  72. return;
  73. }
  74. return text;
  75. }
  76. getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined {
  77. const text = this._getScriptText(fileName);
  78. if (text === undefined) {
  79. return;
  80. }
  81. return <ts.IScriptSnapshot>{
  82. getText: (start, end) => text.substring(start, end),
  83. getLength: () => text.length,
  84. getChangeRange: () => undefined
  85. };
  86. }
  87. getScriptKind?(fileName: string): ts.ScriptKind {
  88. const suffix = fileName.substr(fileName.lastIndexOf('.') + 1);
  89. switch (suffix) {
  90. case 'ts':
  91. return ts.ScriptKind.TS;
  92. case 'tsx':
  93. return ts.ScriptKind.TSX;
  94. case 'js':
  95. return ts.ScriptKind.JS;
  96. case 'jsx':
  97. return ts.ScriptKind.JSX;
  98. default:
  99. return this.getCompilationSettings().allowJs ? ts.ScriptKind.JS : ts.ScriptKind.TS;
  100. }
  101. }
  102. getCurrentDirectory(): string {
  103. return '';
  104. }
  105. getDefaultLibFileName(options: ts.CompilerOptions): string {
  106. switch (options.target) {
  107. case 99 /* ESNext */:
  108. const esnext = 'lib.esnext.full.d.ts';
  109. if (esnext in libFileMap || esnext in this._extraLibs) return esnext;
  110. case 7 /* ES2020 */:
  111. case 6 /* ES2019 */:
  112. case 5 /* ES2018 */:
  113. case 4 /* ES2017 */:
  114. case 3 /* ES2016 */:
  115. case 2 /* ES2015 */:
  116. default:
  117. // Support a dynamic lookup for the ES20XX version based on the target
  118. // which is safe unless TC39 changes their numbering system
  119. const eslib = `lib.es${2013 + (options.target || 99)}.full.d.ts`;
  120. // Note: This also looks in _extraLibs, If you want
  121. // to add support for additional target options, you will need to
  122. // add the extra dts files to _extraLibs via the API.
  123. if (eslib in libFileMap || eslib in this._extraLibs) {
  124. return eslib;
  125. }
  126. return 'lib.es6.d.ts'; // We don't use lib.es2015.full.d.ts due to breaking change.
  127. case 1:
  128. case 0:
  129. return 'lib.d.ts';
  130. }
  131. }
  132. isDefaultLibFileName(fileName: string): boolean {
  133. return fileName === this.getDefaultLibFileName(this._compilerOptions);
  134. }
  135. async getLibFiles(): Promise<Record<string, string>> {
  136. return libFileMap;
  137. }
  138. // --- language features
  139. private static clearFiles(diagnostics: ts.Diagnostic[]): Diagnostic[] {
  140. // Clear the `file` field, which cannot be JSON'yfied because it
  141. // contains cyclic data structures.
  142. diagnostics.forEach((diag) => {
  143. diag.file = undefined;
  144. const related = <ts.Diagnostic[]>diag.relatedInformation;
  145. if (related) {
  146. related.forEach((diag2) => (diag2.file = undefined));
  147. }
  148. });
  149. return <Diagnostic[]>diagnostics;
  150. }
  151. async getSyntacticDiagnostics(fileName: string): Promise<Diagnostic[]> {
  152. const diagnostics = this._languageService.getSyntacticDiagnostics(fileName);
  153. return TypeScriptWorker.clearFiles(diagnostics);
  154. }
  155. async getSemanticDiagnostics(fileName: string): Promise<Diagnostic[]> {
  156. const diagnostics = this._languageService.getSemanticDiagnostics(fileName);
  157. return TypeScriptWorker.clearFiles(diagnostics);
  158. }
  159. async getSuggestionDiagnostics(fileName: string): Promise<Diagnostic[]> {
  160. const diagnostics = this._languageService.getSuggestionDiagnostics(fileName);
  161. return TypeScriptWorker.clearFiles(diagnostics);
  162. }
  163. async getCompilerOptionsDiagnostics(fileName: string): Promise<Diagnostic[]> {
  164. const diagnostics = this._languageService.getCompilerOptionsDiagnostics();
  165. return TypeScriptWorker.clearFiles(diagnostics);
  166. }
  167. async getCompletionsAtPosition(
  168. fileName: string,
  169. position: number
  170. ): Promise<ts.CompletionInfo | undefined> {
  171. return this._languageService.getCompletionsAtPosition(fileName, position, undefined);
  172. }
  173. async getCompletionEntryDetails(
  174. fileName: string,
  175. position: number,
  176. entry: string
  177. ): Promise<ts.CompletionEntryDetails | undefined> {
  178. return this._languageService.getCompletionEntryDetails(
  179. fileName,
  180. position,
  181. entry,
  182. undefined,
  183. undefined,
  184. undefined
  185. );
  186. }
  187. async getSignatureHelpItems(
  188. fileName: string,
  189. position: number
  190. ): Promise<ts.SignatureHelpItems | undefined> {
  191. return this._languageService.getSignatureHelpItems(fileName, position, undefined);
  192. }
  193. async getQuickInfoAtPosition(
  194. fileName: string,
  195. position: number
  196. ): Promise<ts.QuickInfo | undefined> {
  197. return this._languageService.getQuickInfoAtPosition(fileName, position);
  198. }
  199. async getOccurrencesAtPosition(
  200. fileName: string,
  201. position: number
  202. ): Promise<ReadonlyArray<ts.ReferenceEntry> | undefined> {
  203. return this._languageService.getOccurrencesAtPosition(fileName, position);
  204. }
  205. async getDefinitionAtPosition(
  206. fileName: string,
  207. position: number
  208. ): Promise<ReadonlyArray<ts.DefinitionInfo> | undefined> {
  209. return this._languageService.getDefinitionAtPosition(fileName, position);
  210. }
  211. async getReferencesAtPosition(
  212. fileName: string,
  213. position: number
  214. ): Promise<ts.ReferenceEntry[] | undefined> {
  215. return this._languageService.getReferencesAtPosition(fileName, position);
  216. }
  217. async getNavigationBarItems(fileName: string): Promise<ts.NavigationBarItem[]> {
  218. return this._languageService.getNavigationBarItems(fileName);
  219. }
  220. async getFormattingEditsForDocument(
  221. fileName: string,
  222. options: ts.FormatCodeOptions
  223. ): Promise<ts.TextChange[]> {
  224. return this._languageService.getFormattingEditsForDocument(fileName, options);
  225. }
  226. async getFormattingEditsForRange(
  227. fileName: string,
  228. start: number,
  229. end: number,
  230. options: ts.FormatCodeOptions
  231. ): Promise<ts.TextChange[]> {
  232. return this._languageService.getFormattingEditsForRange(fileName, start, end, options);
  233. }
  234. async getFormattingEditsAfterKeystroke(
  235. fileName: string,
  236. postion: number,
  237. ch: string,
  238. options: ts.FormatCodeOptions
  239. ): Promise<ts.TextChange[]> {
  240. return this._languageService.getFormattingEditsAfterKeystroke(fileName, postion, ch, options);
  241. }
  242. async findRenameLocations(
  243. fileName: string,
  244. position: number,
  245. findInStrings: boolean,
  246. findInComments: boolean,
  247. providePrefixAndSuffixTextForRename: boolean
  248. ): Promise<readonly ts.RenameLocation[] | undefined> {
  249. return this._languageService.findRenameLocations(
  250. fileName,
  251. position,
  252. findInStrings,
  253. findInComments,
  254. providePrefixAndSuffixTextForRename
  255. );
  256. }
  257. async getRenameInfo(
  258. fileName: string,
  259. position: number,
  260. options: ts.RenameInfoOptions
  261. ): Promise<ts.RenameInfo> {
  262. return this._languageService.getRenameInfo(fileName, position, options);
  263. }
  264. async getEmitOutput(fileName: string): Promise<ts.EmitOutput> {
  265. return this._languageService.getEmitOutput(fileName);
  266. }
  267. async getCodeFixesAtPosition(
  268. fileName: string,
  269. start: number,
  270. end: number,
  271. errorCodes: number[],
  272. formatOptions: ts.FormatCodeOptions
  273. ): Promise<ReadonlyArray<ts.CodeFixAction>> {
  274. const preferences = {};
  275. try {
  276. return this._languageService.getCodeFixesAtPosition(
  277. fileName,
  278. start,
  279. end,
  280. errorCodes,
  281. formatOptions,
  282. preferences
  283. );
  284. } catch {
  285. return [];
  286. }
  287. }
  288. async updateExtraLibs(extraLibs: IExtraLibs): Promise<void> {
  289. this._extraLibs = extraLibs;
  290. }
  291. }
  292. export interface ICreateData {
  293. compilerOptions: ts.CompilerOptions;
  294. extraLibs: IExtraLibs;
  295. customWorkerPath?: string;
  296. }
  297. /** The shape of the factory */
  298. export interface CustomTSWebWorkerFactory {
  299. (
  300. TSWorkerClass: typeof TypeScriptWorker,
  301. tsc: typeof ts,
  302. libs: Record<string, string>
  303. ): typeof TypeScriptWorker;
  304. }
  305. declare global {
  306. var importScripts: (path: string) => void | undefined;
  307. var customTSWorkerFactory: CustomTSWebWorkerFactory | undefined;
  308. }
  309. export function create(ctx: worker.IWorkerContext, createData: ICreateData): TypeScriptWorker {
  310. let TSWorkerClass = TypeScriptWorker;
  311. if (createData.customWorkerPath) {
  312. if (typeof importScripts === 'undefined') {
  313. console.warn(
  314. 'Monaco is not using webworkers for background tasks, and that is needed to support the customWorkerPath flag'
  315. );
  316. } else {
  317. importScripts(createData.customWorkerPath);
  318. const workerFactoryFunc: CustomTSWebWorkerFactory | undefined = self.customTSWorkerFactory;
  319. if (!workerFactoryFunc) {
  320. throw new Error(
  321. `The script at ${createData.customWorkerPath} does not add customTSWorkerFactory to self`
  322. );
  323. }
  324. TSWorkerClass = workerFactoryFunc(TypeScriptWorker, ts, libFileMap);
  325. }
  326. }
  327. return new TSWorkerClass(ctx, createData);
  328. }