Skip to content

Script Studio QA round five: function timerHandler, a find's place in SLua's count, blank lines kept, a brace-spacing setting, math.modf bare, converted SLua stable under format on save - #439

Merged
RyeMutt merged 12 commits into
developfrom
rye/script-qa5
Oct 11, 2026

Conversation

@RyeMutt

@RyeMutt RyeMutt commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

Tapple's fifth round of Script Studio QA, on one converted script.

  • The converter writes function timerHandler().
  • It compares a find's place in SLua's count (== 1).
  • setState says when a state is not there.
  • Blank lines between statements are kept.
  • A new setting decides whether SLua's braces are spaced inside, for both the formatter and the converter.

A later note from Tapple drops the brackets around math.modf where they do nothing.

Following the brace-spacing item through, converted SLua is now left unchanged by format on save. Over Tailslide's 73 test scripts, formatting the converted SLua changes nothing, with braces on or off and types on or off. The one exception is the 120-column width rule (see below).

Commits

The timer's handler is a function statement

  • A script with one state had timerHandler = function() ... end, assigning the local that the timers preamble declares above setTimer. It is now function timerHandler(), which assigns the same local and makes no global. A state's table field keeps timer = function().

setState stops at a state that is not there

  • setState left the old state and then iterated states[name]. A name with no state stopped the script with "attempt to iterate over a nil value", with no handlers left on.
  • The new state is now looked up first. A missing one stops the script with error("unknown state: " .. name, 2), reported at the setState("...") call, before anything is left.
  • Converted code can't hit this (LSL's compiler checked state names), but an edit afterwards can.

A find's place compared with a number, in SLua's count

  • llSubStringIndex(item, P) == 0 came out as (ll.SubStringIndex(item, P) or 0) - 1 == 0. It is now ll.SubStringIndex(item, P) == 1.
  • The same goes for ~= and orders, which keep (x or 0), such as (x or 0) > 1. It works with the number on either side, under a not, and for table.find. A presence test is written against nil.
  • The result is the same whatever the function returns. An == against a number below -1 is left as written, and a place read as a number keeps (x or 0) - 1.
  • A presence test still uses string.find. A test of where keeps ll.SubStringIndex, since string.find counts bytes, not characters.

Blank lines between statements are kept

  • A blank line between two statements, or two globals, was dropped unless a comment marked it. One blank line is now kept wherever the LSL had any.
  • None is added at the start or end of a block.
  • A comment kept apart as a block's first line no longer leaves a blank line under the block's opening line.

Spaces inside SLua's braces, as a setting

  • New setting ALScriptFormatBraceSpaces, on by default, under Preferences > Editor > Formatting. The formatter spaces the inside of Luau's braces by it: { 1, 2 } and { any } as StyLua writes them, or {1, 2} as Luau's docs and SLua's tests do.
  • The formatter also spaced a table's first part only next to a name, number or string. { [string]: (...any) -> () } came out as {[string]:(...any) -> () }, and {{1}} as {{ 1 } }. Both braces are now spaced alike, and a bracket after a colon is spaced as written.
  • The converter wrote lists as {"\n"} and list types as { any }, two styles on one line. Its tables and table types (lists, rule tables, table.extend's, the states' type, the helpers) now follow the same setting.
  • {} and an interpolated string's braces are never touched.

The formatter keeps a sign against what it signs

  • -(a) came out as - (a), #{ 1 } as # { 1 }, and LSL's !(b) as ! (b).
  • A sign after a < that compares was put against it: i < -1 became i <-1, in Luau and LSL alike. Only a < that opens an LSL vector does that now.
  • x - #t became x -#t.

The formatter keeps what the author lined up

  • The converter lines up again what the LSL lined up (QA3), and the formatter put every = back to one space.
  • A run is lines in a row at one depth whose =, or whose trailing comments, the author put at one column, padded wider than one space somewhere. A comment on a line of its own does not break a run.
  • The run keeps that column, or goes just past the longest line where it no longer fits. Tabs count to their stops.

Comments over an elseif keep its level

  • A comment written over an elseif/else/until/end/}, at that line's level, was pushed into the block above. The converter writes its notes on an elseif's or until's condition that way.
  • Such a comment now stays where the author wrote it out of the block: no further in than the line under it, and less far than the code above it.
  • A comment written as far in as the block stays in it. So does one in unindented text, where nothing tells either way.

A comment after an unbraced body trails its statement

  • llDialog(...); // present submenu after an unbraced then came out on its own line before elseif, inside the else branch, or above the whole if or loop.
  • It now trails the statement that ends its line. After an empty body it trails the line that opens it (if c then -- why).

Converted SLua ends with one line break

  • The converted text ended with a blank line, which the formatter drops.

