Parcourir la source

Merge pull request #748 from shaddockh/TSH-ATOMIC-744

Typescript error annotations
JoshEngebretson il y a 9 ans
Parent
commit
0a2c96d963

+ 1 - 1
Script/AtomicWebViewEditor/clientExtensions/ClientExtensionEventNames.ts

@@ -26,7 +26,7 @@
 export default class ClientExtensionEventNames {
     static CodeLoadedEvent = "CodeLoadedEvent";
     static ConfigureEditorEvent = "ConfigureEditorEvent";
-    static ResourceSavedEvent = "ResourceSavedEvent";
+    static CodeSavedEvent = "CodeSavedEvent";
     static ResourceRenamedEvent = "ResourceRenamedEvent";
     static ResourceDeletedEvent = "ResourceDeletedEvent";
     static ProjectUnloadedEvent = "ProjectUnloadedEvent";

+ 3 - 2
Script/AtomicWebViewEditor/clientExtensions/ClientExtensionServices.ts

@@ -78,6 +78,7 @@ export class ExtensionServiceRegistry extends ServiceRegistry<Editor.ClientExten
         eventDispatcher.subscribeToEvent(ClientExtensionEventNames.ResourceRenamedEvent, (ev) => this.renameResource(ev));
         eventDispatcher.subscribeToEvent(ClientExtensionEventNames.ProjectUnloadedEvent, (ev) => this.projectUnloaded());
         eventDispatcher.subscribeToEvent(ClientExtensionEventNames.ResourceDeletedEvent, (ev) => this.deleteResource(ev));
+        eventDispatcher.subscribeToEvent(ClientExtensionEventNames.CodeSavedEvent, (ev) => this.saveCode(ev));
     }
 
     /**
@@ -98,10 +99,10 @@ export class ExtensionServiceRegistry extends ServiceRegistry<Editor.ClientExten
     }
 
     /**
-     * Called after a resource has been saved
+     * Called after code has been saved
      * @param  {Editor.EditorEvents.SaveResourceEvent} ev
      */
