This document outlines the coding style and conventions used in the Content Scope Scripts project.
We use Prettier for automatic code formatting. This ensures consistent code style across the entire codebase.
- Configuration: See
.prettierrcfor current formatting settings - IDE Integration: Enable Prettier in your IDE/editor for automatic formatting on save
- CI/CD: Code formatting is checked in our continuous integration pipeline
# Format all files
npm run lint-fix
# Check formatting (without making changes)
npm run lintWe use JSDoc comments to provide TypeScript-like type safety without requiring a TypeScript compilation step.
/**
* @param {string} videoId - The video identifier
* @param {() => void} handler - Callback function to invoke
* @returns {boolean} Whether the operation was successful
*/
function processVideo(videoId, handler) {
// implementation
}// Variable type annotation
/** @type {HTMLElement|null} */
const element = document.getElementById('my-element');
// Function parameter and return types
/**
* @param {HTMLIFrameElement} iframe
* @returns {(() => void)|null}
*/
function setupIframe(iframe) {
// implementation
}/**
* @typedef {Object} VideoParams
* @property {string} id - Video ID
* @property {string} title - Video title
* @property {number} duration - Duration in seconds
*/
/**
* @typedef {import("./iframe").IframeFeature} IframeFeature
*/When working with DOM elements or external environments (like iframes), prefer runtime type checks over type casting:
// ❌ Avoid type casting when safety is uncertain
/** @type {Element} */
const element = someUnknownValue;
// ✅ Use instanceof checks for runtime safety
if (!(target instanceof Element)) return;
const element = target; // TypeScript now knows this is an ElementAlways check for null/undefined when accessing properties that might not exist:
// ❌ Unsafe
const doc = iframe.contentDocument;
doc.addEventListener('click', handler);
// ✅ Safe
const doc = iframe.contentDocument;
if (!doc) {
console.log('could not access contentDocument');
return;
}
doc.addEventListener('click', handler);- Use meaningful variable names that describe their purpose
- Add JSDoc comments for all public functions and complex logic
- Prefer explicit type checks over type assertions in uncertain environments
- Handle edge cases gracefully with proper error handling
- Keep functions small and focused on a single responsibility
- Design richer return types to avoid using exceptions as control flow
- Favor implements over extends to avoid class inheritance (see Interface Implementation section)
- Remove 'index' files if they only serve to enable re-exports - prefer explicit imports/exports
- Prefer function declarations over arrow functions for module-level functions
Instead of using exceptions for control flow, design richer return types:
// ❌ Using exceptions for control flow
function parseVideoId(url) {
if (!url) throw new Error('URL is required');
if (!isValidUrl(url)) throw new Error('Invalid URL');
return extractId(url);
}
// ✅ Using richer return types
/**
* @typedef {Object} ParseResult
* @property {boolean} success
* @property {string} [videoId] - Present when success is true
* @property {string} [error] - Present when success is false
*/
/**
* @param {string} url
* @returns {ParseResult}
*/
function parseVideoId(url) {
if (!url) return { success: false, error: 'URL is required' };
if (!isValidUrl(url)) return { success: false, error: 'Invalid URL' };
return { success: true, videoId: extractId(url) };
}Favor implements over extends to avoid class inheritance. While this is awkward in JSDoc, it promotes composition over inheritance:
/**
* @typedef {Object} IframeFeature
* @property {function(HTMLIFrameElement): void} iframeDidLoad
*/
/**
* @implements {IframeFeature}
*/
export class ReplaceWatchLinks {
/**
* @param {HTMLIFrameElement} iframe
*/
iframeDidLoad(iframe) {
// implementation
}
}Avoid index files that only serve re-exports. Be explicit about imports:
// ❌ Avoid index.js files with only re-exports
// index.js
export { FeatureA } from './feature-a.js';
export { FeatureB } from './feature-b.js';
// ✅ Import directly from source files
import { FeatureA } from './features/feature-a.js';
import { FeatureB } from './features/feature-b.js';Prefer function declarations over arrow functions for module-level functions:
// ❌ Arrow functions at module level
const processVideo = (videoId) => {
// implementation
};
const validateUrl = (url) => {
// implementation
};
// ✅ Function declarations at module level
function processVideo(videoId) {
// implementation
}
function validateUrl(url) {
// implementation
}Why function declarations are preferred:
- Hoisted, so order doesn't matter
- Cleaner syntax for longer functions
- Better stack traces in debugging
- More conventional for module-level exports
Recommended extensions:
- Prettier - Code formatter
- TypeScript and JavaScript Language Features (built-in)
- ESLint
We use ESLint for code quality checks. See eslint.config.js for the current linting configuration.
Run linting with:
npm run lintFollow the linting rules and fix any issues before committing code.