math.modf bare where one value is read anyway

  • LSL's (integer) of a float was always (math.modf(x)). The brackets keep a two-value call to its first, which matters only where its values would spread.
  • (integer)llFrand(1000) - 10000 is now math.modf(ll.Frand(1000)) - 10000. The call is likewise bare as an operator's operand, a compound assignment's value, and the whole value given to a variable or a local.
  • The brackets stay in arguments (tostring((math.modf(f)))), list items, return, and a vector part assigned, which becomes an argument of vector().
  • math.random(0, 999) is not substituted for (integer)llFrand(1000). The converter never changes the source of randomness.

Not done

  • At the default width of 120, format on save still breaks 68 converted lines in 13 of the corpus scripts. The converter keeps the LSL's own line breaks, so long LSL lines stay long. Whether the converter should break them itself is left open.

Testing

  • alscript ctest 77/77 on macOS (RelWithDebInfo), with both Luau solvers for the converter.
  • Rebased onto develop with the test modernisation (Test framework, stage 1: every test ready to share a binary #436); no conflicts.
  • New tests: allsltoslua 86, 87, 88, 89, 95 and 96; alscriptformatter 12, 13, 14 and 15. allsltoslua 4 and 68 gained cases, and the expectations of sixteen others changed for the brace spacing.
  • allsltoslua 88 and 89 convert a script and check that formatting it changes nothing, with braces on and off.
  • The two viewer files changed (alfloaterscriptstudio.cpp, alfloaterscriptpreferences.cpp) compile. The full viewer was not built, and the XUI parses.
  • Not yet checked by hand: the new Preferences checkbox. With it off, converting and formatting should give {1, 2} throughout; with it on, { 1, 2 }.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ec968283-9c3f-4f57-802f-5fbb22348b4b



📥 Commits

Reviewing files that changed from the base of the PR and between 9290594 and 1dc2935.




📒 Files selected for processing (10)
  • indra/alscript/core/alscriptformatter.cpp
  • indra/alscript/core/alscriptformatter.h
  • indra/alscript/lsl/allsltoslua.cpp
  • indra/alscript/lsl/allsltoslua.h
  • indra/alscript/tests/allsltoslua_test.cpp
  • indra/alscript/tests/alscriptformatter_test.cpp
  • indra/newview/alfloaterscriptpreferences.cpp
  • indra/newview/alfloaterscriptstudio.cpp
  • indra/newview/app_settings/settings_alchemy.xml
  • indra/newview/skins/default/xui/en/floater_script_studio_prefs.xml



Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.





📝 Summary

Summary by CodeRabbit

  • New Features

    • Added a preference to control spaces inside SLua table and type braces, enabled by default.
    • SLua conversion now translates comparisons involving list-search results into equivalent index or nil checks.
    • Unknown destinations in multi-state setState calls are reported before the current state is exited.
  • Improvements

    • Formatting better aligns assignments and trailing comments, preserves relevant blank lines, and handles comment placement and operator spacing more consistently.
    • Converted output retains source blank lines between statements while avoiding excess trailing blank lines.
📝 Summary
📝 Summary

Walkthrough

The formatter and LSL-to-SLua converter add configurable brace spacing and related formatting changes. The converter also updates find-result comparisons, comment placement, blank-line preservation, and destination-state validation. Script Studio preferences expose the brace-spacing option.

Changes

Script Formatting and Conversion

Layer / File(s) Summary
Formatter spacing rules
indra/alscript/core/alscriptformatter.h, indra/alscript/core/alscriptformatter.cpp, indra/alscript/tests/alscriptformatter_test.cpp
The formatter adds configurable Luau brace spacing and changes spacing around unary operators, comparisons, and LSL vectors.
Formatter comment indentation and alignment
indra/alscript/core/alscriptformatter.cpp, indra/alscript/tests/alscriptformatter_test.cpp
The formatter adjusts comment indentation and aligns qualifying assignment and trailing-comment runs.
Converter brace spacing and preference wiring
indra/alscript/lsl/allsltoslua.h, indra/alscript/lsl/allsltoslua.cpp, indra/alscript/tests/allsltoslua_test.cpp, indra/newview/alfloaterscriptstudio.cpp, indra/newview/alfloaterscriptpreferences.cpp, indra/newview/app_settings/settings_alchemy.xml, indra/newview/skins/default/xui/en/floater_script_studio_prefs.xml
The converter uses a brace-spacing option for table literals and table types. Script Studio stores the preference and passes it to formatting and conversion.
Find-result comparisons and state handling
indra/alscript/lsl/allsltoslua.cpp, indra/alscript/tests/allsltoslua_test.cpp
The converter rewrites supported comparisons involving find results and checks for an unknown destination state before current-state cleanup. Timer handler output and tests are also updated.
Converted comments and blank-line preservation
indra/alscript/lsl/allsltoslua.cpp, indra/alscript/tests/allsltoslua_test.cpp
The converter changes comment placement around bare statements and empty bodies, preserves source blank-line separation, and removes excess trailing blank lines.

Priority: ⬇️ Low

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Suggested reviewers: taylnos





Merge Risk: ⚪ Minimal · up to 1dc29

No concrete issue is identified that prevents merging after normal checks.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 24.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 81 functions across 8 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title accurately summarizes the main Script Studio QA changes, including timer handling, SLua conversion fixes, blank-line preservation, brace spacing, and format-on-save stability. It is lengthy,…
Description check Passed The description provides detailed context, change-specific sections, testing results, known limitations, and implementation details. It does not reproduce the template headings, issue link, checklist …

Full details: Docstring Coverage

Explanation

Docstring coverage is 24.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 81 functions across 8 files. (2 skipped: 2 unsupported.)


  • Fix all pre-merge checks with AI
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks each curly brace,
Then hops through lines in tidy space.
Comments find their proper place,
Old blank lines leave a measured trace,
And scripts emerge with clearer face.

Comment @coderabbitai help to get the list of available commands.

RyeMutt and others added 12 commits October 11, 2026 02:28
A script with one state, its timer on LLTimers, had its timer handler
written `timerHandler = function() ... end`, assigning the local that
the timers preamble declares above setTimer. `function timerHandler()`
assigns the same local, since a function statement of a local's name
makes no global, and is how Lua writes it. With several states the
handler is still a field of the state's table.

allsltoslua 68, typed too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t is in

The converter's setState left the state it was in, running its
state_exit and taking its handlers off, and then iterated states[name]
as it was. A name with no state stopped the script with "attempt to
iterate over a nil value", inside setState, with no handlers left on.
LSL's compiler refused such a name, so converted code never does it,
but an edit afterwards may. The state entered is now looked up first,
and a name with none stops the script with "unknown state: <name>",
said at the call that named it, before anything is left.

allsltoslua 4, typed too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SLua's ll.SubStringIndex, and table.find for one item looked for in a
list, count from 1 and answer nil for none. The converter makes that
LSL's count, `(x or 0) - 1`, wherever the answer is used, and tests
only whether anything was found against nil. A place compared with a
number written out kept the count's arithmetic: llSubStringIndex(item,
P) == 0 came out as (ll.SubStringIndex(item, P) or 0) - 1 == 0.

