|
@@ -4,6 +4,9 @@
|
|
|
// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
|
|
// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
|
|
|
// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
|
|
// 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 ExtensionServices from "../EditorExtensionServices";
|
|
|
import * as EditorEvents from "../../editor/EditorEvents";
|
|
import * as EditorEvents from "../../editor/EditorEvents";
|
|
|
import * as ts from "modules/typescript";
|
|
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
|
|
* Perform a full compile on save, or just transpile the current file
|
|
|
* @type {boolean}
|
|
* @type {boolean}
|
|
|
*/
|
|
*/
|
|
|
- fullCompile: boolean = false;
|
|
|
|
|
|
|
+ fullCompile: boolean = true;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* used by the compile to build a registery of all of the project files
|
|
* 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 = [];
|
|
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
|
|
//scan all the files in the project
|
|
|
Atomic.fileSystem.scanDir(ToolCore.toolSystem.project.resourcePath, "*.ts", Atomic.SCAN_FILES, true).forEach(filename => {
|
|
Atomic.fileSystem.scanDir(ToolCore.toolSystem.project.resourcePath, "*.ts", Atomic.SCAN_FILES, true).forEach(filename => {
|
|
|
this.projectFiles.push(Atomic.addTrailingSlash(ToolCore.toolSystem.project.resourcePath) + 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
|
|
// initialize the list of files
|
|
|
this.projectFiles.forEach(fileName => {
|
|
this.projectFiles.forEach(fileName => {
|
|
|
this.versionMap[fileName] = { version: 0 };
|
|
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) => {
|
|
fileNames.forEach((fileName) => {
|
|
|
console.log(`${this.name}: Transpiling ${fileName}`);
|
|
console.log(`${this.name}: Transpiling ${fileName}`);
|
|
|
let script = new Atomic.File(fileName, Atomic.FILE_READ);
|
|
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
|
|
* 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
|
|
//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 => {
|
|
output.outputFiles.forEach(o => {
|
|
|
- console.log(`${this.name}: Writing out ${o.name}`);
|
|
|
|
|
let script = new Atomic.File(o.name, Atomic.FILE_WRITE);
|
|
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
|
|
* 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]
|
|
* @return {[type]} [description]
|
|
|
*/
|
|
*/
|
|
|
- logErrors(fileName: string, diagnostics: ts.Diagnostic[]) {
|
|
|
|
|
|
|
+ logErrors(diagnostics: ts.Diagnostic[]) {
|
|
|
|
|
+ let msg = [];
|
|
|
|
|
|
|
|
diagnostics.forEach(diagnostic => {
|
|
diagnostics.forEach(diagnostic => {
|
|
|
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
|
if (diagnostic.file) {
|
|
if (diagnostic.file) {
|
|
|
let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
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 {
|
|
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}`);
|
|
console.log(`${this.name}: received a save resource event for ${ev.path}`);
|
|
|
if (this.fullCompile) {
|
|
if (this.fullCompile) {
|
|
|
this.compile([ev.path], {
|
|
this.compile([ev.path], {
|
|
|
- noEmitOnError: false,
|
|
|
|
|
|
|
+ noEmitOnError: true,
|
|
|
noImplicitAny: false,
|
|
noImplicitAny: false,
|
|
|
target: ts.ScriptTarget.ES5,
|
|
target: ts.ScriptTarget.ES5,
|
|
|
module: ts.ModuleKind.CommonJS,
|
|
module: ts.ModuleKind.CommonJS,
|
|
@@ -201,6 +262,7 @@ export default class TypescriptLanguageService implements ExtensionServices.Reso
|
|
|
*/
|
|
*/
|
|
|
canSave(ev: EditorEvents.SaveResourceEvent): boolean {
|
|
canSave(ev: EditorEvents.SaveResourceEvent): boolean {
|
|
|
const ext = Atomic.getExtension(ev.path);
|
|
const ext = Atomic.getExtension(ev.path);
|
|
|
|
|
+ //if (ext == ".ts" && !ev.path.match("\.d\.ts$")) {
|
|
|
if (ext == ".ts") {
|
|
if (ext == ".ts") {
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|