TypscriptLanguageExtension.ts 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. //
  2. // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
  3. // LICENSE: Atomic Game Engine Editor and Tools EULA
  4. // Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
  5. // license information: https://github.com/AtomicGameEngine/AtomicGameEngine
  6. //
  7. // Based upon the TypeScript language services example at https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#incremental-build-support-using-the-language-services
  8. import * as ExtensionServices from "../EditorExtensionServices";
  9. import * as EditorEvents from "../../editor/EditorEvents";
  10. import * as ts from "modules/typescript";
  11. import {TypescriptLanguageService, FileSystemInterface} from "./TypescriptLanguageService";
  12. import * as metrics from "modules/metrics";
  13. /**
  14. * Class that provides access to the Atomic filesystem routines
  15. */
  16. class AtomicFileSystem implements FileSystemInterface {
  17. /**
  18. * Deterimine if the particular file exists in the resources
  19. * @param {string} filename
  20. * @return {boolean}
  21. */
  22. fileExists(filename: string): boolean {
  23. return !Atomic.fileSystem.exists(filename);
  24. }
  25. /**
  26. * Grab the contents of the file
  27. * @param {string} filename
  28. * @return {string}
  29. */
  30. getFile(filename: string): string {
  31. let script = new Atomic.File(filename, Atomic.FILE_READ);
  32. try {
  33. return script.readText();
  34. } finally {
  35. script.close();
  36. }
  37. }
  38. /**
  39. * Write the contents to the file specified
  40. * @param {string} filename
  41. * @param {string} contents
  42. */
  43. writeFile(filename: string, contents: string) {
  44. let script = new Atomic.File(filename, Atomic.FILE_WRITE);
  45. try {
  46. script.writeString(contents);
  47. script.flush();
  48. } finally {
  49. script.close();
  50. }
  51. }
  52. }
  53. /**
  54. * Resource extension that handles compiling or transpling typescript on file save.
  55. */
  56. export default class TypescriptLanguageExtension implements ExtensionServices.ResourceService, ExtensionServices.ProjectService {
  57. name: string = "TypeScriptResourceService";
  58. description: string = "This service transpiles TypeScript into JavaScript on save.";
  59. /**
  60. * Perform a full compile on save, or just transpile the current file
  61. * @type {boolean}
  62. */
  63. fullCompile: boolean = true;
  64. /**
  65. * The language service that will handle building
  66. * @type {TypescriptLanguageService}
  67. */
  68. languageService: TypescriptLanguageService = null;
  69. /**
  70. * Determines if the file name/path provided is something we care about
  71. * @param {string} path
  72. * @return {boolean}
  73. */
  74. private isValidFiletype(path: string): boolean {
  75. const ext = Atomic.getExtension(path);
  76. if (ext == ".ts") {
  77. return true;
  78. }
  79. return false;
  80. }
  81. /**
  82. * Seed the language service with all of the relevant files in the project
  83. */
  84. private loadProjectFiles() {
  85. // First we need to load in a copy of the lib.core.d.ts that is necessary for the hosted typescript compiler
  86. this.languageService.addProjectFile(Atomic.addTrailingSlash(Atomic.addTrailingSlash(ToolCore.toolEnvironment.toolDataDir) + "TypeScriptSupport") + "lib.core.d.ts");
  87. // Load up a copy of the duktape.d.ts
  88. this.languageService.addProjectFile(Atomic.addTrailingSlash(Atomic.addTrailingSlash(ToolCore.toolEnvironment.toolDataDir) + "TypeScriptSupport") + "duktape.d.ts");
  89. //scan all the files in the project
  90. Atomic.fileSystem.scanDir(ToolCore.toolSystem.project.resourcePath, "*.ts", Atomic.SCAN_FILES, true).forEach(filename => {
  91. this.languageService.addProjectFile(Atomic.addTrailingSlash(ToolCore.toolSystem.project.resourcePath) + filename);
  92. });
  93. // Look in a 'typings' directory for any typescript definition files
  94. const typingsDir = Atomic.addTrailingSlash(ToolCore.toolSystem.project.projectPath) + "typings";
  95. Atomic.fileSystem.scanDir(typingsDir, "*.d.ts", Atomic.SCAN_FILES, true).forEach(filename => {
  96. this.languageService.addProjectFile(Atomic.addTrailingSlash(typingsDir) + filename);
  97. });
  98. }
  99. /**
  100. * Inject this language service into the registry
  101. * @return {[type]} True if successful
  102. */
  103. initialize(serviceRegistry: ExtensionServices.ServiceLocatorType) {
  104. // initialize the language service
  105. this.languageService = new TypescriptLanguageService(new AtomicFileSystem());
  106. // We care about both resource events as well as project events
  107. serviceRegistry.resourceServices.register(this);
  108. serviceRegistry.projectServices.register(this);
  109. }
  110. /*** ResourceService implementation ****/
  111. /**
  112. * Can this service extension handle the save event for the resource?
  113. * @param {EditorEvents.SaveResourceEvent} ev the
  114. * @return {boolean} return true if this service can handle the resource
  115. */
  116. canSave(ev: EditorEvents.SaveResourceEvent): boolean {
  117. return this.isValidFiletype(ev.path);
  118. }
  119. /**
  120. * Called once a resource has been saved
  121. * @param {EditorEvents.SaveResourceEvent} ev
  122. */
  123. save(ev: EditorEvents.SaveResourceEvent) {
  124. console.log(`${this.name}: received a save resource event for ${ev.path}`);
  125. if (this.fullCompile) {
  126. this.languageService.compile([ev.path], {
  127. noEmitOnError: true,
  128. noImplicitAny: false,
  129. target: ts.ScriptTarget.ES5,
  130. module: ts.ModuleKind.CommonJS,
  131. noLib: true
  132. });
  133. } else {
  134. this.languageService.transpile([ev.path], {
  135. noEmitOnError: false,
  136. noImplicitAny: false,
  137. target: ts.ScriptTarget.ES5,
  138. module: ts.ModuleKind.CommonJS,
  139. noLib: true
  140. });
  141. }
  142. metrics.logMetrics();
  143. }
  144. /**
  145. * Determine if we care if an asset has been deleted
  146. * @param {EditorEvents.DeleteResourceEvent} ev
  147. * @return {boolean} true if we care
  148. */
  149. canDelete(ev: EditorEvents.DeleteResourceEvent): boolean {
  150. return this.isValidFiletype(ev.path);
  151. }
  152. /**
  153. * Handle the delete. This should delete the corresponding javascript file
  154. * @param {EditorEvents.DeleteResourceEvent} ev
  155. */
  156. delete(ev: EditorEvents.DeleteResourceEvent) {
  157. console.log(`${this.name}: received a delete resource event`);
  158. // notify the typescript language service that the file has been deleted
  159. this.languageService.deleteProjectFile(ev.path);
  160. // Delete the corresponding js file
  161. let jsFile = ev.path.replace(/\.ts$/, ".js");
  162. let jsFileAsset = ToolCore.assetDatabase.getAssetByPath(jsFile);
  163. if (jsFileAsset) {
  164. console.log(`${this.name}: deleting corresponding .js file`);
  165. ToolCore.assetDatabase.deleteAsset(jsFileAsset);
  166. }
  167. }
  168. /**
  169. * Determine if we want to respond to resource renames
  170. * @param {EditorEvents.RenameResourceEvent} ev
  171. * @return {boolean} true if we care
  172. */
  173. canRename(ev: EditorEvents.RenameResourceEvent): boolean {
  174. return this.isValidFiletype(ev.path);
  175. }
  176. /**
  177. * Handle the rename. Should rename the corresponding .js file
  178. * @param {EditorEvents.RenameResourceEvent} ev
  179. */
  180. rename(ev: EditorEvents.RenameResourceEvent) {
  181. console.log(`${this.name}: received a rename resource event`);
  182. // notify the typescript language service that the file has been renamed
  183. this.languageService.renameProjectFile(ev.path, ev.newPath);
  184. // Rename the corresponding js file
  185. let jsFile = ev.path.replace(/\.ts$/, ".js");
  186. let jsFileNew = ev.newPath.replace(/\.ts$/, ".js");
  187. let jsFileAsset = ToolCore.assetDatabase.getAssetByPath(jsFile);
  188. if (jsFileAsset) {
  189. console.log(`${this.name}: renaming corresponding .js file`);
  190. jsFileAsset.rename(jsFileNew);
  191. }
  192. }
  193. /*** ProjectService implementation ****/
  194. /**
  195. * Called when the project is being unloaded to allow the typscript language service to reset
  196. */
  197. projectUnloaded() {
  198. // got an unload, we need to reset the language service
  199. console.log(`${this.name}: received a project unloaded event`);
  200. this.languageService.reset();
  201. }
  202. /**
  203. * Called when the project is being loaded to allow the typscript language service to reset and
  204. * possibly compile
  205. */
  206. projectLoaded(ev: EditorEvents.LoadProjectEvent) {
  207. // got a load, we need to reset the language service
  208. console.log(`${this.name}: received a project loaded event for project at ${ev.path}`);
  209. this.languageService.reset();
  210. this.loadProjectFiles();
  211. //TODO: do we want to run through and compile at this point?
  212. }
  213. /**
  214. * Called when the player is launched
  215. */
  216. playerStarted() {
  217. console.log(`${this.name}: received a player started event for project`);
  218. // Make sure the project has the latest files
  219. this.loadProjectFiles();
  220. if (this.fullCompile) {
  221. this.languageService.compile(null, {
  222. noEmitOnError: true,
  223. noImplicitAny: false,
  224. target: ts.ScriptTarget.ES5,
  225. module: ts.ModuleKind.CommonJS,
  226. noLib: true
  227. });
  228. }
  229. }
  230. }