Browse Source

work on executing a full compile on save

Shaddock Heath 10 years ago
parent
commit
329bb5ce53

+ 6 - 2
Script/AtomicEditor/extensionServices/EditorExtensionServices.ts

@@ -7,7 +7,7 @@
 
 import * as EditorEvents from "../editor/EditorEvents";
 import TypescriptLanguageService from "./resourceServices/TypescriptLanguageService";
-
+import * as EditorUI from "../ui/EditorUI";
 /**
  * Base interface for any editor services.
  */
@@ -59,7 +59,11 @@ class ResourceServiceRegistry extends ServiceRegistry<ResourceService> {
         this.registeredServices.forEach((service) => {
             // Verify that the service contains the appropriate methods and that it can save
             if (service.canSave && service.save && service.canSave(ev)) {
-                service.save(ev);
+                try {
+                    service.save(ev);
+                } catch (e) {
+                    EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}\n ${e}`);
+                }
             }
         });
     }

+ 136 - 74
Script/AtomicEditor/extensionServices/resourceServices/TypescriptLanguageService.ts

@@ -4,6 +4,9 @@
 // Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
 // license information: https://github.com/AtomicGameEngine/AtomicGameEngine
 //
+
+// 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
+
 import * as ExtensionServices from "../EditorExtensionServices";
 import * as EditorEvents from "../../editor/EditorEvents";
 import * as ts from "modules/typescript";
@@ -23,31 +26,46 @@ export default class TypescriptLanguageService implements ExtensionServices.Reso
      * Perform a full compile on save, or just transpile the current file
      * @type {boolean}
      */
-    fullCompile: boolean = false;
+    fullCompile: boolean = true;
 
     /**
      * used by the compile to build a registery of all of the project files
-     * @return {[type]} [description]
+     * @param {string[]} files optional list of files to refresh.  If not provided, then all files will be reloaded
      */
-    refreshProjectFiles() {
-        if (!this.projectFiles) {
+    refreshProjectFiles(files?: string[]) {
+        if (!this.projectFiles || !files) {
+            // First time in, let's index the entire project
             this.projectFiles = [];
 
+            // First we need to load in a copy of the lib.core.d.ts that is necessary for the hosted typescript compiler
+            this.projectFiles.push(Atomic.addTrailingSlash(Atomic.addTrailingSlash(ToolCore.toolEnvironment.toolDataDir) + "TypeScriptSupport") + "lib.core.d.ts");
+
+            // Load up a copy of the duktape.d.ts
+            this.projectFiles.push(Atomic.addTrailingSlash(Atomic.addTrailingSlash(ToolCore.toolEnvironment.toolDataDir) + "TypeScriptSupport") + "duktape.d.ts");
+
             //scan all the files in the project
             Atomic.fileSystem.scanDir(ToolCore.toolSystem.project.resourcePath, "*.ts", Atomic.SCAN_FILES, true).forEach(filename => {
                 this.projectFiles.push(Atomic.addTrailingSlash(ToolCore.toolSystem.project.resourcePath) + filename);
             });
 
-            Atomic.fileSystem.scanDir(ToolCore.toolSystem.project.projectPath, "*.d.ts", Atomic.SCAN_FILES, true).forEach(filename => {
-                if (filename.indexOf("node_modules") == -1) {
-                    this.projectFiles.push(Atomic.addTrailingSlash(ToolCore.toolSystem.project.projectPath) + filename);
-                }
+            // Look in a 'typings' directory for any typescript definition files
+            const typingsDir = Atomic.addTrailingSlash(ToolCore.toolSystem.project.projectPath) + "typings";
+            Atomic.fileSystem.scanDir(typingsDir, "*.d.ts", Atomic.SCAN_FILES, true).forEach(filename => {
+                this.projectFiles.push(Atomic.addTrailingSlash(typingsDir) + filename);
             });
 
             // initialize the list of files
             this.projectFiles.forEach(fileName => {
                 this.versionMap[fileName] = { version: 0 };
             });
+        } else {
+            //We already have a project, let's just add the files that are being saved if they are new
+            files.forEach((file) => {
+                if (!this.projectFiles.indexOf(file)) {
+                    this.versionMap[file] = { version: 0 };
+                    this.projectFiles.push(file);
+                }
+            });
         }
     }
 
@@ -60,105 +78,148 @@ export default class TypescriptLanguageService implements ExtensionServices.Reso
         fileNames.forEach((fileName) => {
             console.log(`${this.name}:  Transpiling ${fileName}`);
             let script = new Atomic.File(fileName, Atomic.FILE_READ);
-            let diagnostics: ts.Diagnostic[] = [];
-            let result = ts.transpile(script.readText(), options, fileName, diagnostics);
-            this.logErrors(fileName, diagnostics);
-
-            if (diagnostics.length == 0) {
-                let output = new Atomic.File(fileName.replace(".ts", ".js"), Atomic.FILE_WRITE);
-                output.writeString(result);
-                output.flush();
-                output.close();
+            try {
+                let diagnostics: ts.Diagnostic[] = [];
+                let result = ts.transpile(script.readText(), options, fileName, diagnostics);
+                if (diagnostics.length) {
+                    this.logErrors(diagnostics);
+                }
+
+                if (diagnostics.length == 0) {
+                    let output = new Atomic.File(fileName.replace(".ts", ".js"), Atomic.FILE_WRITE);
+                    try {
+                        output.writeString(result);
+                        output.flush();
+                    } finally {
+                        output.close();
+                    }
+                }
+            } finally {
+                script.close();
             }
-            script.close();
         });
     }
 
     /**
      * Compile the provided file to javascript with full type checking etc
-     * @param  {string}  filename the file name and path to compile
-     * @return {boolean}          true on success
+     * @param  {string}  a list of file names to compile
+     * @param  {ts.CompilerOptions} options for the compiler
      */
-    compile(fileNames: string[], options: ts.CompilerOptions): void {
+    compile(files: string[], options: ts.CompilerOptions): void {
 
         //scan all the files in the project
-        this.refreshProjectFiles();
-
-        // Create the language service host to allow the LS to communicate with the host
-        const servicesHost: ts.LanguageServiceHost = {
-            getScriptFileNames: () => this.projectFiles,
-            getScriptVersion: (fileName) => this.versionMap[fileName] && this.versionMap[fileName].version.toString(),
-            getScriptSnapshot: (fileName) => {
-                if (!Atomic.fileSystem.exists(fileName)) {
-                    console.log(`${fileName} does not exist`);
-                    return undefined;
-                }
-                console.log(`${fileName} exists`);
-                let script = new Atomic.File(fileName, Atomic.FILE_READ);
-                return ts.ScriptSnapshot.fromString(script.readText());
-            },
-            getCurrentDirectory: () => ToolCore.toolSystem.project.resourcePath,
-            getCompilationSettings: () => options,
-            getDefaultLibFileName: (options) => undefined
-        };
-
-        // Create the language service files
-        this.languageService = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
-
-        fileNames.forEach(fileName => {
-            try {
-                this.emitFile(fileName);
-            } catch (err) {
-                console.log(`${this.name}: problem encountered compiling ${fileName}: ${err}`);
-                console.log(err.stack);
-            }
-        });
+        this.refreshProjectFiles(files);
+        let errors: ts.Diagnostic[] = [];
+
+        if (!this.languageService) {
+            // This is the first time in.  Need to create a language service
+
+            // Create the language service host to allow the LS to communicate with the host
+            const servicesHost: ts.LanguageServiceHost = {
+                getScriptFileNames: () => this.projectFiles,
+                getScriptVersion: (fileName) => this.versionMap[fileName] && this.versionMap[fileName].version.toString(),
+                getScriptSnapshot: (fileName) => {
+                    if (!Atomic.fileSystem.exists(fileName)) {
+                        return undefined;
+                    }
+                    let script = new Atomic.File(fileName, Atomic.FILE_READ);
+                    try {
+                        return ts.ScriptSnapshot.fromString(script.readText());
+                    } finally {
+                        script.close();
+                    }
+                },
+                getCurrentDirectory: () => ToolCore.toolSystem.project.resourcePath,
+                getCompilationSettings: () => options,
+                getDefaultLibFileName: (options) => undefined
+            };
+
+            // Create the language service files
+            this.languageService = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
+
+            // This is the first time in, let's compile all the files
+            this.projectFiles.forEach(filename => {
+                errors = errors.concat(this.compileFile(filename));
+            });
+        } else {
+            // Only compile the files that are newly edited
+            files.forEach(filename => {
+                // increment the version number since we changed
+                this.versionMap[filename].version++;
+                errors = errors.concat(this.compileFile(filename));
+            });
+        }
+
+        if (errors.length) {
+            this.logErrors(errors);
+        }
     }
 
     /**
-     * writes out a file from the compile process
-     * @param  {string} fileName [description]
-     * @return {[type]}          [description]
+     * Compile an individual file
+     * @param  {string} filename the file to compile
+     * @return {[ts.Diagnostic]} a list of any errors
      */
-    emitFile(fileName: string) {
-        let output = this.languageService.getEmitOutput(fileName);
-
-        if (!output.emitSkipped) {
-            console.log(`${this.name}: Emitting ${fileName}`);
+    compileFile(filename: string): ts.Diagnostic[] {
+        console.log(`${this.name}: Compiling version ${this.versionMap[filename].version} of ${filename}`);
+        //if (!filename.match("\.d\.ts$")) {
+        try {
+            return this.emitFile(filename);
+        } catch (err) {
+            console.log(`${this.name}: problem encountered compiling ${filename}: ${err}`);
+            return [];
+            // console.log(err.stack);
         }
-        else {
-            console.log(`${this.name}: Emitting ${fileName} failed`);
-            let allDiagnostics = this.languageService.getCompilerOptionsDiagnostics()
-                .concat(this.languageService.getSyntacticDiagnostics(fileName))
-                .concat(this.languageService.getSemanticDiagnostics(fileName));
-            this.logErrors(fileName, allDiagnostics);
+        //}
+    }
+
+    /**
+     * writes out a file from the compile process
+     * @param  {string} filename [description]
+     * @return {[ts.Diagnostic]} a list of any errors
+     */
+    emitFile(filename: string): ts.Diagnostic[] {
+        let output = this.languageService.getEmitOutput(filename);
+        let allDiagnostics: ts.Diagnostic[] = [];
+        if (output.emitSkipped) {
+            console.log(`${this.name}: Failure Emitting ${filename}`);
+            allDiagnostics = this.languageService.getCompilerOptionsDiagnostics()
+                .concat(this.languageService.getSyntacticDiagnostics(filename))
+                .concat(this.languageService.getSemanticDiagnostics(filename));
         }
 
         output.outputFiles.forEach(o => {
-            console.log(`${this.name}: Writing out ${o.name}`);
             let script = new Atomic.File(o.name, Atomic.FILE_WRITE);
-            script.writeString(o.text);
-            // fs.writeFileSync(o.name, o.text, "utf8");
+            try {
+                script.writeString(o.text);
+                script.flush();
+            } finally {
+                script.close();
+            }
         });
+        return allDiagnostics;
     }
 
     /**
      * Logs errors from the diagnostics returned from the compile/transpile process
-     * @param  {string} fileName [description]
+     * @param {ts.Diagnostic} diagnostics information about the errors
      * @return {[type]}          [description]
      */
-    logErrors(fileName: string, diagnostics: ts.Diagnostic[]) {
+    logErrors(diagnostics: ts.Diagnostic[]) {
+        let msg = [];
 
         diagnostics.forEach(diagnostic => {
             let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
             if (diagnostic.file) {
                 let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
-                console.log(`${this.name}:  Error ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
+                msg.push(`${this.name}:  Error ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
             }
             else {
-                console.log(`${this.name}  Error: ${message}`);
+                msg.push(`${this.name}  Error: ${message}`);
             }
         });
+        console.log(`TypeScript Errors:\n${msg.join("\n") }`);
+        throw new Error(`TypeScript Errors:\n${msg.join("\n") }`);
     }
 
     /**
@@ -177,7 +238,7 @@ export default class TypescriptLanguageService implements ExtensionServices.Reso
         console.log(`${this.name}: received a save resource event for ${ev.path}`);
         if (this.fullCompile) {
             this.compile([ev.path], {
-                noEmitOnError: false,
+                noEmitOnError: true,
                 noImplicitAny: false,
                 target: ts.ScriptTarget.ES5,
                 module: ts.ModuleKind.CommonJS,
@@ -201,6 +262,7 @@ export default class TypescriptLanguageService implements ExtensionServices.Reso
      */
     canSave(ev: EditorEvents.SaveResourceEvent): boolean {
         const ext = Atomic.getExtension(ev.path);
+        //if (ext == ".ts" && !ev.path.match("\.d\.ts$")) {
         if (ext == ".ts") {
             return true;
         }