-    saveResource(ev: Editor.EditorEvents.SaveResourceEvent) {
+    saveCode(ev: Editor.EditorEvents.CodeSavedEvent) {
         // run through and find any services that can handle this.
         this.registeredServices.forEach((service) => {
             try {

+ 25 - 5
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/TypescriptLanguageExtension.ts

@@ -144,9 +144,26 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
             case WorkerProcessTypes.Alert:
                 alert(e.data.message);
                 break;
+            case WorkerProcessTypes.AnnotationsUpdated:
+                this.setAnnotations(e.data);
+                break;
         }
     }
 
+    /**
+     * Set annotations based upon issues reported by the typescript language service
+     * @param  {WorkerProcessTypes.GetAnnotationsResponseMessageData} event
+     */
+    setAnnotations(event: WorkerProcessTypes.GetAnnotationsResponseMessageData) {
+        // grab the existing annotations and filter out any TS annotations
+        let oldAnnotations = this.editor.session.getAnnotations().filter(ann => !ann.tsAnnotation);
+        this.editor.session.clearAnnotations();
+
+        // Mark these annotations as special
+        event.annotations.forEach(ann => ann.tsAnnotation = true);
+        this.editor.session.setAnnotations(oldAnnotations.concat(event.annotations));
+    }
+
     /**
      * Build/Attach to the shared web worker
      */
@@ -215,13 +232,16 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
      * Called once a resource has been saved
      * @param  {Editor.EditorEvents.SaveResourceEvent} ev
      */
-    save(ev: Editor.EditorEvents.SaveResourceEvent) {
-        if (this.isValidFiletype(ev.path)) {
-            console.log(`${this.name}: received a save resource event for ${ev.path}`);
+    save(ev: Editor.EditorEvents.CodeSavedEvent) {
+        if (this.isValidFiletype(ev.filename)) {
+            console.log(`${this.name}: received a save resource event for ${ev.filename}`);
 
             const message: WorkerProcessTypes.SaveMessageData = {
-                command: ClientExtensionEventNames.ResourceSavedEvent,
-                path: ev.path
+                command: ClientExtensionEventNames.CodeSavedEvent,
+                filename: ev.filename,
+                fileExt: ev.fileExt,
+                code: ev.code,
+                editor: null // cannot send editor across the boundary
             };
 
             this.worker.port.postMessage(message);

+ 23 - 9
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/workerprocess/TypescriptLanguageService.ts

@@ -163,6 +163,26 @@ export class TypescriptLanguageService {
     getProjectFiles(): string[] {
         return this.projectFiles;
     }
+
+    getPreEmitWarnings(filename: string, options?: ts.CompilerOptions) {
+        options = options || this.compilerOptions;
+
+        let allDiagnostics = this.compileFile(filename);
+        let results = [];
+
+        allDiagnostics.forEach(diagnostic => {
+            let lineChar = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
+            let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
+            results.push({
+                row: lineChar.line,
+                column: lineChar.character,
+                text: message,
+                type: diagnostic.category == 1 ? "error" : "warning"
+            });
+        });
+        return results;
+    }
+
     /**
      * Simply transpile the typescript file.  This is much faster and only checks for syntax errors
      * @param {string[]}           fileNames array of files to transpile
@@ -222,7 +242,7 @@ export class TypescriptLanguageService {
      * @param  {string}  a list of file names to compile
      * @param  {ts.CompilerOptions} options for the compiler
      */
-    compile(files: string[], options?: ts.CompilerOptions): void {
+    compile(files: string[], options?: ts.CompilerOptions): ts.Diagnostic[] {
         let start = new Date().getTime();
 
         options = options || this.compilerOptions;
@@ -242,18 +262,12 @@ export class TypescriptLanguageService {
         } else {
             // Only compile the files that are newly edited
             files.forEach(filename => {
-                // increment the version number since we changed
-                this.versionMap[filename].version++;
-                this.versionMap[filename].snapshot = null;
                 errors = errors.concat(this.compileFile(filename));
             });
         }
 
-        if (errors.length) {
-            this.logErrors(errors);
-        }
-
         console.log(`${this.name}: Compiling complete after ${new Date().getTime() - start} ms`);
+        return errors;
     }
 
     /**
@@ -285,7 +299,7 @@ export class TypescriptLanguageService {
         }
         let idx = this.projectFiles.indexOf(filepath);
         if (idx > -1) {
-            console.log(`Update project files array from ${filepath} to ${newpath}`)
+            console.log(`Update project files array from ${filepath} to ${newpath}`);
             this.projectFiles[idx] = newpath;
         }
     }

+ 19 - 8
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/workerprocess/TypescriptLanguageServiceWebWorker.ts

@@ -168,7 +168,7 @@ export default class TypescriptLanguageServiceWebWorker {
                 case WorkerProcessTypes.GetDocTooltip:
                     this.handleGetDocTooltip(port, e.data);
                     break;
-                case ClientExtensionEventNames.ResourceSavedEvent:
+                case ClientExtensionEventNames.CodeSavedEvent:
                     this.handleSave(port, e.data);
                     break;
                 case ClientExtensionEventNames.ResourceRenamedEvent:
@@ -180,6 +180,9 @@ export default class TypescriptLanguageServiceWebWorker {
                 case ClientExtensionEventNames.ProjectUnloadedEvent:
                     this.handleProjectUnloaded(port);
                     break;
+                case WorkerProcessTypes.GetAnnotations:
+                    this.handleGetAnnotations(port, e.data);
+                    break;
             }
 
         }, false);
@@ -247,7 +250,8 @@ export default class TypescriptLanguageServiceWebWorker {
     }) {
         port.postMessage({ command: WorkerProcessTypes.Message, message: "Hello " + eventData.sender + " (port #" + this.connections + ")" });
         this.loadProjectFiles().then(() => {
-            this.languageService.compile([eventData.filename]);
+            let diagnostics = this.languageService.compile([eventData.filename]);
+            this.handleGetAnnotations(port, eventData);
         });
     }
 
@@ -314,7 +318,7 @@ export default class TypescriptLanguageServiceWebWorker {
         if (details) {
             let docs = details.displayParts.map(part => part.text).join("");
             if (details.documentation) {
-                docs += "<br/" + details.documentation.map(part => part.text).join("");
+                docs += "<p>" + details.documentation.map(part => part.text).join("") + "</p>";
             }
 
             message.docHTML = docs;
@@ -323,16 +327,23 @@ export default class TypescriptLanguageServiceWebWorker {
         port.postMessage(message);
     }
 
+    handleGetAnnotations(port: MessagePort, eventData: WorkerProcessTypes.GetAnnotationsMessageData) {
+        let message: WorkerProcessTypes.GetAnnotationsResponseMessageData = {
+            command: WorkerProcessTypes.AnnotationsUpdated,
+            annotations: this.languageService.getPreEmitWarnings(eventData.filename)
+        };
+
+        port.postMessage(message);
+    }
+
     /**
-     * Called when the file has been saved.
+     * Called when the file has been saved.  This will also send back annotations to the caller
      * @param  {MessagePort} port
      * @param  {WorkerProcessCommands.SaveMessageData} eventData
      */
     handleSave(port: MessagePort, eventData: WorkerProcessTypes.SaveMessageData) {
-        // let's reload the file
-        getFileResource(eventData.path).then((code: string) => {
-            this.languageService.updateProjectFile(eventData.path, code);
-        });
+        this.languageService.updateProjectFile(eventData.filename, eventData.code);
+        this.handleGetAnnotations(port, eventData);
     }
 
     /**

+ 11 - 4
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/workerprocess/workerProcessTypes.ts

@@ -28,6 +28,10 @@ export interface WorkerProcessMessageData {
     command: string;
 }
 
+export interface SaveMessageData extends WorkerProcessMessageData, Editor.EditorEvents.CodeSavedEvent { }
+export interface DeleteMessageData extends WorkerProcessMessageData, Editor.EditorEvents.DeleteResourceEvent {}
+export interface RenameMessageData extends WorkerProcessMessageData, Editor.EditorEvents.RenameResourceEvent {}
+
 export const GetCompletions = "COMPLETIONS";
 export const CompletionResponse = "COMPLETION_RESPONSE";
 export interface WordCompletion {
@@ -65,11 +69,14 @@ export interface GetDocTooltipResponseMessageData extends WorkerProcessMessageDa
     docHTML?: string;
 }
 
+export const GetAnnotations = "ANNOTATIONS";
+export const AnnotationsUpdated = "ANNOTATIONS_RESPONSE";
+export interface GetAnnotationsMessageData extends SaveMessageData {};
+export interface GetAnnotationsResponseMessageData extends WorkerProcessMessageData {
+    annotations: any[];
+}
+
 export const Connect = "HELO";
 export const Disconnect = "CLOSE";
 export const Message = "MESSAGE";
 export const Alert = "ALERT";
-
-export interface SaveMessageData extends WorkerProcessMessageData, Editor.EditorEvents.SaveResourceEvent { }
-export interface DeleteMessageData extends WorkerProcessMessageData, Editor.EditorEvents.DeleteResourceEvent {}
-export interface RenameMessageData extends WorkerProcessMessageData, Editor.EditorEvents.RenameResourceEvent {}

+ 16 - 0
Script/AtomicWebViewEditor/editor/editorCommands.ts

@@ -107,3 +107,19 @@ export function resourceDeleted(path: string) {
     };
     serviceLocator.sendEvent(ClientExtensionEventNames.ResourceDeletedEvent, data);
 }
+
+/**
+ * Called when a resource is saved
+ * @param  {string} path
+ * @param {string} fileExt
+ * @param {string} contents
+ */
+export function codeSaved(path: string, fileExt: string, contents: string) {
+    let data:Editor.EditorEvents.CodeSavedEvent = {
+        filename: path,
+        fileExt: fileExt,
+        editor: editor,
+        code: contents
+    };
+    serviceLocator.sendEvent(ClientExtensionEventNames.CodeSavedEvent, data);
+}

+ 18 - 4
Script/AtomicWebViewEditor/interop.ts

@@ -62,6 +62,9 @@ function atomicQueryPromise(message: any): Promise<{}> {
 export default class HostInteropType {
 
     private static _inst: HostInteropType = null;
+    private fileName: string = null;
+    private fileExt: string = null;
+
     static getInstance(): HostInteropType {
         if (HostInteropType._inst == null) {
             HostInteropType._inst = new HostInteropType();
@@ -90,9 +93,13 @@ export default class HostInteropType {
      */
     loadCode(codeUrl: string) {
         console.log("Load Code called for :" + codeUrl);
-        const fileExt = codeUrl.split(".").pop();
+        const fileExt = codeUrl.indexOf(".") != -1 ? codeUrl.split(".").pop() : "";
         const filename = codeUrl.replace("atomic://", "");
 
+        // Keep track of our filename
+        this.fileName = filename;
+        this.fileExt = fileExt;
+
         // go ahead and set the theme prior to pulling the file across
         editorCommands.configure(fileExt, filename);
 
@@ -108,10 +115,13 @@ export default class HostInteropType {
      * Save the contents of the editor
      * @return {Promise}
      */
-    saveCode(): Promise<{}> {
+    saveCode(): Promise<any> {
+        let source = editorCommands.getSourceText();
         return atomicQueryPromise({
             message: HostInteropType.EDITOR_SAVE_CODE,
-            payload: editorCommands.getSourceText()
+            payload: source
+        }).then(() => {
+            editorCommands.codeSaved(this.fileName, this.fileExt, source);
         });
     }
 
@@ -121,11 +131,14 @@ export default class HostInteropType {
      * @param  {string} fileContents
      * @return {Promise}
      */
-    saveFile(filename: string, fileContents: string): Promise<{}> {
+    saveFile(filename: string, fileContents: string): Promise<any> {
+        const fileExt = filename.indexOf(".") != -1 ? filename.split(".").pop() : "";
         return atomicQueryPromise({
             message: HostInteropType.EDITOR_SAVE_FILE,
             filename: filename,
             payload: fileContents
+        }).then(() => {
+            editorCommands.codeSaved(filename, fileExt, fileContents);
         });
     }
 
@@ -189,6 +202,7 @@ export default class HostInteropType {
      * @param  {string} newPath
      */
     resourceRenamed(path: string, newPath: string) {
+        this.fileName = newPath;
         editorCommands.resourceRenamed(path, newPath);
     }
 

+ 5 - 1
Script/TypeScript/EditorWork.d.ts

@@ -76,6 +76,10 @@ declare module Editor.EditorEvents {
         code: string;
     }
 
+    export interface CodeSavedEvent extends EditorFileEvent {
+        code: string;
+    }
+
     export interface EditorCloseResourceEvent {
 
         editor: Editor.ResourceEditor;
@@ -252,7 +256,7 @@ declare module Editor.ClientExtensions {
     export interface WebViewService extends Editor.Extensions.EditorService {
         configureEditor?(ev: EditorEvents.EditorFileEvent);
         codeLoaded?(ev: EditorEvents.CodeLoadedEvent);
-        save?(ev: EditorEvents.SaveResourceEvent);
+        save?(ev: EditorEvents.CodeSavedEvent);
         delete?(ev: EditorEvents.DeleteResourceEvent);
         rename?(ev: EditorEvents.RenameResourceEvent);
         projectUnloaded?();