tsWorker.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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 { IExtraLibs } from './monaco.contribution';
  9. import IWorkerContext = monaco.worker.IWorkerContext;
  10. export class TypeScriptWorker
  11. implements
  12. ts.LanguageServiceHost,
  13. monaco.languages.typescript.TypeScriptWorker {
  14. // --- model sync -----------------------
  15. private _ctx: IWorkerContext;
  16. private _extraLibs: IExtraLibs = Object.create(null);
  17. private _languageService = ts.createLanguageService(this);
  18. private _compilerOptions: ts.CompilerOptions;
  19. constructor(ctx: IWorkerContext, createData: ICreateData) {
  20. this._ctx = ctx;
  21. this._compilerOptions = createData.compilerOptions;
  22. this._extraLibs = createData.extraLibs;
  23. }
  24. // --- language service host ---------------
  25. getCompilationSettings(): ts.CompilerOptions {
  26. return this._compilerOptions;
  27. }
  28. getScriptFileNames(): string[] {
  29. let models = this._ctx
  30. .getMirrorModels()
  31. .map((model) => model.uri.toString());
  32. return models.concat(Object.keys(this._extraLibs));
  33. }
  34. private _getModel(fileName: string): monaco.worker.IMirrorModel | null {
  35. let models = this._ctx.getMirrorModels();
  36. for (let i = 0; i < models.length; i++) {
  37. if (models[i].uri.toString() === fileName) {
  38. return models[i];
  39. }
  40. }
  41. return null;
  42. }
  43. getScriptVersion(fileName: string): string {
  44. let model = this._getModel(fileName);
  45. if (model) {
  46. return model.version.toString();
  47. } else if (this.isDefaultLibFileName(fileName)) {
  48. // default lib is static
  49. return '1';
  50. } else if (fileName in this._extraLibs) {
  51. return String(this._extraLibs[fileName].version);
  52. }
  53. return '';
  54. }
  55. getScriptText(fileName: string): Promise<string | undefined> {
  56. return Promise.resolve(this._getScriptText(fileName));
  57. }
  58. _getScriptText(fileName: string): string | undefined {
  59. let text: string;
  60. let model = this._getModel(fileName);
  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 (fileName in this._extraLibs) {
  67. // extra lib
  68. text = this._extraLibs[fileName].content;
  69. } else {
  70. return;
  71. }
  72. return text;
  73. }
  74. getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined {
  75. const text = this._getScriptText(fileName);
  76. if (text === undefined) {
  77. return;
  78. }
  79. return <ts.IScriptSnapshot>{
  80. getText: (start, end) => text.substring(start, end),
  81. getLength: () => text.length,
  82. getChangeRange: () => undefined
  83. };
  84. }
  85. getScriptKind?(fileName: string): ts.ScriptKind {
  86. const suffix = fileName.substr(fileName.lastIndexOf('.') + 1);
  87. switch (suffix) {
  88. case 'ts':
  89. return ts.ScriptKind.TS;
  90. case 'tsx':
  91. return ts.ScriptKind.TSX;
  92. case 'js':
  93. return ts.ScriptKind.JS;
  94. case 'jsx':
  95. return ts.ScriptKind.JSX;
  96. default:
  97. return this.getCompilationSettings().allowJs
  98. ? ts.ScriptKind.JS
  99. : 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. getLibFiles(): Promise<Record<string, string>> {
  136. return Promise.resolve(libFileMap);
  137. }
  138. // --- language features
  139. private static clearFiles(
  140. diagnostics: ts.Diagnostic[]
  141. ): monaco.languages.typescript.Diagnostic[] {
  142. // Clear the `file` field, which cannot be JSON'yfied because it
  143. // contains cyclic data structures.
  144. diagnostics.forEach((diag) => {
  145. diag.file = undefined;
  146. const related = <ts.Diagnostic[]>diag.relatedInformation;
  147. if (related) {
  148. related.forEach((diag2) => (diag2.file = undefined));
  149. }
  150. });
  151. return <monaco.languages.typescript.Diagnostic[]>diagnostics;
  152. }
  153. getSyntacticDiagnostics(
  154. fileName: string
  155. ): Promise<monaco.languages.typescript.Diagnostic[]> {
  156. const diagnostics = this._languageService.getSyntacticDiagnostics(fileName);
  157. return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics));
  158. }
  159. getSemanticDiagnostics(
  160. fileName: string
  161. ): Promise<monaco.languages.typescript.Diagnostic[]> {
  162. const diagnostics = this._languageService.getSemanticDiagnostics(fileName);
  163. return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics));
  164. }
  165. getSuggestionDiagnostics(
  166. fileName: string
  167. ): Promise<monaco.languages.typescript.Diagnostic[]> {
  168. const diagnostics = this._languageService.getSuggestionDiagnostics(
  169. fileName
  170. );
  171. return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics));
  172. }
  173. getCompilerOptionsDiagnostics(
  174. fileName: string
  175. ): Promise<monaco.languages.typescript.Diagnostic[]> {
  176. const diagnostics = this._languageService.getCompilerOptionsDiagnostics();
  177. return Promise.resolve(TypeScriptWorker.clearFiles(diagnostics));
  178. }
  179. getCompletionsAtPosition(
  180. fileName: string,
  181. position: number
  182. ): Promise<ts.CompletionInfo | undefined> {
  183. return Promise.resolve(
  184. this._languageService.getCompletionsAtPosition(
  185. fileName,
  186. position,
  187. undefined
  188. )
  189. );
  190. }
  191. getCompletionEntryDetails(
  192. fileName: string,
  193. position: number,
  194. entry: string
  195. ): Promise<ts.CompletionEntryDetails | undefined> {
  196. return Promise.resolve(
  197. this._languageService.getCompletionEntryDetails(
  198. fileName,
  199. position,
  200. entry,
  201. undefined,
  202. undefined,
  203. undefined
  204. )
  205. );
  206. }
  207. getSignatureHelpItems(
  208. fileName: string,
  209. position: number
  210. ): Promise<ts.SignatureHelpItems | undefined> {
  211. return Promise.resolve(
  212. this._languageService.getSignatureHelpItems(fileName, position, undefined)
  213. );
  214. }
  215. getQuickInfoAtPosition(
  216. fileName: string,
  217. position: number
  218. ): Promise<ts.QuickInfo | undefined> {
  219. return Promise.resolve(
  220. this._languageService.getQuickInfoAtPosition(fileName, position)
  221. );
  222. }
  223. getOccurrencesAtPosition(
  224. fileName: string,
  225. position: number
  226. ): Promise<ReadonlyArray<ts.ReferenceEntry> | undefined> {
  227. return Promise.resolve(
  228. this._languageService.getOccurrencesAtPosition(fileName, position)
  229. );
  230. }
  231. getDefinitionAtPosition(
  232. fileName: string,
  233. position: number
  234. ): Promise<ReadonlyArray<ts.DefinitionInfo> | undefined> {
  235. return Promise.resolve(
  236. this._languageService.getDefinitionAtPosition(fileName, position)
  237. );
  238. }
  239. getReferencesAtPosition(
  240. fileName: string,
  241. position: number
  242. ): Promise<ts.ReferenceEntry[] | undefined> {
  243. return Promise.resolve(
  244. this._languageService.getReferencesAtPosition(fileName, position)
  245. );
  246. }
  247. getNavigationBarItems(fileName: string): Promise<ts.NavigationBarItem[]> {
  248. return Promise.resolve(
  249. this._languageService.getNavigationBarItems(fileName)
  250. );
  251. }
  252. getFormattingEditsForDocument(
  253. fileName: string,
  254. options: ts.FormatCodeOptions
  255. ): Promise<ts.TextChange[]> {
  256. return Promise.resolve(
  257. this._languageService.getFormattingEditsForDocument(fileName, options)
  258. );
  259. }
  260. getFormattingEditsForRange(
  261. fileName: string,
  262. start: number,
  263. end: number,
  264. options: ts.FormatCodeOptions
  265. ): Promise<ts.TextChange[]> {
  266. return Promise.resolve(
  267. this._languageService.getFormattingEditsForRange(
  268. fileName,
  269. start,
  270. end,
  271. options
  272. )
  273. );
  274. }
  275. getFormattingEditsAfterKeystroke(
  276. fileName: string,
  277. postion: number,
  278. ch: string,
  279. options: ts.FormatCodeOptions
  280. ): Promise<ts.TextChange[]> {
  281. return Promise.resolve(
  282. this._languageService.getFormattingEditsAfterKeystroke(
  283. fileName,
  284. postion,
  285. ch,
  286. options
  287. )
  288. );
  289. }
  290. findRenameLocations(
  291. fileName: string,
  292. position: number,
  293. findInStrings: boolean,
  294. findInComments: boolean,
  295. providePrefixAndSuffixTextForRename: boolean
  296. ): Promise<readonly ts.RenameLocation[] | undefined> {
  297. return Promise.resolve(
  298. this._languageService.findRenameLocations(
  299. fileName,
  300. position,
  301. findInStrings,
  302. findInComments,
  303. providePrefixAndSuffixTextForRename
  304. )
  305. );
  306. }
  307. getRenameInfo(
  308. fileName: string,
  309. position: number,
  310. options: ts.RenameInfoOptions
  311. ): Promise<ts.RenameInfo> {
  312. return Promise.resolve(
  313. this._languageService.getRenameInfo(fileName, position, options)
  314. );
  315. }
  316. getEmitOutput(fileName: string): Promise<ts.EmitOutput> {
  317. return Promise.resolve(this._languageService.getEmitOutput(fileName));
  318. }
  319. getCodeFixesAtPosition(
  320. fileName: string,
  321. start: number,
  322. end: number,
  323. errorCodes: number[],
  324. formatOptions: ts.FormatCodeOptions
  325. ): Promise<ReadonlyArray<ts.CodeFixAction>> {
  326. const preferences = {};
  327. return Promise.resolve(
  328. this._languageService.getCodeFixesAtPosition(
  329. fileName,
  330. start,
  331. end,
  332. errorCodes,
  333. formatOptions,
  334. preferences
  335. )
  336. );
  337. }
  338. updateExtraLibs(extraLibs: IExtraLibs) {
  339. this._extraLibs = extraLibs;
  340. }
  341. }
  342. export interface ICreateData {
  343. compilerOptions: ts.CompilerOptions;
  344. extraLibs: IExtraLibs;
  345. customWorkerPath?: string;
  346. }
  347. /** The shape of the factory */
  348. export interface CustomTSWebWorkerFactory {
  349. (
  350. TSWorkerClass: typeof TypeScriptWorker,
  351. tsc: typeof ts,
  352. libs: Record<string, string>
  353. ): typeof TypeScriptWorker;
  354. }
  355. declare global {
  356. var importScripts: (path: string) => void | undefined;
  357. var customTSWorkerFactory: CustomTSWebWorkerFactory | undefined;
  358. }
  359. export function create(
  360. ctx: IWorkerContext,
  361. createData: ICreateData
  362. ): TypeScriptWorker {
  363. let TSWorkerClass = TypeScriptWorker;
  364. if (createData.customWorkerPath) {
  365. if (typeof importScripts === 'undefined') {
  366. console.warn(
  367. 'Monaco is not using webworkers for background tasks, and that is needed to support the customWorkerPath flag'
  368. );
  369. } else {
  370. importScripts(createData.customWorkerPath);
  371. const workerFactoryFunc: CustomTSWebWorkerFactory | undefined =
  372. self.customTSWorkerFactory;
  373. if (!workerFactoryFunc) {
  374. throw new Error(
  375. `The script at ${createData.customWorkerPath} does not add customTSWorkerFactory to self`
  376. );
  377. }
  378. TSWorkerClass = workerFactoryFunc(TypeScriptWorker, ts, libFileMap);
  379. }
  380. }
  381. return new TSWorkerClass(ctx, createData);
  382. }