Such a comparison is now made in SLua's count, the number moved on by
one: ll.SubStringIndex(item, P) == 1, ~= 3, (x or 0) > 1 for an order,
with the place on the left where the number was written first. It is
the same test whatever the function answers. An == or ~= with a number
below -1 is left as written, and a place read as a number keeps LSL's
count.

allsltoslua 86.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The converter kept a blank line written over a comment, but dropped one
between two statements with no comment between them. So the author's
grouping survived only where a comment marked it: a function's `if
(...) return FALSE;`, a blank line, and `return TRUE;` came out with
the two lines together. The same went for globals.

A statement or global with one or more blank lines between it and the
one written before it now has one blank line over it. A global after a
function the LSL had between them counts as apart, since the function
is written later. None is written at a block's start, nor a second
where a comment over the statement keeps its own. A comment kept apart
as a block's first line no longer leaves a blank line under the line
that opens the block.

allsltoslua 87.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The formatter put a space inside Luau's braces, as StyLua does, but
only where a name, number or string stood next to them. A brace, a
bracket or a sign did not get one, so the converter's own state table
type, `{ [string]: { [string]: (...any) -> () } }`, came out as
`{[string]: {[string]:(...any) -> () } }`, and `{{1}, {2}}` as
`{{ 1 }, { 2 } }`. A bracket after a colon lost its space too, a rule
for a method call that no Luau call has.

Inside a table's braces, or a table type's, the first part and the
closing brace are now spaced alike whatever they are, and a bracket
after a colon is spaced as written. Whether there is a space at all is
a setting, ALScriptFormatBraceSpaces, on by default: StyLua writes
{ 1, 2 }, and Luau's own documentation and SLua's tests write {1, 2}.
It is under Preferences > Editor > Formatting. {} and an interpolated
string's braces are never spaced, and LSL's braces are as they were.

alscriptformatter 12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The converter wrote a list as {"\n"}, with no spaces, but a list's type
as { any } and a rule table as { auto_play = true }. Tapple's converted
line, `local params: { any } = ll.ParseStringKeepNulls(msg, {"\n"},
{})`, held both, and format on save then changed every list.

Tables and table types are now written by ALScriptFormatBraceSpaces,
the formatter's setting: { "\n" } and { any } by default, as StyLua and
the formatter write them, and {"\n"} and {any} with it off. A list over
several lines takes the spaces where an item shares a line with a
brace. An interpolated string's braces and {} are as they were.
Converted text's braces are then as format on save would leave them.

allsltoslua 88, and the expectations of sixteen others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aced

Three slips in the formatter's spacing, found formatting converted
SLua. A sign before a bracket or a brace was spaced from it: -(a) came
out as - (a), #{1, 2} as # { 1, 2 }, and LSL's !(b) as ! (b). A sign
after a < was always put against it, since LSL's < opens a vector
there, but a < that compares does not: Luau's i < -1 came out as
i <-1, i < #t as i <#t, and LSL's if (i < -1) as i <-1. And a sign
after a minus was put against it as if the minus were a sign too, so
x - #t came out as x -#t.

A sign standing as one, where nothing it could be an operator of comes
before it, is now written against the bracket or brace after it. After
a <, it is only where that < opened an LSL vector, and after a minus
only where the minus is a sign itself.

alscriptformatter 13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…level

A comment on a line of its own was indented as far as the block it
stood in. One the author wrote over an `elseif`, `else`, `until`, `end`
or `}`, at that line's level, was pushed into the block before it. The
converter writes its notes on an elseif's or an until's condition that
way, over the line they are about, so format on save moved them into
the wrong block.

Such a comment now keeps the level of the line under it where the
author wrote it out of the block: no further in than that line, and
less far than the code over it in the block. In a block with no code
before it, the author's indentation decides, wherever the text is
indented at all. A comment written as far in as the block, or in text
with no indentation to tell, is the block's end and stays in it.

alscriptformatter 15.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The formatter put one space around every `=`, so a run of declarations
the author had lined up came apart, and comments lined up after them
came apart with the code. The converter lines up again what the LSL
lined up (QA3, 91b94b5), so format on save undid it in converted
scripts too.

A run is lines one after another at one depth whose `=`, its first
outside brackets, or whose trailing comments the author put at one
column, with more than one space before at least one of them to show
it was meant. A comment on a line of its own at that depth does not
break a run. Once each line is spaced afresh, the run's `=` or comments
go to that column again, or just past the longest line where it no
longer fits. A run of comments whose code did not change keeps its
blanks as written. Columns are measured with tabs to their stops, so
LSL lined up with tabs is kept, in spaces.

alscriptformatter 14.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A comment at the end of the line an unbraced body ends was given to
the wrong statement. After an if's `then` body it went over the
`else`: `llDialog(...); // present submenu` came out on a line of its
own before `elseif`, at the if's level, or as the first line of the
else branch. After the last branch of an if, or a loop's body, it went
over the whole if or loop, since a comment after one of them would
trail its first line.

