Code Style Guide
Overview
Section titled “Overview”This document defines the naming conventions, formatting rules, and code patterns for the JellyRock codebase. All contributors must follow these rules. Naming conventions are enforced by code review (bslint does not support naming rules).
Core principle: PascalCase = type definitions. lowerCamelCase = everything else. UPPER_SNAKE_CASE = immutable predefined values.
Naming Conventions
Section titled “Naming Conventions”Variables & Parameters
Section titled “Variables & Parameters”lowerCamelCase for all variables, parameters, and local references.
audioStreamIdx = 1fullUrl = serverUrl + "/" + pathpreferredLang = resolveSubtitleLanguagePreference(settings, config)Functions, Methods & Subs
Section titled “Functions, Methods & Subs”lowerCamelCase for all function and sub names.
function getSetting(key, defaultValue = invalid)sub registryWrite(key, value, section = invalid)function buildAuthHeader() as stringClasses
Section titled “Classes”PascalCase for class names.
class ApiClient private global = invalid
sub new() m.global = GetGlobalAA().global end subend classPascalCase for enum names (they are type definitions). UPPER_SNAKE_CASE for enum members (they are immutable predefined values — constants).
enum MediaSegmentType UNKNOWN = "Unknown" INTRO = "Intro" OUTRO = "Outro"end enum
enum SubtitleSelection NOT_SET = -2 NONE = -1end enum
' Usageif segment.type = MediaSegmentType.INTROEnum values (the right side of =) must match external API contracts where applicable. Only the member names follow our convention.
Components
Section titled “Components”PascalCase for component names in XML, matching the file name.
<component name="ItemDetails" extends="JRScreen">Namespaces
Section titled “Namespaces”lowerCamelCase for namespace names. Namespaces are containers, not types.
namespace imageSize const POSTER_SM = { width: 108, height: 162 }end namespace
namespace sdk function getItems(params as object) end functionend namespaceConstants
Section titled “Constants”UPPER_SNAKE_CASE for all const declarations and namespace level constants.
const QUOTE = Chr(34)
namespace itemTypeOrder const SEARCH = ["Movie", "Series", "Episode"] const NO_RESUME = ["MusicAlbum", "MusicArtist"]end namespaceBooleans
Section titled “Booleans”Boolean variables and fields must use a prefix: is, has, should, can, or enable.
' CorrectisFolder = truehasSubtitles = falseshouldAutoPlay = truecanDelete = item.canDeleteenableNextEpisodeAutoPlay = true
' Incorrectfolder = truesubtitles = falseautoPlay = trueBoolean Naming Exceptions
Section titled “Boolean Naming Exceptions”The following boolean fields are exempt from the prefix rule:
- Signal fields: XML fields with
alwaysNotify="true"used purely as observable event triggers (e.g.,backPressed,refreshItemDetailsData). The value is irrelevant; the write event itself is the message. Examples:backPressed,exit,submit,reset,closeSidePanel,optionSelected,reloadHomeRequested,requestFocusReturn. - API-mirrored fields: Fields in
JellyfinUserConfigurationandJellyfinUserPolicythat mirror Jellyfin server API property names exactly (e.g.,playDefaultAudioTrack,enableNextEpisodeAutoPlay). These are external contracts. - Settings/registry keys:
JellyfinUserSettingsfield IDs that are 1:1 with Roku registry keys (e.g.,playbackCinemaMode,uiFontFallback). Renaming requires a registry migration to avoid user data loss.
Private Class Members
Section titled “Private Class Members”Use the private keyword only. Do not use an underscore prefix — in BrighterScript, _ prefix means “unused parameter.”
class MyClass ' Correct private global = invalid private imageDefaults = {}
' Incorrect — _ means "unused", not "private" private _global = invalidend classUnused Parameters
Section titled “Unused Parameters”Use _ prefix for parameters that must exist in a signature but are not used.
function onKeyEvent(key as string, _press as boolean) as boolean ' _press is unused — only key matters here return key = "back"end functionFile Naming
Section titled “File Naming”Class, Component & Enum Files → PascalCase
Section titled “Class, Component & Enum Files → PascalCase”Files containing type definitions (classes, components, enums) use PascalCase.
source/api/ApiClient.bs ' class ApiClientsource/enums/MediaSegmentType.bs ' enum MediaSegmentTypecomponents/ItemDetails.xml ' component ItemDetailscomponents/ItemDetails.bs ' companion scriptUtility & Function Files → lowerCamelCase
Section titled “Utility & Function Files → lowerCamelCase”Files containing only functions, subs, or namespace definitions use lowerCamelCase. Single-word names are naturally valid.
source/utils/config.bs ' utility functionssource/utils/nodeHelpers.bs ' namespace nodeHelperssource/api/baseRequest.bs ' utility functionssource/constants/imageSize.bs ' namespace imageSizeXML + BS Pairing
Section titled “XML + BS Pairing”Component XML and BS files must share the same PascalCase base name. BrighterScript auto-scopes them together.
components/video/VideoPlayerView.xmlcomponents/video/VideoPlayerView.bsXML Conventions
Section titled “XML Conventions”Interface Field IDs → lowerCamelCase
Section titled “Interface Field IDs → lowerCamelCase”<interface> <field id="baseTitle" type="string" /> <field id="configKey" type="string" /> <field id="valueIndex" type="integer" /> <field id="globalSetting" type="boolean" value="false" /></interface>Child Element IDs → lowerCamelCase
Section titled “Child Element IDs → lowerCamelCase”<children> <LabelSecondarySmaller id="videoCodec" /> <LabelSecondarySmallest id="videoCodecCount" /></children>onChange Callbacks
Section titled “onChange Callbacks”onChange values are string references to function names. They must match the function definition exactly.
<field id="choices" type="array" onChange="updateTitle" />' Must match exactly — mismatch fails silently at runtimesub updateTitle() m.top.title = m.top.baseTitle + ": " + m.top.choices[m.top.valueIndex].displayend subFormatting
Section titled “Formatting”Indentation & Whitespace
Section titled “Indentation & Whitespace”- 2 spaces — no tabs (enforced by
.editorconfigandbsfmt.json) LFline endings (Unix-style)- Trim trailing whitespace
- Insert final newline
Strings & Comments
Section titled “Strings & Comments”- Double quotes for string values:
"hello" - Single quote (
') for comments:' This is a comment - Never use
REMor//for comments
Operators
Section titled “Operators”Single space around all binary operators:
result = a + bif isValid(item) and item.type = "Movie"url = serverUrl + "/" + pathvalue = apiData.Id ?? ""Import Ordering
Section titled “Import Ordering”Imports are sorted alphabetically (enforced by bsfmt.json with sortImports: true).
import "pkg:/source/api/baseRequest.bs"import "pkg:/source/utils/config.bs"import "pkg:/source/utils/misc.bs"Line Length
Section titled “Line Length”120 characters is the guideline. Not currently enforced by tooling — use developer judgment.
Comments
Section titled “Comments”JSDoc Style for Functions
Section titled “JSDoc Style for Functions”Every public function should have a JSDoc style comment describing its purpose, parameters, and return value.
' Filter registry keys to find those that should be deleted during a settings reset' Preserves session/identity keys, deletes everything else' @param allKeys - associative array of all registry key-value pairs' @param preserveKeys - array of key names to preserve' @return array of key names that should be deletedfunction getSettingKeysToDelete(allKeys as object, preserveKeys as object) as objectInline Comments
Section titled “Inline Comments”Use inline comments for complex logic, Roku-specific oddities, and non-obvious decisions.
' ContentNode fields return "" instead of invalid, so check bothif not isValid(serverUrl) or serverUrl = "" then return invalidSection Dividers
Section titled “Section Dividers”Use decorated comment blocks for major logical sections within large files.
' ════════════════════════════════════════' SECTION NAME' ════════════════════════════════════════BrightScript Specific Rules
Section titled “BrightScript Specific Rules”Roku Built-In Function & Method Casing
Section titled “Roku Built-In Function & Method Casing”BrightScript is case-insensitive for all identifiers, keywords, and function/method names. inStr(), Instr(), and INSTR() are identical at runtime. However, we use lowerCamelCase for Roku built-in functions and methods, consistent with our project-wide naming convention.
' Correct — lowerCamelCasepos = myString.inStr("search")idx = inStr(1, locale, "_")
' Incorrect — PascalCase / lowercasepos = myString.Instr("search")idx = Instr(1, locale, "_")Associative Array Key Casing
Section titled “Associative Array Key Casing”BrightScript stores AA keys in lowercase regardless of source code casing. { maxWidth: 1920 } stores the key as "maxwidth". This is a language limitation, not a convention violation.
When writing AA literals, still use lowerCamelCase in source for readability:
params = { maxWidth: 1920, imageType: "Primary"}' At runtime, keys are "maxwidth" and "imagetype"When checking AA keys by string, use lowercase:
if params.DoesExist("maxwidth") ' correct — matches runtime storageContentNode Field Defaults
Section titled “ContentNode Field Defaults”ContentNode fields cannot be invalid — they get type defaults when declared in XML. Use the “default = no data” pattern:
- Empty string
""= no data 0= no datafalse= no data- Check
item.typefor layout decisions, check field values for display decisions
API Boundary
Section titled “API Boundary”The Jellyfin API uses PascalCase field names (Id, Name, Type). These are transformed to lowerCamelCase at the API boundary in JellyfinDataTransformer. Never use PascalCase for internal data — the transformer is the conversion point.
' In JellyfinDataTransformer (the boundary)item.id = apiData.Id ?? "" ' PascalCase → `lowerCamelCase`item.name = apiData.Name ?? ""item.type = apiData.Type ?? ""
' In API request bodies (external contract — PascalCase required)FormatJson({ "Username": username, "Pw": password })Registry Keys — Do Not Rename
Section titled “Registry Keys — Do Not Rename”Registry keys like "saved_servers" and "active_user" are persisted on user devices. Renaming them requires a registry migration to avoid data loss. Treat existing registry key strings as frozen.
function vs sub
Section titled “function vs sub”Use function when returning a value. Use sub when performing a side effect with no return value.
function getSetting(key) as dynamic ' returns a value return registryRead(key, section)end function
sub setSetting(key, value) ' side effect only registryWrite(key, valueToString(value), section)end subGoto Labels
Section titled “Goto Labels”Use lowerCamelCase for goto labels. These are control flow markers, not constants or types.
startLogin: serverUrl = getSetting("server") ... goto startLoginTooling
Section titled “Tooling”| Tool | Config File | What It Enforces |
|---|---|---|
| bsfmt | bsfmt.json | Formatting (indentation, comment style, import sorting) |
| bslint | bslint.json | Case sensitivity, unused variables, unreachable code |
.editorconfig | .editorconfig | Indent style, line endings, trailing whitespace |
Not currently enforced by tooling: naming conventions (code review only), line length.
Run npm run lint to check formatting, linting, and compilation. Run npm run format to auto-fix formatting.