Skip to content

Commit

Permalink
feat(vfs): add JSDoc comments and refactor implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
rahulyadav-57 committed Dec 20, 2024
1 parent 62b1f9b commit 8fd2a95
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 18 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `ShortCircuitCondition` detector: PR [#202](https://github.com/nowarp/misti/pull/202)
- `PreferredStdlibApi` detector now suggest some preferred replacements for cell methods
- Add Callgraph: PR [#185](https://github.com/nowarp/misti/pull/185)
- Support for browser environment: Issue [#228](https://github.com/nowarp/misti/issues/228)
- `souffleEnabled` option to disable Souffle check execution: Issue [#228](https://github.com/nowarp/misti/issues/228)
- Support for browser environment: PR [#231](https://github.com/nowarp/misti/pull/231)
- `souffleEnabled` option to disable Souffle check execution: PR [#231](https://github.com/nowarp/misti/pull/231)
- Add function effects to Callgraph: PR [#227](https://github.com/nowarp/misti/pull/227)

### Changed
Expand Down
1 change: 1 addition & 0 deletions src/internals/ir/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export class ImportGraph {
* Resolves project root based on the import directives.
* The project root is a directory including all the imported files.
*
* @param fs The virtual file system used to manage and resolve file paths during the operation.
* @returns Project root directory or undefined if there are no user imports.
*/
public resolveProjectRoot(fs: VirtualFileSystem): string | undefined {
Expand Down
1 change: 1 addition & 0 deletions src/internals/tact/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export class TactConfigManager {
* @param ctx Misti context.
* @param projectName Name of the project.
* @param contractPath Path to the Tact contract.
* @param vfs Virtual file system to manage interactions with the project files.
*/
public static fromContract(
projectRoot: string,
Expand Down
3 changes: 2 additions & 1 deletion src/internals/tact/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { precompile } from "@tact-lang/compiler/dist/pipeline/precompile";
* @param mistiCtx Misti context
* @param projectRoot Absolute path to the root the project
* @param config The Tact configuration object: contents of the existing file or a generated object
* @param vfs Virtual file system to manage file interactions during parsing
* @returns A mapping of project names to their corresponding ASTs.
*/
export function parseTactProject(
Expand All @@ -32,7 +33,7 @@ export function parseTactProject(
const stdlibPath = mistiCtx.config.tactStdlibPath ?? getStdlibPath();
let stdlib: VirtualFileSystem | TactVirtualFileSystem;

if (vfs.type === "node") {
if (vfs.type === "local") {
stdlib = createNodeFileSystem(stdlibPath);
vfs = createNodeFileSystem(projectRoot);
} else {
Expand Down
69 changes: 65 additions & 4 deletions src/vfs/createNodeFileSystem.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { VirtualFileSystem, FileStat } from "./virtualFileSystem";
import { InternalException } from "../internals/exceptions";
import fs from "fs";
import path from "path";

/**
* Creates a Virtual File System backed by the local file system.
* This file system interacts directly with the host's disk storage.
*
* @param root - The root directory for the virtual file system.
* @param readonly - If true, prevents write operations. Default is true.
* @returns A VirtualFileSystem instance with local file system operations.
*/
export function createNodeFileSystem(
root: string,
readonly: boolean = true,
Expand All @@ -12,42 +21,94 @@ export function createNodeFileSystem(
}

return {
/**
* The normalized root directory for the virtual file system.
*/
root: normalizedRoot,
type: "node",

/**
* The type of the virtual file system. In this case, it is "local".
*/
type: "local",

/**
* Checks if a file or directory exists at the specified path.
*
* @param filePath - The path to check existence for.
* @returns True if the file or directory exists, otherwise false.
*/
exists(filePath: string): boolean {
const resolvedPath = this.resolve(filePath);
return fs.existsSync(resolvedPath);
},

/**
* Resolves a given path to an absolute path within the virtual file system's root.
*
* @param filePath - One or more path segments to resolve.
* @returns The resolved absolute path.
*/
resolve(...filePath: string[]): string {
return path.normalize(path.resolve(normalizedRoot, ...filePath));
},

/**
* Reads a file from the virtual file system.
*
* @param filePath - The path of the file to read.
* @returns A Buffer containing the file's content.
*/
readFile(filePath: string): Buffer {
const resolvedPath = this.resolve(filePath);
return fs.readFileSync(resolvedPath);
},

/**
* Writes content to a file in the virtual file system.
* Creates necessary directories if they do not exist.
*
* @param filePath - The path of the file to write to.
* @param content - The content to write, as a Buffer or string.
* @throws An exception if the file system is in readonly mode.
*/
writeFile(filePath: string, content: Buffer | string): void {
if (readonly) {
throw new Error("File system is readonly");
throw InternalException.make(
`Cannot write to file "${filePath}": The file system is in readonly mode.`,
);
}
const resolvedPath = this.resolve(filePath);
// Ensure the directory exists
const dir = path.dirname(resolvedPath);
fs.mkdirSync(dir, { recursive: true });
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(resolvedPath, content);
},

/**
* Reads the contents of a directory in the virtual file system.
*
* @param dirPath - The path of the directory to read.
* @returns An array of filenames in the directory.
* @throws An error if the specified path is not a directory.
*/
readdir(dirPath: string): string[] {
const resolvedPath = this.resolve(dirPath);
if (!fs.statSync(resolvedPath).isDirectory()) {
throw new Error(`Path '${resolvedPath}' is not a directory`);
throw InternalException.make(
`Path '${resolvedPath}' is not a directory`,
);
}
return fs.readdirSync(resolvedPath);
},

/**
* Retrieves the statistics of a file or directory.
*
* @param filePath - The path of the file or directory.
* @returns An object containing file/directory metadata.
*/
stat(filePath: string): FileStat {
const resolvedPath = this.resolve(filePath);
const stats = fs.statSync(resolvedPath);
Expand Down
66 changes: 59 additions & 7 deletions src/vfs/createVirtualFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import {
FileSystemTree,
VirtualFileSystem,
} from "./virtualFileSystem";
import { InternalException } from "../internals/exceptions";
import path from "path";

/**
* Creates a virtual file system for managing files only (no directories).
* This file system is entirely in-memory and does not interact with the host file system.
*
* @param root - The root directory for the virtual file system.
* @param fileSystemTree - The initial structure of the in-memory file system. Default is an empty object.
* @param readonly - If true, prevents write operations. Default is true.
* @returns A VirtualFileSystem instance for managing in-memory files.
*/

export function createVirtualFileSystem(
root: string,
fileSystemTree: FileSystemTree = {},
Expand All @@ -22,35 +28,69 @@ export function createVirtualFileSystem(
const memoryFS = fileSystemTree;

return {
/**
* The normalized root directory for the virtual file system.
*/
root: normalizedRoot,
type: "memory",

/**
* The type of the virtual file system. In this case, it is "inMemory".
*/
type: "inMemory",

/**
* Checks if a file exists at the specified path.
*
* @param filePath - The path to check existence for.
* @returns True if the file exists, otherwise false.
*/
exists(filePath: string): boolean {
const resolvedPath = this.resolve(filePath);
return resolvedPath in memoryFS;
},

/**
* Resolves a given path to an absolute path within the virtual file system's root.
*
* @param filePath - One or more path segments to resolve.
* @returns The resolved absolute path.
*/
resolve(...filePath: string[]): string {
return path.normalize(path.resolve(normalizedRoot, ...filePath));
},

/**
* Reads a file from the virtual file system.
*
* @param filePath - The path of the file to read.
* @returns A Buffer containing the file's content.
* @throws An error if the file does not exist or is not a file.
*/
readFile(filePath: string): Buffer {
const resolvedPath = this.resolve(filePath);
const file = memoryFS[resolvedPath];

if (!file || file.type !== "file") {
throw new Error(
throw InternalException.make(
`File '${resolvedPath}' does not exist or is not a file`,
);
}

const content = file.content ?? ""; // Default to an empty string if content is undefined
return Buffer.from(content, "utf-8");
return Buffer.from(file.content, "utf-8");
},

/**
* Writes content to a file in the virtual file system.
*
* @param filePath - The path of the file to write to.
* @param content - The content to write, as a Buffer or string.
* @throws An error if the file system is in readonly mode.
*/
writeFile(filePath: string, content: Buffer | string): void {
if (readonly) {
throw new Error("File system is readonly");
throw InternalException.make(
`Cannot write to file "${filePath}": The file system is in readonly mode.`,
);
}
const resolvedPath = this.resolve(filePath);

Expand All @@ -67,19 +107,31 @@ export function createVirtualFileSystem(
};
},

/**
* Lists all file names in the virtual file system.
*
* @returns An array of file names.
*/
readdir(): string[] {
// List all file names in the memory file system
return Object.keys(memoryFS).filter(
(key) => memoryFS[key].type === "file",
);
},

/**
* Retrieves the statistics of a file.
*
* @param filePath - The path of the file.
* @returns An object containing file metadata such as size, creation date, and update date.
* @throws An error if the file does not exist.
*/
stat(filePath: string): FileStat {
const resolvedPath = this.resolve(filePath);
const node = memoryFS[resolvedPath];

if (!node) {
throw new Error(`File '${resolvedPath}' does not exist`);
throw InternalException.make(`File '${resolvedPath}' does not exist`);
}

return {
Expand Down
19 changes: 15 additions & 4 deletions src/vfs/virtualFileSystem.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
type FileSystemNode = {
type: "file" | "directory";
content?: string;
type FileNode = {
type: "file";
content: string;
size?: number;
createdAt?: Date;
updatedAt?: Date;
};

type DirectoryNode = {
type: "directory";
size?: number;
createdAt?: Date;
updatedAt?: Date;
};

type FileSystemNode = FileNode | DirectoryNode;

export type FileSystemTree = Record<string, FileSystemNode>;

export type FileStat = {
Expand All @@ -16,9 +25,11 @@ export type FileStat = {
updatedAt: Date;
};

type FileSystemBackend = "local" | "inMemory";

export type VirtualFileSystem = {
root: string;
type: "node" | "memory";
type: FileSystemBackend;
resolve(...path: string[]): string;
exists(path: string): boolean;
readFile(path: string): Buffer;
Expand Down

0 comments on commit 8fd2a95

Please sign in to comment.