Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Luau: Fixed the `read`/`write` access modifier merging into the element type in array type shorthand, changing the meaning of the code (`{ read Foo }` was formatted as `{ readFoo }`) ([#1126](https://github.com/JohnnyMorganz/StyLua/issues/1126))

## [2.5.2] - 2026-05-16

### Fixed
Expand Down
44 changes: 39 additions & 5 deletions src/formatters/luau.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,21 @@ fn format_type_info_internal(
|| contains_comments(access)
|| contains_comments(type_info);

let access = access.as_ref().map(|token_reference| {
format_token_reference(ctx, token_reference, shape + BRACKET_LEN)
});
// Format modifier comments at the multiline element indentation.
let access_shape = if contains_comments {
shape.increment_additional_indent()
} else {
shape + BRACKET_LEN
};

let access = access
.as_ref()
.map(|token_reference| format_token_reference(ctx, token_reference, access_shape));

// Count the modifier and its required separator without trivia.
let access_shape_increment = access
.as_ref()
.map_or(0, |token| token.to_string().len() + 1);
.map_or(0, |token| strip_trivia(token).to_string().len() + 1);

let (table_type, new_type_info) = if contains_comments {
(TableType::MultiLine, None)
Expand Down Expand Up @@ -343,10 +351,36 @@ fn format_type_info_internal(
),
};

let (access, type_info_leading_trivia) = match access {
Some(access) => {
// A trailing line comment must end before the element type.
let separator = if access.has_trailing_comments(CommentSearch::Single) {
vec![
create_newline_trivia(ctx),
create_indent_trivia(ctx, shape.increment_additional_indent()),
]
} else {
vec![Token::new(TokenType::spaces(1))]
};

(
Some(
access
.update_leading_trivia(leading_trivia)
.update_trailing_trivia(FormatTriviaType::Append(separator)),
),
FormatTriviaType::NoChange,
)
}
None => (None, leading_trivia),
};

TypeInfo::Array {
braces,
access,
type_info: Box::new(new_type_info.update_trivia(leading_trivia, trailing_trivia)),
type_info: Box::new(
new_type_info.update_trivia(type_info_leading_trivia, trailing_trivia),
),
}
}

Expand Down
40 changes: 40 additions & 0 deletions tests/inputs-luau/type-array-access-modifiers.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
type SingleRead = { read Service }
type SingleWrite = { write Service }

type MultiRead = {
-- keep this comment
read Service
}

type MultiWrite = {
-- and keep this one
write Service
}

type MultiNested = {
read {
Name: string,
Other: number,
}
}

type CommentBetweenLine = {
read -- between
Service
}

type CommentBetweenBlock = { read --[[between]] Service }

type ExcessWhitespace = { read Service }

type PropertyAccess = { read Name: string }
type IndexerAccess = { read [number]: Service }
type NoAccess = { Service }

type BoundaryFits = {
field: { read AccessModifierShapeAccountingBoundaryServiceTypeNameFillingTheAvailableColumnBudgetExactlyHereOkX | B },
}

type BoundaryHangs = {
field: { read AccessModifierShapeAccountingBoundaryServiceTypeNameFillingTheAvailableColumnBudgetExactlyHereOkXX | B },
}
50 changes: 50 additions & 0 deletions tests/snapshots/tests__luau@type-array-access-modifiers.lua.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
source: tests/tests.rs
expression: "format(&contents, LuaVersion::Luau)"
input_file: tests/inputs-luau/type-array-access-modifiers.lua
---
type SingleRead = { read Service }
type SingleWrite = { write Service }

type MultiRead = {
-- keep this comment
read Service
}

type MultiWrite = {
-- and keep this one
write Service
}

type MultiNested = {
read {
Name: string,
Other: number,
}
}

type CommentBetweenLine = {
read -- between
Service
}

type CommentBetweenBlock = {
read --[[between]] Service
}

type ExcessWhitespace = { read Service }

type PropertyAccess = { read Name: string }
type IndexerAccess = { read [number]: Service }
type NoAccess = { Service }

type BoundaryFits = {
field: { read AccessModifierShapeAccountingBoundaryServiceTypeNameFillingTheAvailableColumnBudgetExactlyHereOkX | B },
}

type BoundaryHangs = {
field: {
read AccessModifierShapeAccountingBoundaryServiceTypeNameFillingTheAvailableColumnBudgetExactlyHereOkXX | B
},
}