Browse Source

Work on being able to compile typescript files when not all the pieces are located under Resources (ie d.ts files located in a project/typings folder). Also pull in an existing tsconfig.json if it exists in the root of the project.

Shaddock Heath 9 years ago
parent
commit
d8f936ef43

+ 52 - 25
Script/AtomicEditor/hostExtensions/languageExtensions/TypscriptLanguageExtension.ts

@@ -59,23 +59,52 @@ export default class TypescriptLanguageExtension implements Editor.HostExtension
      * and generate a file list
      */
     private buildTsConfig(): any {
-        let projectFiles: Array<string> = [];
-
-        //scan all the files in the project for any typescript files so we can determine if this is a typescript project
-        Atomic.fileSystem.scanDir(ToolCore.toolSystem.project.resourcePath, "*.ts", Atomic.SCAN_FILES, true).forEach(filename => {
-            projectFiles.push(Atomic.addTrailingSlash(ToolCore.toolSystem.project.resourcePath) + filename);
-            this.isTypescriptProject = true;
-        });
-
         // only build out a tsconfig.atomic if we actually have typescript files in the project
         if (this.isTypescriptProject) {
+            let projectFiles: Array<string> = [];
+
+            //scan all the files in the project for any typescript files and add them to the project
+            Atomic.fileSystem.scanDir(ToolCore.toolSystem.project.resourcePath, "*.ts", Atomic.SCAN_FILES, true).forEach(filename => {
+                projectFiles.push(Atomic.addTrailingSlash(ToolCore.toolSystem.project.resourcePath) + filename);
+            });
+
             // First we need to load in a copy of the lib.core.d.ts that is necessary for the hosted typescript compiler
             projectFiles.push(Atomic.addTrailingSlash(Atomic.addTrailingSlash(ToolCore.toolEnvironment.toolDataDir) + "TypeScriptSupport") + "lib.core.d.ts");
 
+            const slashedProjectPath = Atomic.addTrailingSlash(ToolCore.toolSystem.project.projectPath);
+
+            const tsconfigFn = Atomic.addTrailingSlash(ToolCore.toolSystem.project.projectPath) + "tsconfig.json";
+            // Let's look for a tsconfig.json file in the project root and add any additional files
+            if (Atomic.fileSystem.fileExists(tsconfigFn)) {
+                // load up the tsconfig file and parse out the files block and compare it to what we have
+                // in resources
+                const file = new Atomic.File(tsconfigFn, Atomic.FILE_READ);
+                try {
+                    const savedTsConfig = JSON.parse(file.readText());
+                    if (savedTsConfig["files"]) {
+                        savedTsConfig["files"].forEach((file: string) => {
+                            let newFile = Atomic.addTrailingSlash(ToolCore.toolSystem.project.projectPath) + file;
+                            let exists = false;
+                            if (Atomic.fileSystem.fileExists(newFile)) {
+                                exists = true;
+                                file = newFile;
+                            } else if (Atomic.fileSystem.exists(file)) {
+                                exists = true;
+                            }
+                            if (exists && projectFiles.indexOf(file) == -1) {
+                                projectFiles.push(file);
+                            }
+                        });
+                    }
+                } finally {
+                    file.close();
+                }
+            };
+
             // Then see if we have a copy of Atomic.d.ts in the project directory.  If we don't then we should load it up from the tool environment
             let found = false;
             projectFiles.forEach((file) => {
-                if (file.indexOf("Atomic.d.ts") != -1) {
+                if (file.toLowerCase().indexOf("atomic.d.ts") != -1) {
                     found = true;
                 }
             });
@@ -85,20 +114,9 @@ export default class TypescriptLanguageExtension implements Editor.HostExtension
                 projectFiles.push(Atomic.addTrailingSlash(Atomic.addTrailingSlash(ToolCore.toolEnvironment.toolDataDir) + "TypeScriptSupport") + "Atomic.d.ts");
             }
 
-            let files = projectFiles.map((f: string) => {
-                if (f.indexOf(ToolCore.toolSystem.project.resourcePath) != -1) {
-                    // if we are in the resources directory, just pass back the path from resources down
-                    return f.replace(Atomic.addTrailingSlash(ToolCore.toolSystem.project.projectPath), "");
-                } else {
-                    // otherwise return the full path
-                    return f;
-                }
-            });
-
             let tsConfig = {
-                files: files
+                files: projectFiles
             };
-
             return tsConfig;
         } else {
             return {
@@ -196,10 +214,16 @@ export default class TypescriptLanguageExtension implements Editor.HostExtension
      */
     projectLoaded(ev: Editor.EditorEvents.LoadProjectEvent) {
         // got a load, we need to reset the language service
-        console.log(`${this.name}: received a project loaded event for project at ${ev.path}`);
-        this.setTsConfigOnWebView(this.buildTsConfig());
+        // console.log(`${this.name}: received a project loaded event for project at ${ev.path}`);
+
+        this.isTypescriptProject = false;
+        //scan all the files in the project for any typescript files so we can determine if this is a typescript project
+        Atomic.fileSystem.scanDir(ToolCore.toolSystem.project.resourcePath, "*.ts", Atomic.SCAN_FILES, true).forEach(filename => {
+            this.isTypescriptProject = true;
+        });
 
         if (this.isTypescriptProject) {
+            this.setTsConfigOnWebView(this.buildTsConfig());
             const isCompileOnSave = this.serviceRegistry.projectServices.getUserPreference(this.name, "CompileOnSave", false);
 
             // Build the menu - First build up an empty menu then manually add the items so we can have reference to them
@@ -311,11 +335,14 @@ export default class TypescriptLanguageExtension implements Editor.HostExtension
             // If we are compiling the lib.d.ts or some other built-in library and it was successful, then
             // we really don't need to display that result since it's just noise.  Only display it if it fails
             if (result.type == "success") {
-                return result.file.indexOf(resourceDir) == 0;
+                return result.file.indexOf(ToolCore.toolSystem.project.projectPath) == 0;
             }
             return true;
         }).map(result => {
-            let message = `<color #888888>${result.file}: </color>`;
+
+            // Clean up the path for display
+            let file = result.file.replace(ToolCore.toolSystem.project.projectPath, "");
+            let message = `<color #888888>${file}: </color>`;
             if (result.type == "success") {
                 message += `<color #00ff00>${result.text}</color>`;
             } else {

+ 3 - 2
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/TypescriptLanguageExtension.ts

@@ -338,8 +338,9 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
      * Tell the language service to perform a full compile
      */
     doFullCompile() {
-        const message: WorkerProcessTypes.WorkerProcessMessageData = {
-            command: WorkerProcessTypes.DoFullCompile
+        const message: WorkerProcessTypes.FullCompileMessageData = {
+            command: WorkerProcessTypes.DoFullCompile,
+            tsConfig: this.getTsConfig()
         };
         this.worker.port.postMessage(message);
     }

+ 21 - 7
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/workerprocess/TypescriptLanguageService.ts

@@ -142,13 +142,13 @@ export class TypescriptLanguageService {
     /**
      * Updates the internal file representation.
      * @param  {string} filename name of the file
-     * @param  {string} fileContents optional contents of the file.  If not provided, the file system object will be queried
+     * @param  {string} fileContents optional contents of the file.
      * @return {ts.SourceFile}
      */
-    updateProjectFile(filename: string, fileContents?: string): ts.SourceFile {
-        console.log("Updated project file: " + filename);
+    updateProjectFile(filename: string, fileContents: string): ts.SourceFile {
         this.versionMap[filename].version++;
-        this.versionMap[filename].snapshot = ts.ScriptSnapshot.fromString(fileContents || this.fs.getFile(filename));
+        this.versionMap[filename].snapshot = ts.ScriptSnapshot.fromString(fileContents);
+
         return this.documentRegistry.updateDocument(
             filename,
             this.compilerOptions,
@@ -156,6 +156,19 @@ export class TypescriptLanguageService {
             this.versionMap[filename].version.toString());
     }
 
+    /**
+     * Updates the internal file version number
+     * @param  {string} filename name of the file
+     * @return {ts.SourceFile}
+     */
+    updateProjectFileVersionNumber(filename: string): ts.SourceFile {
+        this.versionMap[filename].version++;
+        return this.documentRegistry.updateDocument(
+            filename,
+            this.compilerOptions,
+            this.versionMap[filename].snapshot,
+            this.versionMap[filename].version.toString());
+    }
     /**
      * Returns the list of project files
      * @return {string[]}
@@ -237,13 +250,14 @@ export class TypescriptLanguageService {
     getCompletionEntryDetails(filename: string, pos: number, entryname: string): ts.CompletionEntryDetails {
         return this.languageService.getCompletionEntryDetails(filename, pos, entryname);
     }
+    
     /**
      * Compile the provided file to javascript with full type checking etc
      * @param  {string}  a list of file names to compile
      * @param  {ts.CompilerOptions} options for the compiler
      * @param  {function} optional callback which will be called for every file compiled and will provide any errors
      */
-    compile(files: string[], options?: ts.CompilerOptions, progress?: (filename:string, errors: ts.Diagnostic[]) => void): ts.Diagnostic[] {
+    compile(files: string[], options?: ts.CompilerOptions, progress?: (filename: string, errors: ts.Diagnostic[]) => void): ts.Diagnostic[] {
         let start = new Date().getTime();
         options = options || this.compilerOptions;
 
@@ -377,8 +391,8 @@ export class TypescriptLanguageService {
                 msg.push(`${this.name}  Error: ${message}`);
             }
         });
-        console.log(`TypeScript Errors:\n${msg.join("\n") }`);
-        throw new Error(`TypeScript Errors:\n${msg.join("\n") }`);
+        console.log(`TypeScript Errors:\n${msg.join("\n")}`);
+        throw new Error(`TypeScript Errors:\n${msg.join("\n")}`);
     }
 
 }

+ 95 - 64
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/workerprocess/TypescriptLanguageServiceWebWorker.ts

@@ -174,37 +174,41 @@ export default class TypescriptLanguageServiceWebWorker {
         this.connections++;
 
         port.addEventListener("message", (e: WorkerProcessTypes.WorkerProcessMessage<any>) => {
-            switch (e.data.command) {
-                case WorkerProcessTypes.Connect:
-                    this.handleHELO(port, e.data);
-                    break;
-                case WorkerProcessTypes.Disconnect:
-                    this.handleCLOSE(port, e.data);
-                    break;
-                case WorkerProcessTypes.GetCompletions:
-                    this.handleGetCompletions(port, e.data);
-                    break;
-                case WorkerProcessTypes.GetDocTooltip:
-                    this.handleGetDocTooltip(port, e.data);
-                    break;
-                case ClientExtensionEventNames.CodeSavedEvent:
-                    this.handleSave(port, e.data);
-                    break;
-                case ClientExtensionEventNames.ResourceRenamedEvent:
-                    this.handleRename(port, e.data);
-                    break;
-                case ClientExtensionEventNames.ResourceDeletedEvent:
-                    this.handleDelete(port, e.data);
-                    break;
-                case WorkerProcessTypes.GetAnnotations:
-                    this.handleGetAnnotations(port, e.data);
-                    break;
-                case WorkerProcessTypes.SetPreferences:
-                    this.setPreferences(port, e.data);
-                    break;
-                case WorkerProcessTypes.DoFullCompile:
-                    this.doFullCompile(port);
-                    break;
+            try {
+                switch (e.data.command) {
+                    case WorkerProcessTypes.Connect:
+                        this.handleHELO(port, e.data);
+                        break;
+                    case WorkerProcessTypes.Disconnect:
+                        this.handleCLOSE(port, e.data);
+                        break;
+                    case WorkerProcessTypes.GetCompletions:
+                        this.handleGetCompletions(port, e.data);
+                        break;
+                    case WorkerProcessTypes.GetDocTooltip:
+                        this.handleGetDocTooltip(port, e.data);
+                        break;
+                    case ClientExtensionEventNames.CodeSavedEvent:
+                        this.handleSave(port, e.data);
+                        break;
+                    case ClientExtensionEventNames.ResourceRenamedEvent:
+                        this.handleRename(port, e.data);
+                        break;
+                    case ClientExtensionEventNames.ResourceDeletedEvent:
+                        this.handleDelete(port, e.data);
+                        break;
+                    case WorkerProcessTypes.GetAnnotations:
+                        this.handleGetAnnotations(port, e.data);
+                        break;
+                    case WorkerProcessTypes.SetPreferences:
+                        this.setPreferences(port, e.data);
+                        break;
+                    case WorkerProcessTypes.DoFullCompile:
+                        this.doFullCompile(port, e.data);
+                        break;
+                }
+            } catch (e) {
+                port.postMessage({ command: WorkerProcessTypes.Message, message: `Error in TypescriptLanguageServiceWebWorker: ${e}\n${e.stack}` });
             }
 
         }, false);
@@ -228,34 +232,31 @@ export default class TypescriptLanguageServiceWebWorker {
     private loadProjectFiles() {
         // Let's query the backend and get a list of the current files
         // and delete any that may be been removed and sync up
-        return getFileResource("resources/tsconfig.atomic").then((jsonTsConfig: string) => {
-            let promises: PromiseLike<void>[] = [];
+        let promises: PromiseLike<void>[] = [];
 
-            if (this.tsConfig.compilerOptions) {
-                this.languageService.compilerOptions = this.tsConfig.compilerOptions;
-            };
+        if (this.tsConfig.compilerOptions) {
+            this.languageService.compilerOptions = this.tsConfig.compilerOptions;
+        };
 
-            let existingFiles = this.languageService.getProjectFiles();
+        let existingFiles = this.languageService.getProjectFiles();
 
-            // see if anything was deleted
-            existingFiles.forEach((f) => {
-                if (this.tsConfig.files.indexOf(f) == -1) {
-                    this.languageService.deleteProjectFile(f);
-                }
-            });
+        // see if anything was deleted
+        existingFiles.forEach((f) => {
+            if (this.tsConfig.files.indexOf(f) == -1) {
+                this.languageService.deleteProjectFile(f);
+            }
+        });
 
-            // load up any new files that may have been added
-            this.tsConfig.files.forEach((f) => {
-                if (existingFiles.indexOf(f) == -1) {
-                    promises.push(getFileResource(f).then((code: string) => {
-                        this.languageService.addProjectFile(f, code);
-                    }));
-                }
-            });
-            return Promise.all(promises);
-        }).then(() => {
-            // Let's seed the compiler state
-            // this.languageService.compile([this.filename]);
+        // load up any new files that may have been added
+        this.tsConfig.files.forEach((f) => {
+            if (existingFiles.indexOf(f) == -1) {
+                promises.push(getFileResource(f).then((code: string) => {
+                    this.languageService.addProjectFile(f, code);
+                }));
+            }
+        });
+
+        return Promise.all(promises).then(() => {
             this.projectLoaded = true;
         });
     }
@@ -270,10 +271,16 @@ export default class TypescriptLanguageServiceWebWorker {
         filename: string,
         tsConfig: any
     }) {
-        // port.postMessage({ command: WorkerProcessTypes.Message, message: "Hello " + eventData.sender + " (port #" + this.connections + ")" });
+        //port.postMessage({ command: WorkerProcessTypes.Message, message: "Hello " + eventData.sender + " (port #" + this.connections + ")" });
         this.tsConfig = eventData.tsConfig;
+
+        // Check to see if the file coming in is already in the
+        // tsconfig.  The file coming in won't have a full path
+        // so, compare the ends
+        const fn = this.resolvePartialFilename(eventData.filename);
+
         this.loadProjectFiles().then(() => {
-            let diagnostics = this.languageService.compile([eventData.filename]);
+            let diagnostics = this.languageService.compile([fn]);
             this.handleGetAnnotations(port, eventData);
         });
     }
@@ -290,16 +297,29 @@ export default class TypescriptLanguageServiceWebWorker {
         }
     }
 
+    /**
+     * Look up the filename in the tsconfig and see if it exists.  The filename
+     * may contain a partial path and the tsconfig contains full paths, so check
+     * the ends
+     * @param  {string} partial
+     * @return {string}
+     */
+    resolvePartialFilename(partial: string):string {
+        let result = this.tsConfig.files.find(fn => fn.endsWith(partial));
+        return result || partial;
+    }
     /**
      * Get completions
      * @param  {MessagePort} port
      * @param  {WorkerProcessCommands.GetCompletionsMessage} eventData
      */
     handleGetCompletions(port: MessagePort, eventData: WorkerProcessTypes.GetCompletionsMessageData) {
-        let sourceFile = this.languageService.updateProjectFile(eventData.filename, eventData.sourceText);
+        // filename may not include the entire path, so let's find it in the tsconfig
+        let filename = this.resolvePartialFilename(eventData.filename);
+        let sourceFile = this.languageService.updateProjectFile(filename, eventData.sourceText);
 
         let newpos = this.languageService.getPositionOfLineAndCharacter(sourceFile, eventData.pos.row, eventData.pos.column);
-        let completions = this.languageService.getCompletions(eventData.filename, newpos);
+        let completions = this.languageService.getCompletions(filename, newpos);
 
         let message: WorkerProcessTypes.GetCompletionsResponseMessageData = {
             command: WorkerProcessTypes.CompletionResponse,
@@ -336,7 +356,8 @@ export default class TypescriptLanguageServiceWebWorker {
         let message: WorkerProcessTypes.GetDocTooltipResponseMessageData = {
             command: WorkerProcessTypes.DocTooltipResponse
         };
-        const details = this.languageService.getCompletionEntryDetails(eventData.filename, eventData.pos, eventData.completionItem.caption);
+        let filename = this.resolvePartialFilename(eventData.filename);
+        const details = this.languageService.getCompletionEntryDetails(filename, eventData.pos, eventData.completionItem.caption);
         if (details) {
             let docs = details.displayParts.map(part => part.text).join("");
             if (details.documentation) {
@@ -350,9 +371,10 @@ export default class TypescriptLanguageServiceWebWorker {
     }
 
     handleGetAnnotations(port: MessagePort, eventData: WorkerProcessTypes.GetAnnotationsMessageData) {
+        let filename = this.resolvePartialFilename(eventData.filename);
         let message: WorkerProcessTypes.GetAnnotationsResponseMessageData = {
             command: WorkerProcessTypes.AnnotationsUpdated,
-            annotations: this.languageService.getPreEmitWarnings(eventData.filename)
+            annotations: this.languageService.getPreEmitWarnings(filename)
         };
 
         port.postMessage(message);
@@ -365,12 +387,14 @@ export default class TypescriptLanguageServiceWebWorker {
      */
     handleSave(port: MessagePort, eventData: WorkerProcessTypes.SaveMessageData) {
         this.tsConfig = eventData.tsConfig;
-        this.languageService.updateProjectFile(eventData.filename, eventData.code);
+        let filename = this.resolvePartialFilename(eventData.filename);
+
+        this.languageService.updateProjectFile(filename, eventData.code);
         this.handleGetAnnotations(port, eventData);
 
         if (this.options.compileOnSave) {
             this.fs.setCommunicationPort(port);
-            let results = this.languageService.compile([eventData.filename]);
+            let results = this.languageService.compile([filename]);
         }
     }
 
@@ -378,8 +402,15 @@ export default class TypescriptLanguageServiceWebWorker {
      * Perform a full compile of the typescript
      * @param  {MessagePort} port
      */
-    doFullCompile(port: MessagePort) {
+    doFullCompile(port: MessagePort, eventData: WorkerProcessTypes.FullCompileMessageData) {
+        this.tsConfig = eventData.tsConfig;
         this.fs.setCommunicationPort(port);
+
+        // update all the files
+        this.tsConfig.files.forEach(file => {
+            this.languageService.updateProjectFileVersionNumber(file);
+        });
+
         let results = [];
         this.languageService.compile([], this.languageService.compilerOptions, (filename, errors) => {
             if (errors.length > 0) {

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

@@ -28,13 +28,12 @@ export interface WorkerProcessMessageData {
     command: string;
 }
 
-export interface TSConfigAttached {
-    tsConfig: any;
+export interface SaveMessageData extends WorkerProcessMessageData, Editor.EditorEvents.CodeSavedEvent {
+    tsConfig: any
 }
 
-export interface SaveMessageData extends WorkerProcessMessageData, Editor.EditorEvents.CodeSavedEvent, TSConfigAttached { }
-export interface DeleteMessageData extends WorkerProcessMessageData, Editor.EditorEvents.DeleteResourceEvent {}
-export interface RenameMessageData extends WorkerProcessMessageData, Editor.EditorEvents.RenameResourceEvent {}
+export interface DeleteMessageData extends WorkerProcessMessageData, Editor.EditorEvents.DeleteResourceEvent { }
+export interface RenameMessageData extends WorkerProcessMessageData, Editor.EditorEvents.RenameResourceEvent { }
 
 export const SetPreferences = "SET_PREFERENCES";
 export interface SetPreferencesMessageData extends WorkerProcessMessageData {
@@ -82,14 +81,18 @@ export interface GetDocTooltipResponseMessageData extends WorkerProcessMessageDa
 
 export const GetAnnotations = "ANNOTATIONS";
 export const AnnotationsUpdated = "ANNOTATIONS_RESPONSE";
-export interface GetAnnotationsMessageData extends SaveMessageData {};
+export interface GetAnnotationsMessageData extends SaveMessageData { };
 export interface GetAnnotationsResponseMessageData extends WorkerProcessMessageData {
     annotations: any[];
 }
 
-export const DoFullCompile  = "DO_FULL_COMPILE";
+export const DoFullCompile = "DO_FULL_COMPILE";
+export interface FullCompileMessageData extends WorkerProcessMessageData {
+    tsConfig: any;
+};
+
 export const DisplayFullCompileResults = "DISPLAY_FULL_COMPILE_RESULTS";
-export interface FullCompileResultsMessageData extends GetAnnotationsResponseMessageData {}
+export interface FullCompileResultsMessageData extends GetAnnotationsResponseMessageData { }
 
 export const SaveFile = "SAVE_FILE";