Install & setup
npm install --save-dev kopytko-formatterAdd scripts to package.json:
{
"scripts": {
"format": "kopytko-format --write \"src/**/*.brs\"",
"format:check": "kopytko-format --check \"src/**/*.brs\""
}
}For VS Code format-on-save:
// .vscode/settings.json
{
"editor.formatOnSave": true,
"[brightscript]": {
"editor.defaultFormatter": "bchelkowski.kopytko"
}
}Example config
{
"indentSize": 4,
"endKeywordStyle": "spaced",
"thenStyle": "always",
"spaceAroundOperators": true,
"spaceAroundAssignment": true,
"commentStyle": "'",
"spaceAfterCommentMarker": true,
"sortImports": true,
"emptyLineAfterImports": true,
"emptyLinesBetweenFunctions": 1,
"maxEmptyLines": 2,
"trailingComma": "multiline",
"associativeArrayBracketSpacing": true,
"trimTrailingWhitespace": true,
"insertFinalNewline": true
}Config resolution: --config flag → kopytko-formatter.json → .vscode/settings.json (kopytko.format.*)
Live playground
Paste any BrightScript code and experiment with the options below — the output updates instantly. Try toggling End keywords, "then", and m. style to see the difference.
' @import /utils/z-helper.brs ' @import /utils/a-helper.brs sub processData(data,count,name) if count <> 0 then x = data.value + count m["total"] = x print "processing "+name endif for i = 0 to count-1 if data.items[i] <> invalid print data.items[i] endif endfor end sub
Before / after example
Unformatted BrightScript → formatted with the recommended config above.
' Unformatted input
function add(a ,b)
x=a+b
return x
end functionfunction add(a, b)
x = a + b
return x
end functionCasing
10 independent casing dimensions with per-identifier overrides. Values: preserve, upper-case, lower-case, capitalize, pascal-case, camel-case.
// .vscode/settings.json
{
"kopytko.casing.keyword": "lower-case",
"kopytko.casing.builtin": "pascal-case",
"kopytko.casing.type": "pascal-case",
"kopytko.casing.literal": "lower-case",
"kopytko.casing.logicOperator": "lower-case",
"kopytko.casing.userFunction": "camel-case",
"kopytko.casing.exact": {
"createObject": "pascal-case"
}
}FUNCTION add(A AS INTEGER, B AS INTEGER) AS INTEGER
RETURN A + B
END FUNCTIONfunction add(a as Integer, b as Integer) as Integer
return a + b
end function| Setting | Controls | Default |
|---|---|---|
kopytko.casing.builtin | Built-in functions (StrToI, Left, Right…) | preserve |
kopytko.casing.keyword | Keywords (if, for, function…) — fallback for sub-categories | preserve |
kopytko.casing.type | Type names (Integer, String, Object…) | falls back to keyword |
kopytko.casing.literal | true, false, invalid | falls back to keyword |
kopytko.casing.logicOperator | and, or, not | falls back to keyword |
kopytko.casing.mathOperator | mod | falls back to keyword |
kopytko.casing.method | ro* component methods | preserve |
kopytko.casing.userFunction | User-defined functions | preserve |
kopytko.casing.userMethod | User-defined AA methods | preserve |
kopytko.casing.exact | Per-identifier map of overrides | {} |
All options
49 formatting options, all defaulting to 'preserve'. Click any option's anchor to link directly.
Indentation & whitespace
indentSize number default: 4 Number of spaces per indent level when useTabs is false.
function main()
if true then
print "nested"
end if
end functionfunction main()
if true then
print "nested"
end if
end functionuseTabs boolean default: false Use tab characters instead of spaces for indentation.
function main()
print "spaces"
end functionfunction main()
print "tabs"
end functionmaxEmptyLines number default: 2 Maximum number of consecutive blank lines allowed. 0 = no limit.
function a()
end function
function b()
end functionfunction a()
end function
function b()
end functionemptyLinesBetweenFunctions number default: 1 Exact number of blank lines between top-level function/sub declarations.
function a()
end function
function b()
end functionfunction a()
end function
function b()
end functionemptyLinesBetweenMethods number default: 1 Exact number of blank lines between AA method definitions inside a builder function.
trimTrailingWhitespace boolean default: true Remove all trailing whitespace from the end of each line.
insertFinalNewline boolean default: true Ensure the file ends with exactly one newline character.
lineEnding 'lf' | 'crlf' | 'auto' default: 'auto' Line ending style. 'auto' preserves the file's existing line endings.
Compound keywords
endKeywordStyle 'spaced' | 'compact' | 'preserve' default: 'preserve' Controls whether compound end keywords use a space ('end if') or are compact ('endif').
if x = 1 then
print "one"
endif
while x > 0
x--
endwhileif x = 1 then
print "one"
end if
while x > 0
x--
end whilethenStyle 'always' | 'never' | 'multiline-only' | 'singleline-only' | 'preserve' default: 'preserve' Controls whether 'then' appears on if conditions.
if x = 1
print "one"
end if
if y > 0 then print "pos"if x = 1 then
print "one"
end if
if y > 0 then print "pos"Functions & subs
functionVsSubForVoid 'function' | 'sub' | 'allow-void' | 'preserve' default: 'preserve' Enforce consistent use of function vs sub for procedures that return nothing.
function doWork() as Void
print "work"
end functionsub doWork()
print "work"
end subspaceBeforeNamedFunctionParens boolean default: false Insert a space between the function name and its opening parenthesis in definitions.
function add(a, b)
return a + b
end functionfunction add (a, b)
return a + b
end functionspaceBeforeAnonymousFunctionParens boolean default: false Insert a space between the function keyword and its opening parenthesis in anonymous function expressions.
callback = function(x)
return x
end functioncallback = function (x)
return x
end functionspaceBeforeCallParens boolean default: false Insert a space between function name and '(' in call expressions.
result = add(1, 2)
print resultresult = add (1, 2)
print resultspaceInsideParens 'never' | 'always' default: 'never' Enforce spaces inside parentheses in calls and definitions.
result = add(1, 2)result = add( 1, 2 )paramAlignmentStyle 'indent' | 'align-to-paren' | 'preserve' default: 'preserve' How to align parameters when a function signature spans multiple lines. Note: in BrightScript, multi-line parameter lists require _ line-continuation characters. This pass is not yet implemented — the option is accepted but has no effect on formatting output.
Operators & expressions
spaceAroundOperators boolean default: true Enforce spaces around binary operators: +, -, *, /, \, ^, <, >, <=, >=, <>, and, or, mod.
x=a+b*c
valid=x>0 and x<100x = a + b * c
valid = x > 0 and x < 100spaceAroundAssignment boolean default: true Enforce spaces around = in standalone assignment statements.
x=1
name="hello"x = 1
name = "hello"unarySpacing boolean default: true Enforce a space after the unary 'not' operator.
result = not validresult = not validComments
commentStyle "'" | 'rem' | 'preserve' default: 'preserve' Normalize all comment markers to tick (') or rem.
rem initialize the screen
x = 1
' set the value
y = 2' initialize the screen
x = 1
' set the value
y = 2spaceAfterCommentMarker boolean default: true Enforce a single space between the comment marker (') and the comment text.
'No space after tick
x = 1 'Also no space' Space after tick
x = 1 ' Also spacecommentWidth number default: 0 Maximum line length for comment lines. 0 = no limit.
Imports
sortImports boolean default: false Sort @import statements alphabetically (module-relative imports first, then absolute paths).
' @import /utils/z-helper.brs
' @import /utils/a-helper.brs
' @import /components/Widget.brs' @import /components/Widget.brs
' @import /utils/a-helper.brs
' @import /utils/z-helper.brsemptyLineAfterImports boolean default: false Insert a blank line after the last @import line and before the first line of code.
' @import /utils/helper.brs
function main()
print "hello"
end function' @import /utils/helper.brs
function main()
print "hello"
end functionBlank lines
emptyLineAfterFunctionOpen boolean default: false Insert a blank line after the function/sub declaration line.
function main()
x = 1
end functionfunction main()
x = 1
end functionemptyLineBeforeFunctionClose boolean default: false Insert a blank line before end function/sub.
function main()
print "done"
end functionfunction main()
print "done"
end functionemptyLineBeforeReturn 'always' | 'not-alone' | false default: false Insert a blank line before return statements. 'not-alone' skips when return is the only statement.
function getValue()
x = compute()
return x
end functionfunction getValue()
x = compute()
return x
end functionemptyLineBeforeComment boolean default: false Enforce a blank line before a stand-alone comment line.
x = 1
' compute result
y = x + 1x = 1
' compute result
y = x + 1Control flow
parenthesisIfCase 'always' | 'never' | 'preserve' default: 'preserve' Control whether if conditions are wrapped in parentheses. Works on any condition expression including method calls and comparisons (e.g. 'if a.someMethod() > 0' → 'if (a.someMethod() > 0)').
if x = 1 then
print "one"
end if
if result > threshold then
print "ok"
end ifif (x = 1) then
print "one"
end if
if (result > threshold) then
print "ok"
end ifelseOnNewLine boolean default: true Intended to control else placement. This pass is not yet implemented — the option is accepted but has no effect on formatting output.
forLoopSpacing boolean default: true Enforce spaces around 'to' and 'step' keywords in for-loop headers.
for i=1 to 10 step 2
print i
end forfor i = 1 to 10 step 2
print i
end forArrays & associative arrays
associativeArrayBracketSpacing boolean default: true Spaces inside {} braces for inline associative arrays: { key: value } vs {key: value}.
data = {name: "Alice", age: 30}data = { name: "Alice", age: 30 }trailingComma 'never' | 'always' | 'multiline' default: 'never' Trailing comma after the last element in arrays/AA literals.
items = [
"apple",
"banana",
"cherry"
]items = [
"apple",
"banana",
"cherry",
]arrayCommaStyle 'always' | 'never' | 'preserve' default: 'preserve' Comma separators between entries in multi-line arrays. BrightScript allows omitting commas when entries are on separate lines.
items = [
"apple"
"banana"
]items = [
"apple",
"banana",
]associativeArrayCommaStyle 'always' | 'never' | 'preserve' default: 'preserve' Comma separators between entries in multi-line associative arrays. BrightScript allows omitting commas when entries are on separate lines.
data = {
name: "Alice"
age: 30
}data = {
name: "Alice",
age: 30,
}associativeArraySingleLineThreshold number default: 0 Force multi-line layout when an AA has more than this many keys. 0 = no threshold.
data = { a: 1, b: 2, c: 3, d: 4 }data = {
a: 1,
b: 2,
c: 3,
d: 4,
}arraySplitOpenBracket boolean default: false Split [{ onto separate lines for multi-item array-of-objects.
items = [{ id: 1, name: "a" }, { id: 2, name: "b" }]items = [
{ id: 1, name: "a" },
{ id: 2, name: "b" },
]BrightScript patterns
mPrefixStyle 'dot' | 'bracket' | 'preserve' default: 'preserve' Normalize m-scope field access to dot notation or bracket notation.
m["count"] = m["count"] + 1
m["name"] = "test"m.count = m.count + 1
m.name = "test"alignAssignments boolean default: false Align = signs in consecutive assignment statements.
firstName = "John"
lastName = "Doe"
age = 30
isActive = truefirstName = "John"
lastName = "Doe"
age = 30
isActive = trueobserveFieldStyle 'always-scoped' | 'preserve' default: 'preserve' Enforce observeFieldScoped() over observeField() for task/render-safe observation. Using non-scoped observeField() can cause memory leaks in long-lived Task nodes.
m.top.observeField("myField", "onChanged")m.top.observeFieldScoped("myField", "onChanged")fieldAccessConsistency 'dot' | 'method' | 'preserve' default: 'preserve' Normalize SceneGraph node field access between dot notation and getField()/setField() methods.
value = m.top.getField("myField")
m.top.setField("result", 42)value = m.top.myField
m.top.result = 42Miscellaneous
printStatement 'warn' | 'remove' | 'preserve' default: 'preserve' Flag or remove 'print' debug statements.
function main()
print "debug: x = " + x.toStr()
result = compute()
print "done"
return result
end functionfunction main()
result = compute()
return result
end functionlineCommentPosition 'above' | 'inline' | 'preserve' default: 'preserve' Move trailing line comments. 'above' puts them on the line above the code they annotate.
x = 1 ' the initial value' the initial value
x = 1maxLineLength number default: 120 Maximum line length. Lines exceeding this are flagged (works with wrapLongStrings). 0 = no limit.
wrapLongStrings 'plus' | 'array-join' | 'preserve' default: 'preserve' How to wrap string literals that exceed maxLineLength.
message = "This is a very long string that will definitely exceed the maximum configured line length limit"message = "This is a very long string that will definitely " + _
"exceed the maximum configured line length limit"stringConcatStyle 'preserve' | 'plus' | 'array-join' default: 'preserve' Style used when wrapping long strings that need concatenation: '+' operators or an array-join expression.
message = "This is a very long string that will definitely " + _
"exceed the maximum configured line length limit"message = ["This is a very long string that will definitely ", _
"exceed the maximum configured line length limit"].join("")emptyLinesAtBlockBoundaries 'strip' | 'enforce' | 'preserve' default: 'preserve' Control blank lines at the start and end of blocks (if, for, while bodies).
verifySyntax boolean default: true Re-parse the formatted output and abort the write if it fails to parse, to guard against formatter bugs corrupting source files.
Library API
import { formatText, checkFormatting } from 'kopytko-formatter';
// Format a BrightScript source string
const formatted = formatText(source, {
indentSize: 4,
endKeywordStyle: 'spaced',
spaceAroundOperators: true,
});
// Check if source is already formatted (returns boolean, no mutation)
const isClean = checkFormatting(source, config);
if (!isClean) {
console.log('File needs formatting');
}