diff --git a/deno/common/DenoRuntime.ts b/deno/common/DenoRuntime.ts
index 07255f45c..ce0df1e2b 100644
--- a/deno/common/DenoRuntime.ts
+++ b/deno/common/DenoRuntime.ts
@@ -90,38 +90,25 @@ class DenoRuntimeFileSystem {
     return Deno.copyFileSync(srcPath, destPath);
   }
 
-  async fileExists(filePath: string) {
-    try {
-      const stat = await Deno.stat(filePath);
-      return stat.isFile;
-    } catch {
-      return false;
-    }
-  }
-
-  fileExistsSync(filePath: string) {
-    try {
-      return Deno.statSync(filePath).isFile;
-    } catch {
-      return false;
-    }
+  async stat(filePath: string) {
+    const stat = await Deno.stat(filePath);
+    return this._toStat(stat);
   }
 
-  async directoryExists(dirPath: string) {
-    try {
-      const stat = await Deno.stat(dirPath);
-      return stat.isDirectory;
-    } catch {
-      return false;
-    }
+  statSync(path: string) {
+    const stat = Deno.statSync(path);
+    return this._toStat(stat);
   }
 
-  directoryExistsSync(dirPath: string) {
-    try {
-      return Deno.statSync(dirPath).isDirectory;
-    } catch (err) {
-      return false;
-    }
+  private _toStat(stat: Deno.FileInfo) {
+    return {
+      isFile() {
+        return stat.isFile;
+      },
+      isDirectory() {
+        return stat.isDirectory;
+      },
+    };
   }
 
   realpathSync(path: string) {
diff --git a/deno/common/ts_morph_common.d.ts b/deno/common/ts_morph_common.d.ts
index 6342f663c..8866d8b39 100644
--- a/deno/common/ts_morph_common.d.ts
+++ b/deno/common/ts_morph_common.d.ts
@@ -914,6 +914,11 @@ export interface RuntimeDirEntry {
     isSymlink: boolean;
 }
 
+export interface RuntimeFileInfo {
+    isFile(): boolean;
+    isDirectory(): boolean;
+}
+
 export interface RuntimeFileSystem {
     /** Gets if this file system is case sensitive. */
     isCaseSensitive(): boolean;
@@ -943,14 +948,10 @@ export interface RuntimeFileSystem {
     copy(srcPath: string, destPath: string): Promise<void>;
     /** Synchronously copies a file or directory. */
     copySync(srcPath: string, destPath: string): void;
-    /** Asynchronously checks if a file exists. */
-    fileExists(filePath: string): Promise<boolean>;
-    /** Synchronously checks if a file exists. */
-    fileExistsSync(filePath: string): boolean;
-    /** Asynchronously checks if a directory exists. */
-    directoryExists(dirPath: string): Promise<boolean>;
-    /** Synchronously checks if a directory exists. */
-    directoryExistsSync(dirPath: string): boolean;
+    /** Asynchronously gets the path's stat information. */
+    stat(path: string): Promise<RuntimeFileInfo>;
+    /** Synchronously gets the path's stat information. */
+    statSync(path: string): RuntimeFileInfo;
     /** See https://nodejs.org/api/fs.html#fs_fs_realpathsync_path_options */
     realpathSync(path: string): string;
     /** Gets the current directory of the environment. */
diff --git a/deno/common/ts_morph_common.js b/deno/common/ts_morph_common.js
index 1770c41f5..363c0f914 100644
--- a/deno/common/ts_morph_common.js
+++ b/deno/common/ts_morph_common.js
@@ -1605,8 +1605,18 @@ class RealFileSystemHost {
     readDirSync(dirPath) {
         try {
             const entries = fs.readDirSync(dirPath);
-            for (const entry of entries)
+            for (const entry of entries) {
                 entry.name = FileUtils.pathJoin(dirPath, entry.name);
+                if (entry.isSymlink) {
+                    try {
+                        const info = fs.statSync(entry.name);
+                        entry.isDirectory = info.isDirectory();
+                        entry.isFile = info.isFile();
+                    }
+                    catch (_a) {
+                    }
+                }
+            }
             return entries;
         }
         catch (err) {
@@ -1653,17 +1663,37 @@ class RealFileSystemHost {
     copySync(srcPath, destPath) {
         fs.copySync(srcPath, destPath);
     }
-    fileExists(filePath) {
-        return fs.fileExists(filePath);
+    async fileExists(filePath) {
+        try {
+            return (await fs.stat(filePath)).isFile();
+        }
+        catch (_a) {
+            return false;
+        }
     }
     fileExistsSync(filePath) {
-        return fs.fileExistsSync(filePath);
+        try {
+            return fs.statSync(filePath).isFile();
+        }
+        catch (_a) {
+            return false;
+        }
     }
-    directoryExists(dirPath) {
-        return fs.directoryExists(dirPath);
+    async directoryExists(dirPath) {
+        try {
+            return (await fs.stat(dirPath)).isDirectory();
+        }
+        catch (_a) {
+            return false;
+        }
     }
     directoryExistsSync(dirPath) {
-        return fs.directoryExistsSync(dirPath);
+        try {
+            return fs.statSync(dirPath).isDirectory();
+        }
+        catch (_a) {
+            return false;
+        }
     }
     realpathSync(path) {
         return fs.realpathSync(path);
diff --git a/deno/ts_morph.d.ts b/deno/ts_morph.d.ts
index f0b5e4c99..6c8150302 100644
--- a/deno/ts_morph.d.ts
+++ b/deno/ts_morph.d.ts
@@ -9214,6 +9214,8 @@ export declare class Symbol {
   getExportSymbol(): Symbol;
   /** Gets if the symbol is an alias. */
   isAlias(): boolean;
+  /** Gets if the symbol is optional. */
+  isOptional(): boolean;
   /** Gets the symbol flags. */
   getFlags(): SymbolFlags;
   /**
@@ -10124,6 +10126,8 @@ export declare class Type<TType extends ts.Type = ts.Type> {
   isAny(): boolean;
   /** Gets if this is an array type. */
   isArray(): boolean;
+  /** Gets if this is a template literal type. */
+  isTemplateLiteral(): boolean;
   /** Gets if this is a boolean type. */
   isBoolean(): boolean;
   /** Gets if this is a string type. */
diff --git a/deno/ts_morph.js b/deno/ts_morph.js
index 148ea0d1e..df59ade78 100644
--- a/deno/ts_morph.js
+++ b/deno/ts_morph.js
@@ -16840,6 +16840,9 @@ class Symbol {
     isAlias() {
         return (this.getFlags() & SymbolFlags.Alias) === SymbolFlags.Alias;
     }
+    isOptional() {
+        return (this.getFlags() & SymbolFlags.Optional) === SymbolFlags.Optional;
+    }
     getFlags() {
         return this.compilerSymbol.getFlags();
     }
@@ -17971,6 +17974,9 @@ class Type {
             return false;
         return symbol.getName() === "Array" && this.getTypeArguments().length === 1;
     }
+    isTemplateLiteral() {
+        return this._hasTypeFlag(TypeFlags.TemplateLiteral);
+    }
     isBoolean() {
         return this._hasTypeFlag(TypeFlags.Boolean);
     }
diff --git a/packages/common/lib/ts-morph-common.d.ts b/packages/common/lib/ts-morph-common.d.ts
index 330b97d55..bb46f0fd2 100644
--- a/packages/common/lib/ts-morph-common.d.ts
+++ b/packages/common/lib/ts-morph-common.d.ts
@@ -913,6 +913,11 @@ export interface RuntimeDirEntry {
     isSymlink: boolean;
 }
 
+export interface RuntimeFileInfo {
+    isFile(): boolean;
+    isDirectory(): boolean;
+}
+
 export interface RuntimeFileSystem {
     /** Gets if this file system is case sensitive. */
     isCaseSensitive(): boolean;
@@ -942,14 +947,10 @@ export interface RuntimeFileSystem {
     copy(srcPath: string, destPath: string): Promise<void>;
     /** Synchronously copies a file or directory. */
     copySync(srcPath: string, destPath: string): void;
-    /** Asynchronously checks if a file exists. */
-    fileExists(filePath: string): Promise<boolean>;
-    /** Synchronously checks if a file exists. */
-    fileExistsSync(filePath: string): boolean;
-    /** Asynchronously checks if a directory exists. */
-    directoryExists(dirPath: string): Promise<boolean>;
-    /** Synchronously checks if a directory exists. */
-    directoryExistsSync(dirPath: string): boolean;
+    /** Asynchronously gets the path's stat information. */
+    stat(path: string): Promise<RuntimeFileInfo>;
+    /** Synchronously gets the path's stat information. */
+    statSync(path: string): RuntimeFileInfo;
     /** See https://nodejs.org/api/fs.html#fs_fs_realpathsync_path_options */
     realpathSync(path: string): string;
     /** Gets the current directory of the environment. */
diff --git a/packages/common/src/fileSystem/RealFileSystemHost.ts b/packages/common/src/fileSystem/RealFileSystemHost.ts
index 62e005442..37009cdbc 100644
--- a/packages/common/src/fileSystem/RealFileSystemHost.ts
+++ b/packages/common/src/fileSystem/RealFileSystemHost.ts
@@ -29,8 +29,18 @@ export class RealFileSystemHost implements FileSystemHost {
   readDirSync(dirPath: string): RuntimeDirEntry[] {
     try {
       const entries = fs.readDirSync(dirPath);
-      for (const entry of entries)
+      for (const entry of entries) {
         entry.name = FileUtils.pathJoin(dirPath, entry.name);
+        if (entry.isSymlink) {
+          try {
+            const info = fs.statSync(entry.name);
+            entry.isDirectory = info.isDirectory();
+            entry.isFile = info.isFile();
+          } catch {
+            // ignore
+          }
+        }
+      }
       return entries;
     } catch (err) {
       throw this.getDirectoryNotFoundErrorIfNecessary(err, dirPath);
@@ -96,23 +106,39 @@ export class RealFileSystemHost implements FileSystemHost {
   }
 
   /** @inheritdoc */
-  fileExists(filePath: string) {
-    return fs.fileExists(filePath);
+  async fileExists(filePath: string) {
+    try {
+      return (await fs.stat(filePath)).isFile();
+    } catch {
+      return false;
+    }
   }
 
   /** @inheritdoc */
   fileExistsSync(filePath: string) {
-    return fs.fileExistsSync(filePath);
+    try {
+      return fs.statSync(filePath).isFile();
+    } catch {
+      return false;
+    }
   }
 
   /** @inheritdoc */
-  directoryExists(dirPath: string) {
-    return fs.directoryExists(dirPath);
+  async directoryExists(dirPath: string) {
+    try {
+      return (await fs.stat(dirPath)).isDirectory();
+    } catch {
+      return false;
+    }
   }
 
   /** @inheritdoc */
   directoryExistsSync(dirPath: string) {
-    return fs.directoryExistsSync(dirPath);
+    try {
+      return fs.statSync(dirPath).isDirectory();
+    } catch {
+      return false;
+    }
   }
 
   /** @inheritdoc */
diff --git a/packages/common/src/runtimes/BrowserRuntime.ts b/packages/common/src/runtimes/BrowserRuntime.ts
index 07fd33190..dc82f7541 100644
--- a/packages/common/src/runtimes/BrowserRuntime.ts
+++ b/packages/common/src/runtimes/BrowserRuntime.ts
@@ -1,5 +1,5 @@
 import minimatch from "minimatch";
-import { Runtime, RuntimeDirEntry, RuntimeFileSystem, RuntimePath } from "./Runtime";
+import { Runtime, RuntimeDirEntry, RuntimeFileInfo, RuntimeFileSystem, RuntimePath } from "./Runtime";
 
 const path = require("path-browserify");
 
@@ -90,19 +90,11 @@ class BrowserRuntimeFileSystem implements RuntimeFileSystem {
     throw new Error(this._errorMessage);
   }
 
-  fileExists(_filePath: string): Promise<boolean> {
+  stat(_path: string): Promise<RuntimeFileInfo> {
     return Promise.reject(new Error(this._errorMessage));
   }
 
-  fileExistsSync(_filePath: string): boolean {
-    throw new Error(this._errorMessage);
-  }
-
-  directoryExists(_dirPath: string): Promise<boolean> {
-    return Promise.reject(new Error(this._errorMessage));
-  }
-
-  directoryExistsSync(_dirPath: string): boolean {
+  statSync(_path: string): RuntimeFileInfo {
     throw new Error(this._errorMessage);
   }
 
diff --git a/packages/common/src/runtimes/DenoRuntime.ts b/packages/common/src/runtimes/DenoRuntime.ts
index 31dc98ef6..dc2871b77 100644
--- a/packages/common/src/runtimes/DenoRuntime.ts
+++ b/packages/common/src/runtimes/DenoRuntime.ts
@@ -95,38 +95,25 @@ class DenoRuntimeFileSystem {
     return Deno.copyFileSync(srcPath, destPath);
   }
 
-  async fileExists(filePath: string) {
-    try {
-      const stat = await Deno.stat(filePath);
-      return stat.isFile;
-    } catch {
-      return false;
-    }
-  }
-
-  fileExistsSync(filePath: string) {
-    try {
-      return Deno.statSync(filePath).isFile;
-    } catch {
-      return false;
-    }
+  async stat(filePath: string) {
+    const stat = await Deno.stat(filePath);
+    return this._toStat(stat);
   }
 
-  async directoryExists(dirPath: string) {
-    try {
-      const stat = await Deno.stat(dirPath);
-      return stat.isDirectory;
-    } catch {
-      return false;
-    }
+  statSync(path: string) {
+    const stat = Deno.statSync(path);
+    return this._toStat(stat);
   }
 
-  directoryExistsSync(dirPath: string) {
-    try {
-      return Deno.statSync(dirPath).isDirectory;
-    } catch (err) {
-      return false;
-    }
+  private _toStat(stat: Deno.FileInfo) {
+    return {
+      isFile() {
+        return stat.isFile;
+      },
+      isDirectory() {
+        return stat.isDirectory;
+      },
+    };
   }
 
   realpathSync(path: string) {
diff --git a/packages/common/src/runtimes/NodeRuntime.ts b/packages/common/src/runtimes/NodeRuntime.ts
index 5fd5e440c..be0787c46 100644
--- a/packages/common/src/runtimes/NodeRuntime.ts
+++ b/packages/common/src/runtimes/NodeRuntime.ts
@@ -4,7 +4,7 @@ import minimatch from "minimatch";
 import mkdirp from "mkdirp";
 import * as os from "os";
 import * as path from "path";
-import { Runtime, RuntimeFileSystem, RuntimePath } from "./Runtime";
+import { Runtime, RuntimeFileInfo, RuntimeFileSystem, RuntimePath } from "./Runtime";
 
 export class NodeRuntime implements Runtime {
   fs = new NodeRuntimeFileSystem();
@@ -135,42 +135,19 @@ class NodeRuntimeFileSystem implements RuntimeFileSystem {
     fs.copyFileSync(srcPath, destPath);
   }
 
-  fileExists(filePath: string) {
-    return new Promise<boolean>(resolve => {
-      fs.stat(filePath, (err, stat) => {
+  stat(path: string) {
+    return new Promise<RuntimeFileInfo>(resolve => {
+      fs.stat(path, (err, stat) => {
         if (err)
-          resolve(false);
+          throw err;
         else
-          resolve(stat.isFile());
+          resolve(stat);
       });
     });
   }
 
-  fileExistsSync(filePath: string) {
-    try {
-      return fs.statSync(filePath).isFile();
-    } catch (err) {
-      return false;
-    }
-  }
-
-  directoryExists(dirPath: string) {
-    return new Promise<boolean>(resolve => {
-      fs.stat(dirPath, (err, stat) => {
-        if (err)
-          resolve(false);
-        else
-          resolve(stat.isDirectory());
-      });
-    });
-  }
-
-  directoryExistsSync(dirPath: string) {
-    try {
-      return fs.statSync(dirPath).isDirectory();
-    } catch (err) {
-      return false;
-    }
+  statSync(path: string) {
+    return fs.statSync(path);
   }
 
   realpathSync(path: string) {
diff --git a/packages/common/src/runtimes/Runtime.ts b/packages/common/src/runtimes/Runtime.ts
index e6e005316..a20b482e1 100644
--- a/packages/common/src/runtimes/Runtime.ts
+++ b/packages/common/src/runtimes/Runtime.ts
@@ -14,6 +14,11 @@ export interface RuntimeDirEntry {
   isSymlink: boolean;
 }
 
+export interface RuntimeFileInfo {
+  isFile(): boolean;
+  isDirectory(): boolean;
+}
+
 export interface RuntimeFileSystem {
   /** Gets if this file system is case sensitive. */
   isCaseSensitive(): boolean;
@@ -43,14 +48,10 @@ export interface RuntimeFileSystem {
   copy(srcPath: string, destPath: string): Promise<void>;
   /** Synchronously copies a file or directory. */
   copySync(srcPath: string, destPath: string): void;
-  /** Asynchronously checks if a file exists. */
-  fileExists(filePath: string): Promise<boolean>;
-  /** Synchronously checks if a file exists. */
-  fileExistsSync(filePath: string): boolean;
-  /** Asynchronously checks if a directory exists. */
-  directoryExists(dirPath: string): Promise<boolean>;
-  /** Synchronously checks if a directory exists. */
-  directoryExistsSync(dirPath: string): boolean;
+  /** Asynchronously gets the path's stat information. */
+  stat(path: string): Promise<RuntimeFileInfo>;
+  /** Synchronously gets the path's stat information. */
+  statSync(path: string): RuntimeFileInfo;
   /** See https://nodejs.org/api/fs.html#fs_fs_realpathsync_path_options */
   realpathSync(path: string): string;
   /** Gets the current directory of the environment. */
diff --git a/packages/ts-morph/lib/ts-morph.d.ts b/packages/ts-morph/lib/ts-morph.d.ts
index fad84a500..c261930d8 100644
--- a/packages/ts-morph/lib/ts-morph.d.ts
+++ b/packages/ts-morph/lib/ts-morph.d.ts
@@ -9214,6 +9214,8 @@ export declare class Symbol {
   getExportSymbol(): Symbol;
   /** Gets if the symbol is an alias. */
   isAlias(): boolean;
+  /** Gets if the symbol is optional. */
+  isOptional(): boolean;
   /** Gets the symbol flags. */
   getFlags(): SymbolFlags;
   /**
@@ -10124,6 +10126,8 @@ export declare class Type<TType extends ts.Type = ts.Type> {
   isAny(): boolean;
   /** Gets if this is an array type. */
   isArray(): boolean;
+  /** Gets if this is a template literal type. */
+  isTemplateLiteral(): boolean;
   /** Gets if this is a boolean type. */
   isBoolean(): boolean;
   /** Gets if this is a string type. */
diff --git a/packages/ts-morph/src/compiler/types/Type.ts b/packages/ts-morph/src/compiler/types/Type.ts
index 18a927a88..b8160cf3e 100644
--- a/packages/ts-morph/src/compiler/types/Type.ts
+++ b/packages/ts-morph/src/compiler/types/Type.ts
@@ -389,12 +389,12 @@ export class Type<TType extends ts.Type = ts.Type> {
     return symbol.getName() === "Array" && this.getTypeArguments().length === 1;
   }
 
-    /**
-     * Gets if this is a boolean type.
-     */
-    isTemplateLiteral() {
-        return this._hasTypeFlag(TypeFlags.TemplateLiteral);
-    }
+  /**
+   * Gets if this is a template literal type.
+   */
+  isTemplateLiteral() {
+    return this._hasTypeFlag(TypeFlags.TemplateLiteral);
+  }
 
   /**
    * Gets if this is a boolean type.