Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

servicesHost.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const path = require("path");
  4. const constants = require("./constants");
  5. const resolver_1 = require("./resolver");
  6. const utils_1 = require("./utils");
  7. /**
  8. * Create the TypeScript language service
  9. */
  10. function makeServicesHost(scriptRegex, log, loader, instance, enableFileCaching, projectReferences) {
  11. const { compiler, compilerOptions, appendTsTsxSuffixesIfRequired, files, loaderOptions: { resolveModuleName: customResolveModuleName, resolveTypeReferenceDirective: customResolveTypeReferenceDirective } } = instance;
  12. const newLine = compilerOptions.newLine === constants.CarriageReturnLineFeedCode
  13. ? constants.CarriageReturnLineFeed
  14. : compilerOptions.newLine === constants.LineFeedCode
  15. ? constants.LineFeed
  16. : constants.EOL;
  17. // make a (sync) resolver that follows webpack's rules
  18. const resolveSync = resolver_1.makeResolver(loader._compiler.options);
  19. const readFileWithFallback = (filePath, encoding) => compiler.sys.readFile(filePath, encoding) || utils_1.readFile(filePath, encoding);
  20. const fileExists = (filePathToCheck) => compiler.sys.fileExists(filePathToCheck) ||
  21. utils_1.readFile(filePathToCheck) !== undefined;
  22. const moduleResolutionHost = {
  23. fileExists,
  24. readFile: readFileWithFallback,
  25. realpath: compiler.sys.realpath,
  26. directoryExists: compiler.sys.directoryExists
  27. };
  28. const clearCache = enableFileCaching ? addCache(moduleResolutionHost) : null;
  29. // loader.context seems to work fine on Linux / Mac regardless causes problems for @types resolution on Windows for TypeScript < 2.3
  30. const getCurrentDirectory = () => loader.context;
  31. const resolvers = makeResolvers(compiler, compilerOptions, moduleResolutionHost, customResolveTypeReferenceDirective, customResolveModuleName, resolveSync, appendTsTsxSuffixesIfRequired, scriptRegex, instance);
  32. const servicesHost = {
  33. getProjectVersion: () => `${instance.version}`,
  34. getProjectReferences: () => projectReferences,
  35. getScriptFileNames: () => [...files.keys()].filter(filePath => filePath.match(scriptRegex)),
  36. getScriptVersion: (fileName) => {
  37. fileName = path.normalize(fileName);
  38. const file = files.get(fileName);
  39. return file === undefined ? '' : file.version.toString();
  40. },
  41. getScriptSnapshot: (fileName) => {
  42. // This is called any time TypeScript needs a file's text
  43. // We either load from memory or from disk
  44. fileName = path.normalize(fileName);
  45. let file = files.get(fileName);
  46. if (file === undefined) {
  47. const text = utils_1.readFile(fileName);
  48. if (text === undefined) {
  49. return undefined;
  50. }
  51. file = { version: 0, text };
  52. files.set(fileName, file);
  53. }
  54. return compiler.ScriptSnapshot.fromString(file.text);
  55. },
  56. /**
  57. * getDirectories is also required for full import and type reference completions.
  58. * Without it defined, certain completions will not be provided
  59. */
  60. getDirectories: compiler.sys.getDirectories,
  61. /**
  62. * For @types expansion, these two functions are needed.
  63. */
  64. directoryExists: moduleResolutionHost.directoryExists,
  65. useCaseSensitiveFileNames: () => compiler.sys.useCaseSensitiveFileNames,
  66. realpath: moduleResolutionHost.realpath,
  67. // The following three methods are necessary for @types resolution from TS 2.4.1 onwards see: https://github.com/Microsoft/TypeScript/issues/16772
  68. fileExists: moduleResolutionHost.fileExists,
  69. readFile: moduleResolutionHost.readFile,
  70. readDirectory: compiler.sys.readDirectory,
  71. getCurrentDirectory,
  72. getCompilationSettings: () => compilerOptions,
  73. getDefaultLibFileName: (options) => compiler.getDefaultLibFilePath(options),
  74. getNewLine: () => newLine,
  75. trace: log.log,
  76. log: log.log,
  77. // used for (/// <reference types="...">) see https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/250#issuecomment-485061329
  78. resolveTypeReferenceDirectives: resolvers.resolveTypeReferenceDirectives,
  79. resolveModuleNames: resolvers.resolveModuleNames,
  80. getCustomTransformers: () => instance.transformers
  81. };
  82. return { servicesHost, clearCache };
  83. }
  84. exports.makeServicesHost = makeServicesHost;
  85. function makeResolvers(compiler, compilerOptions, moduleResolutionHost, customResolveTypeReferenceDirective, customResolveModuleName, resolveSync, appendTsTsxSuffixesIfRequired, scriptRegex, instance) {
  86. const resolveTypeReferenceDirective = makeResolveTypeReferenceDirective(compiler, compilerOptions, moduleResolutionHost, customResolveTypeReferenceDirective);
  87. const resolveTypeReferenceDirectives = (typeDirectiveNames, containingFile, _redirectedReference) => typeDirectiveNames.map(directive => resolveTypeReferenceDirective(directive, containingFile)
  88. .resolvedTypeReferenceDirective);
  89. const resolveModuleName = makeResolveModuleName(compiler, compilerOptions, moduleResolutionHost, customResolveModuleName);
  90. const resolveModuleNames = (moduleNames, containingFile, _reusedNames, _redirectedReference) => {
  91. const resolvedModules = moduleNames.map(moduleName => resolveModule(resolveSync, resolveModuleName, appendTsTsxSuffixesIfRequired, scriptRegex, moduleName, containingFile));
  92. populateDependencyGraphs(resolvedModules, instance, containingFile);
  93. return resolvedModules;
  94. };
  95. return {
  96. resolveTypeReferenceDirectives,
  97. resolveModuleNames
  98. };
  99. }
  100. /**
  101. * Create the TypeScript Watch host
  102. */
  103. function makeWatchHost(scriptRegex, log, loader, instance, projectReferences) {
  104. const { compiler, compilerOptions, appendTsTsxSuffixesIfRequired, files, otherFiles, loaderOptions: { resolveModuleName: customResolveModuleName, resolveTypeReferenceDirective: customResolveTypeReferenceDirective } } = instance;
  105. const newLine = compilerOptions.newLine === constants.CarriageReturnLineFeedCode
  106. ? constants.CarriageReturnLineFeed
  107. : compilerOptions.newLine === constants.LineFeedCode
  108. ? constants.LineFeed
  109. : constants.EOL;
  110. // make a (sync) resolver that follows webpack's rules
  111. const resolveSync = resolver_1.makeResolver(loader._compiler.options);
  112. const readFileWithFallback = (filePath, encoding) => compiler.sys.readFile(filePath, encoding) || utils_1.readFile(filePath, encoding);
  113. const moduleResolutionHost = {
  114. fileExists,
  115. readFile: readFileWithFallback,
  116. realpath: compiler.sys.realpath
  117. };
  118. // loader.context seems to work fine on Linux / Mac regardless causes problems for @types resolution on Windows for TypeScript < 2.3
  119. const getCurrentDirectory = () => loader.context;
  120. const watchedFiles = {};
  121. const watchedDirectories = {};
  122. const watchedDirectoriesRecursive = {};
  123. const resolvers = makeResolvers(compiler, compilerOptions, moduleResolutionHost, customResolveTypeReferenceDirective, customResolveModuleName, resolveSync, appendTsTsxSuffixesIfRequired, scriptRegex, instance);
  124. const watchHost = {
  125. rootFiles: getRootFileNames(),
  126. options: compilerOptions,
  127. useCaseSensitiveFileNames: () => compiler.sys.useCaseSensitiveFileNames,
  128. getNewLine: () => newLine,
  129. getCurrentDirectory,
  130. getDefaultLibFileName: options => compiler.getDefaultLibFilePath(options),
  131. fileExists,
  132. readFile: readFileWithCachingText,
  133. directoryExists: dirPath => compiler.sys.directoryExists(path.normalize(dirPath)),
  134. getDirectories: dirPath => compiler.sys.getDirectories(path.normalize(dirPath)),
  135. readDirectory: (dirPath, extensions, exclude, include, depth) => compiler.sys.readDirectory(path.normalize(dirPath), extensions, exclude, include, depth),
  136. realpath: dirPath => compiler.sys.resolvePath(path.normalize(dirPath)),
  137. trace: logData => log.log(logData),
  138. watchFile,
  139. watchDirectory,
  140. // used for (/// <reference types="...">) see https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/250#issuecomment-485061329
  141. resolveTypeReferenceDirectives: resolvers.resolveTypeReferenceDirectives,
  142. resolveModuleNames: resolvers.resolveModuleNames,
  143. invokeFileWatcher,
  144. invokeDirectoryWatcher,
  145. updateRootFileNames: () => {
  146. instance.changedFilesList = false;
  147. if (instance.watchOfFilesAndCompilerOptions !== undefined) {
  148. instance.watchOfFilesAndCompilerOptions.updateRootFileNames(getRootFileNames());
  149. }
  150. },
  151. createProgram: projectReferences === undefined
  152. ? compiler.createAbstractBuilder
  153. : createBuilderProgramWithReferences
  154. };
  155. return watchHost;
  156. function getRootFileNames() {
  157. return [...files.keys()].filter(filePath => filePath.match(scriptRegex));
  158. }
  159. function readFileWithCachingText(fileName, encoding) {
  160. fileName = path.normalize(fileName);
  161. const file = files.get(fileName) || otherFiles.get(fileName);
  162. if (file !== undefined) {
  163. return file.text;
  164. }
  165. const text = readFileWithFallback(fileName, encoding);
  166. if (text === undefined) {
  167. return undefined;
  168. }
  169. otherFiles.set(fileName, { version: 0, text });
  170. return text;
  171. }
  172. function fileExists(fileName) {
  173. const filePath = path.normalize(fileName);
  174. return files.has(filePath) || compiler.sys.fileExists(filePath);
  175. }
  176. function invokeWatcherCallbacks(callbacks, fileName, eventKind) {
  177. if (callbacks !== undefined) {
  178. // The array copy is made to ensure that even if one of the callback removes the callbacks,
  179. // we dont miss any callbacks following it
  180. const cbs = callbacks.slice();
  181. for (const cb of cbs) {
  182. cb(fileName, eventKind);
  183. }
  184. }
  185. }
  186. function invokeFileWatcher(fileName, eventKind) {
  187. fileName = path.normalize(fileName);
  188. invokeWatcherCallbacks(watchedFiles[fileName], fileName, eventKind);
  189. }
  190. function invokeDirectoryWatcher(directory, fileAddedOrRemoved) {
  191. directory = path.normalize(directory);
  192. invokeWatcherCallbacks(watchedDirectories[directory], fileAddedOrRemoved);
  193. invokeRecursiveDirectoryWatcher(directory, fileAddedOrRemoved);
  194. }
  195. function invokeRecursiveDirectoryWatcher(directory, fileAddedOrRemoved) {
  196. directory = path.normalize(directory);
  197. invokeWatcherCallbacks(watchedDirectoriesRecursive[directory], fileAddedOrRemoved);
  198. const basePath = path.dirname(directory);
  199. if (directory !== basePath) {
  200. invokeRecursiveDirectoryWatcher(basePath, fileAddedOrRemoved);
  201. }
  202. }
  203. function createWatcher(file, callbacks, callback) {
  204. file = path.normalize(file);
  205. const existing = callbacks[file];
  206. if (existing === undefined) {
  207. callbacks[file] = [callback];
  208. }
  209. else {
  210. existing.push(callback);
  211. }
  212. return {
  213. close: () => {
  214. // tslint:disable-next-line:no-shadowed-variable
  215. const existing = callbacks[file];
  216. if (existing !== undefined) {
  217. utils_1.unorderedRemoveItem(existing, callback);
  218. }
  219. }
  220. };
  221. }
  222. function watchFile(fileName, callback, _pollingInterval) {
  223. return createWatcher(fileName, watchedFiles, callback);
  224. }
  225. function watchDirectory(fileName, callback, recursive) {
  226. return createWatcher(fileName, recursive === true ? watchedDirectoriesRecursive : watchedDirectories, callback);
  227. }
  228. function createBuilderProgramWithReferences(rootNames, options, host, oldProgram, configFileParsingDiagnostics) {
  229. const program = compiler.createProgram({
  230. rootNames: rootNames,
  231. options: options,
  232. host,
  233. oldProgram: oldProgram && oldProgram.getProgram(),
  234. configFileParsingDiagnostics,
  235. projectReferences
  236. });
  237. const builderProgramHost = host;
  238. return compiler.createAbstractBuilder(program, builderProgramHost, oldProgram, configFileParsingDiagnostics);
  239. }
  240. }
  241. exports.makeWatchHost = makeWatchHost;
  242. function makeResolveTypeReferenceDirective(compiler, compilerOptions, moduleResolutionHost, customResolveTypeReferenceDirective) {
  243. if (customResolveTypeReferenceDirective === undefined) {
  244. return (directive, containingFile) => compiler.resolveTypeReferenceDirective(directive, containingFile, compilerOptions, moduleResolutionHost);
  245. }
  246. return (directive, containingFile) => customResolveTypeReferenceDirective(directive, containingFile, compilerOptions, moduleResolutionHost, compiler.resolveTypeReferenceDirective);
  247. }
  248. function isJsImplementationOfTypings(resolvedModule, tsResolution) {
  249. return (resolvedModule.resolvedFileName.endsWith('js') &&
  250. /\.d\.ts$/.test(tsResolution.resolvedFileName));
  251. }
  252. function resolveModule(resolveSync, resolveModuleName, appendTsTsxSuffixesIfRequired, scriptRegex, moduleName, containingFile) {
  253. let resolutionResult;
  254. try {
  255. const originalFileName = resolveSync(undefined, path.normalize(path.dirname(containingFile)), moduleName);
  256. const resolvedFileName = appendTsTsxSuffixesIfRequired(originalFileName);
  257. if (resolvedFileName.match(scriptRegex) !== null) {
  258. resolutionResult = { resolvedFileName, originalFileName };
  259. }
  260. // tslint:disable-next-line:no-empty
  261. }
  262. catch (e) { }
  263. const tsResolution = resolveModuleName(moduleName, containingFile);
  264. if (tsResolution.resolvedModule !== undefined) {
  265. const resolvedFileName = path.normalize(tsResolution.resolvedModule.resolvedFileName);
  266. const tsResolutionResult = {
  267. originalFileName: resolvedFileName,
  268. resolvedFileName,
  269. isExternalLibraryImport: tsResolution.resolvedModule.isExternalLibraryImport
  270. };
  271. return resolutionResult === undefined ||
  272. resolutionResult.resolvedFileName ===
  273. tsResolutionResult.resolvedFileName ||
  274. isJsImplementationOfTypings(resolutionResult, tsResolutionResult)
  275. ? tsResolutionResult
  276. : resolutionResult;
  277. }
  278. return resolutionResult;
  279. }
  280. function makeResolveModuleName(compiler, compilerOptions, moduleResolutionHost, customResolveModuleName) {
  281. if (customResolveModuleName === undefined) {
  282. return (moduleName, containingFile) => compiler.resolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost);
  283. }
  284. return (moduleName, containingFile) => customResolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost, compiler.resolveModuleName);
  285. }
  286. function populateDependencyGraphs(resolvedModules, instance, containingFile) {
  287. resolvedModules = resolvedModules.filter(mod => mod !== null && mod !== undefined);
  288. instance.dependencyGraph[path.normalize(containingFile)] = resolvedModules;
  289. resolvedModules.forEach(resolvedModule => {
  290. if (instance.reverseDependencyGraph[resolvedModule.resolvedFileName] ===
  291. undefined) {
  292. instance.reverseDependencyGraph[resolvedModule.resolvedFileName] = {};
  293. }
  294. instance.reverseDependencyGraph[resolvedModule.resolvedFileName][path.normalize(containingFile)] = true;
  295. });
  296. }
  297. const cacheableFunctions = [
  298. 'fileExists',
  299. 'directoryExists',
  300. 'realpath'
  301. ];
  302. function addCache(servicesHost) {
  303. const clearCacheFunctions = [];
  304. cacheableFunctions.forEach((functionToCache) => {
  305. const originalFunction = servicesHost[functionToCache];
  306. if (originalFunction !== undefined) {
  307. const cache = createCache(originalFunction);
  308. servicesHost[functionToCache] = cache.getCached;
  309. clearCacheFunctions.push(cache.clear);
  310. }
  311. });
  312. return () => clearCacheFunctions.forEach(clear => clear());
  313. }
  314. function createCache(originalFunction) {
  315. const cache = new Map();
  316. return {
  317. clear: () => {
  318. cache.clear();
  319. },
  320. getCached: (arg) => {
  321. let res = cache.get(arg);
  322. if (res !== undefined) {
  323. return res;
  324. }
  325. res = originalFunction(arg);
  326. cache.set(arg, res);
  327. return res;
  328. }
  329. };
  330. }