diff --git a/src/backend/doc/contributors/coding-style.md b/src/backend/doc/contributors/coding-style.md new file mode 100644 index 000000000..4ff125539 --- /dev/null +++ b/src/backend/doc/contributors/coding-style.md @@ -0,0 +1,212 @@ +# Backend Style + +## File Structure + +### Copyright Notice + +All files should begin with the standard copyright notice: + +```javascript +/* + * Copyright (C) 2025-present Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +``` + +### Imports + +```javascript +const express = require('express'); +const passport = require('passport'); + +const { get_user } = require("../../helpers"); +const BaseService = require("../../services/BaseService"); +const config = require("../../config"); + +const path = require('path'); +const fs = require('fs'); +``` + +Import order is generally: +1. Third party dependencies. Having these occur first makes it easy to quickly + determine what this source file is likely to be responsible for. +2. Files within the module. +3. Standard library, "builtins" + +## Code Formatting + +### Indentation and Spacing + +```javascript +const fn = async () => { + const a = 5; // Spaces between operators + + // Note: "=" in for loop initializer does not require space around + // Note: operators in condition part have space around + for ( let i=0; i < 10; i++ ) { + console.log('hello'); + } + + // Control structures have space inside parenthesis + for ( const thing of stuff ) { + // NOOP + } + + // Function calls do not have space inside parenthesis + await something(1, 2); +} +``` + +- Use 4 spaces for indentation. +- Use spaces around operators (`=`, `+`, etc.); not required in + for loop initializer. +- Use a space after keywords like `if`, `for`, `while`, etc. + ```javascript + return [1,2,3]; // Sure + return[1,2,4]; // Definitely not + ``` +- Use spaces between parenthesis in control structures unless + parenthesis are empty. + ```javascript + if ( a === b ) { + return null; + } + ``` +- No trailing whitespace at the end of lines +- Use a space after commas in arrays and objects +- Empty blocks should have the comment `// NOOP` within braces + +### Line Length + +- Try to keep lines under 100 characters for better readability + - Try to keep them under 80, but this is not always practical +- For long function calls or objects, break them into multiple lines + + +### Trailing Commas + +```javascript +// This is great +{ + "apple", + "banana", + "cactus", // <-- Good! +} + +// This is also fine +[ + 1, 2, 3, + 4, 5, 6, + 7, 8, 9, +] + +[ + something(), + another_thing(), + the_last_thing() // <-- Nope, please add trailing comma! +] +``` + +We use trailing commas where applicable because it's easier to re-order +lines, especially when using vim motions. + +### Braces and Blocks + +- Single statement blocks must either be on the same line as + the corresponding control structure, or surrounding by braces: + ```javascript + if ( a === b ) return null; // Sure + if ( a === b ) + return null; // Please no 🤮 + if ( a === b ) { + return null; // Nice + } + ``` +- Opening braces go on the same line as the statement +- Put a space before the opening brace + + +## Naming Conventions + +### Variables + +- Variables are generally in camelCase +- Variables might have a prefix_beforeThem + +```javascript +const svc_systemData = this.services.get('system-data'); +const svc_su = this.services.get('su'); +effective_policy = await svc_su.sudo(async () => { + return await svc_systemData.interpret(effective_policy.data); +}); +``` + +In the example above we see the `svc_` prefix is used to indicate a +reference to a backend service. The name of the service is `system-data` +which is not a valid identifier, so we use `svc_systemData` for our +variable name. + +### Classes + +- Use PascalCase for class names +- Use snake_case for class methods +- Instance variables are often `snake_case` because it's easier to + read. `camelCase` is acceptable too. +- Instance variables only used internally should have a + `trailing_underscore_` even if in `camelCase_`. We avoid using + `#privateProperties` because it unnecessarily inhibits debugging + and patching. + +### File Names + +- Use PascalCase for class files (e.g., `UserService.js`) +- Use kebab-case for non-class files (e.g., `auth-helper.js`) + +## Documentation + +### JSDoc Comments + +- Backend services (classes extending `BaseService`) should have JSDoc comments +- Public methods of backend services should have JSDoc comments +- Include parameter descriptions, return values, and examples where appropriate + +```javascript +/** + * @class UserService + * @description Service for managing user operations + */ + +/** + * Get a user by their ID + * @param {string} id - The user ID + * @returns {Promise} The user object + * @throws {Error} If user not found + */ +async function getUserById(id) { + // ... +} +``` + +### Inline Comments + +- Use inline comments to explain complex logic +- Prefix comments with tags like `track:` to indicate specific purposes + +```javascript +// track: slice a prefix +const uid = uid_part.slice('uid#'.length); +```