Such a comment now trails the statement that ends its line, found down
the if's last branch or the loop's body: `print("a") -- after a`. Where
that is an empty body, `if (c); // why`, it trails the line that opens
the body, `if c then -- why`. An if written on one line, with no else,
still takes it after its `end`.

allsltoslua 95.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two things were still changed when converted SLua was formatted. The
text ended with a blank line, which the formatter drops. And the
lslInteger and lslFloat helpers wrote `{ s }` whatever the brace
setting asked, so with ALScriptFormatBraceSpaces off the formatter
changed them to `{s}`.

The text now ends with one line break, and the helpers' braces follow
the setting. Over Tailslide's 73 test scripts, formatting the
converted SLua now changes nothing, with either brace setting and with
types on or off, but where the width rule breaks a line longer than
the width.

allsltoslua 89, which converts a script with what used to change and
checks that formatting it changes nothing, either brace spacing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nyway

LSL's (integer) of a float is written as math.modf's whole part,
always as (math.modf(x)). The brackets keep a call that answers two
values to its first, which matters only where its values would spread:
a call's last argument, a table's last item, a return. As an operand
they did nothing, so `(integer)llFrand(1000) - 10000` came out as
`(math.modf(ll.Frand(1000))) - 10000`.

The call is now bare where only its first value is read anyway: an
operator's operand, a compound assignment's value, and all of the
value given a variable or a local, `x = math.modf(f) - 10000`. The
brackets stay where the values would spread, including a vector's part
assigned, which is made an argument of vector().

allsltoslua 96.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RyeMutt RyeMutt changed the title Script Studio QA round five: function timerHandler, a find's place in SLua's count, blank lines kept, a brace-spacing setting, converted SLua stable under format on save Script Studio QA round five: function timerHandler, a find's place in SLua's count, blank lines kept, a brace-spacing setting, math.modf bare, converted SLua stable under format on save Oct 11, 2026
@RyeMutt
RyeMutt merged commit 42ee42a into develop Oct 11, 2026
16 checks passed
@RyeMutt
RyeMutt deleted the rye/script-qa5 branch October 11, 2026 09:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant