Skip to content

Commit fe1da58

Browse files
Fix general mistransformations (#202)
* Keep parentheses around type assertions * Fix function types with comments incorrectly formatted * Fix comment in between equals and expr in assignment leading to mistransformation * Clean clippy warnings
1 parent 6740fc3 commit fe1da58

File tree

11 files changed

+210
-39
lines changed

11 files changed

+210
-39
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Fixed
9+
- Fixed parentheses around type assertions being classed as unnecessary and removed under the `luau` feature flag.
10+
- Fixed mistransformation of function type where arguments have comments under the `luau` feature flag. ([#201](https://github.com/JohnnyMorganz/StyLua/issues/201))
11+
- Fixed comments in an assignment in between the equals token and the expression leading to a mistransformation. ([#200](https://github.com/JohnnyMorganz/StyLua/issues/200))
812

913
## [0.9.1] - 2021-06-17
1014
### Added

src/formatters/assignment.rs

Lines changed: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
#[cfg(feature = "luau")]
22
use full_moon::ast::types::TypeSpecifier;
3-
use full_moon::ast::{
4-
punctuated::{Pair, Punctuated},
5-
Assignment, Expression, LocalAssignment,
3+
use full_moon::tokenizer::{Token, TokenReference};
4+
use full_moon::{
5+
ast::{
6+
punctuated::{Pair, Punctuated},
7+
Assignment, Expression, LocalAssignment,
8+
},
9+
tokenizer::TokenType,
610
};
7-
use full_moon::tokenizer::{Token, TokenKind, TokenReference};
811

912
#[cfg(feature = "luau")]
1013
use crate::formatters::luau::format_type_specifier;
@@ -72,20 +75,22 @@ fn hang_equal_token<'ast>(
7275
ctx: &Context,
7376
equal_token: TokenReference<'ast>,
7477
shape: Shape,
78+
indent_first_item: bool,
7579
) -> TokenReference<'ast> {
76-
let equal_token_trailing_trivia = vec![
77-
create_newline_trivia(ctx),
78-
create_indent_trivia(ctx, shape.increment_additional_indent()),
79-
]
80-
.iter()
81-
.chain(
82-
// Remove the space that was present after the equal token
83-
equal_token
84-
.trailing_trivia()
85-
.skip_while(|x| x.token_kind() == TokenKind::Whitespace),
86-
)
87-
.map(|x| x.to_owned())
88-
.collect();
80+
let mut equal_token_trailing_trivia = vec![create_newline_trivia(ctx)];
81+
if indent_first_item {
82+
equal_token_trailing_trivia.push(create_indent_trivia(
83+
ctx,
84+
shape.increment_additional_indent(),
85+
))
86+
}
87+
88+
let equal_token_trailing_trivia = equal_token
89+
.trailing_trivia()
90+
.filter(|x| trivia_util::trivia_is_comment(x))
91+
.flat_map(|x| vec![Token::new(TokenType::spaces(1)), x.to_owned()])
92+
.chain(equal_token_trailing_trivia.iter().map(|x| x.to_owned()))
93+
.collect();
8994

9095
equal_token.update_trailing_trivia(FormatTriviaType::Replace(equal_token_trailing_trivia))
9196
}
@@ -102,7 +107,7 @@ fn attempt_assignment_tactics<'ast>(
102107
// If there is, we should put it on multiple lines
103108
if expressions.len() > 1 {
104109
// First try hanging at the equal token, using an infinite width, to see if its enough
105-
let hanging_equal_token = hang_equal_token(ctx, equal_token.to_owned(), shape);
110+
let hanging_equal_token = hang_equal_token(ctx, equal_token.to_owned(), shape, true);
106111
let hanging_shape = shape.reset().increment_additional_indent();
107112
let expr_list = format_punctuated(
108113
ctx,
@@ -131,6 +136,46 @@ fn attempt_assignment_tactics<'ast>(
131136
}
132137
} else {
133138
// There is only a single element in the list
139+
let expression = expressions.iter().next().unwrap();
140+
141+
// Special case: there is a comment in between the equals and the expression
142+
if trivia_util::token_contains_comments(&equal_token)
143+
|| !trivia_util::expression_leading_comments(expression).is_empty()
144+
{
145+
// We will hang at the equals token, and then format the expression as necessary
146+
let equal_token = hang_equal_token(ctx, equal_token, shape, false);
147+
148+
let shape = shape.reset().increment_additional_indent();
149+
150+
// As we know that there is only a single element in the list, we can extract it to work with it
151+
let expression = format_expression(ctx, expression, shape);
152+
153+
// We need to take all the leading trivia from the expr_list
154+
let (expression, leading_comments) =
155+
trivia_util::take_expression_leading_comments(&expression);
156+
157+
// Indent each comment and trail them with a newline
158+
let leading_comments = leading_comments
159+
.iter()
160+
.flat_map(|x| {
161+
vec![
162+
create_indent_trivia(ctx, shape),
163+
x.to_owned(),
164+
create_newline_trivia(ctx),
165+
]
166+
})
167+
.chain(std::iter::once(create_indent_trivia(ctx, shape)))
168+
.collect();
169+
170+
let expression =
171+
expression.update_leading_trivia(FormatTriviaType::Replace(leading_comments));
172+
173+
// Rebuild expression back into a list
174+
let expr_list = std::iter::once(Pair::new(expression, None)).collect();
175+
176+
return (expr_list, equal_token);
177+
}
178+
134179
// Create an example hanging the expression - we need to create a new context so that we don't overwrite it
135180
let hanging_expr_list = hang_punctuated_list(ctx, expressions, shape);
136181
let hanging_shape = shape.take_first_line(&strip_trivia(&hanging_expr_list));
@@ -147,7 +192,7 @@ fn attempt_assignment_tactics<'ast>(
147192
// Normal format is better: but check to see if the formatting is still over budget
148193
if formatting_shape.over_budget() {
149194
// Hang at the equal token
150-
let equal_token = hang_equal_token(ctx, equal_token, shape);
195+
let equal_token = hang_equal_token(ctx, equal_token, shape, true);
151196
// Add the expression list into the indent range, as it will be indented by one
152197
let shape = shape.increment_additional_indent();
153198
let expr_list = format_punctuated(ctx, expressions, shape, format_expression);
@@ -169,13 +214,14 @@ pub fn format_assignment<'ast>(
169214
let leading_trivia = vec![create_indent_trivia(ctx, shape)];
170215
let trailing_trivia = vec![create_newline_trivia(ctx)];
171216

172-
// Check if the assignment expressions contain comments. If they do, we bail out of determining any tactics
217+
// Check if the assignment expressions or equal token contain comments. If they do, we bail out of determining any tactics
173218
// and format multiline
174-
let contains_comments = assignment.expressions().pairs().any(|pair| {
175-
pair.punctuation()
176-
.map_or(false, |x| trivia_util::token_contains_comments(x))
177-
|| trivia_util::expression_contains_inline_comments(pair.value())
178-
});
219+
let contains_comments = trivia_util::token_contains_comments(assignment.equal_token())
220+
|| assignment.expressions().pairs().any(|pair| {
221+
pair.punctuation()
222+
.map_or(false, |x| trivia_util::token_contains_comments(x))
223+
|| trivia_util::expression_contains_inline_comments(pair.value())
224+
});
179225

180226
// Firstly attempt to format the assignment onto a single line, using an infinite column width shape
181227
let mut var_list = try_format_punctuated(
@@ -280,13 +326,16 @@ pub fn format_local_assignment<'ast>(
280326
if assignment.expressions().is_empty() {
281327
format_local_no_assignment(ctx, assignment, shape, leading_trivia, trailing_trivia)
282328
} else {
283-
// Check if the assignment expressions contain comments. If they do, we bail out of determining any tactics
329+
// Check if the assignment expression or equals token contain comments. If they do, we bail out of determining any tactics
284330
// and format multiline
285-
let contains_comments = assignment.expressions().pairs().any(|pair| {
286-
pair.punctuation()
287-
.map_or(false, |x| trivia_util::token_contains_comments(x))
288-
|| trivia_util::expression_contains_inline_comments(pair.value())
289-
});
331+
let contains_comments = assignment
332+
.equal_token()
333+
.map_or(false, |x| trivia_util::token_contains_comments(x))
334+
|| assignment.expressions().pairs().any(|pair| {
335+
pair.punctuation()
336+
.map_or(false, |x| trivia_util::token_contains_comments(x))
337+
|| trivia_util::expression_contains_inline_comments(pair.value())
338+
});
290339

291340
// Firstly attempt to format the assignment onto a single line, using an infinite column width shape
292341
let local_token = fmt_symbol!(ctx, assignment.local_token(), "local ", shape)

src/formatters/expression.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,17 @@ fn check_excess_parentheses(internal_expression: &Expression) -> bool {
7878
Expression::UnaryOperator { expression, .. } => check_excess_parentheses(expression),
7979
// Don't bother removing them if there is a binop, as they may be needed. TODO: can we be more intelligent here?
8080
Expression::BinaryOperator { .. } => false,
81-
Expression::Value { value, .. } => {
81+
Expression::Value {
82+
value,
83+
#[cfg(feature = "luau")]
84+
type_assertion,
85+
} => {
86+
// If we have a type assertion, we should always keep parentheses
87+
#[cfg(feature = "luau")]
88+
if type_assertion.is_some() {
89+
return false;
90+
}
91+
8292
match &**value {
8393
// Internal expression is a function call
8494
// We could potentially be culling values, so we should not remove parentheses

src/formatters/luau.rs

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@ use crate::{
44
formatters::{
55
expression::{format_expression, format_var},
66
general::{
7-
format_contained_span, format_end_token, format_symbol, format_token_reference,
7+
format_contained_span, format_end_token, format_punctuated,
8+
format_punctuated_multiline, format_symbol, format_token_reference,
89
try_format_punctuated, EndTokenType,
910
},
1011
table::{create_table_braces, TableType},
1112
trivia::{
1213
strip_leading_trivia, strip_trailing_trivia, FormatTriviaType, UpdateLeadingTrivia,
1314
UpdateTrailingTrivia,
1415
},
15-
trivia_util::{contains_comments, take_type_field_trailing_comments},
16+
trivia_util::{
17+
contains_comments, take_type_field_trailing_comments, token_trivia_contains_comments,
18+
},
1619
},
1720
shape::Shape,
1821
};
@@ -93,11 +96,49 @@ pub fn format_type_info<'ast>(
9396
arrow,
9497
return_type,
9598
} => {
96-
let parentheses = format_contained_span(ctx, parentheses, shape);
97-
let arguments =
98-
try_format_punctuated(ctx, arguments, shape, format_type_argument, None);
99+
let (start_parens, end_parens) = parentheses.tokens();
100+
101+
let force_multiline = token_trivia_contains_comments(start_parens.trailing_trivia())
102+
|| token_trivia_contains_comments(end_parens.leading_trivia())
103+
|| contains_comments(arguments);
104+
105+
let (parentheses, arguments, shape) = if force_multiline {
106+
let start_parens = fmt_symbol!(ctx, start_parens, "(", shape)
107+
.update_trailing_trivia(FormatTriviaType::Append(vec![
108+
create_newline_trivia(ctx),
109+
create_indent_trivia(ctx, shape.increment_additional_indent()),
110+
]));
111+
let end_parens =
112+
format_end_token(ctx, end_parens, EndTokenType::ClosingParens, shape)
113+
.update_leading_trivia(FormatTriviaType::Append(vec![
114+
create_newline_trivia(ctx),
115+
create_indent_trivia(ctx, shape),
116+
]));
117+
118+
let parentheses = ContainedSpan::new(start_parens, end_parens);
119+
120+
let arguments = format_punctuated_multiline(
121+
ctx,
122+
arguments,
123+
shape.reset(),
124+
format_type_argument,
125+
Some(1),
126+
);
127+
128+
let shape = shape.reset() + 1; // 1 = ")"
129+
130+
(parentheses, arguments, shape)
131+
} else {
132+
let parentheses = format_contained_span(ctx, &parentheses, shape);
133+
let arguments = format_punctuated(ctx, arguments, shape + 1, format_type_argument);
134+
let shape = shape + (2 + arguments.to_string().len()); // 2 = opening and closing parens
135+
136+
(parentheses, arguments, shape)
137+
};
138+
99139
let arrow = fmt_symbol!(ctx, arrow, " -> ", shape);
100140
let return_type = Box::new(format_type_info(ctx, return_type, shape));
141+
101142
TypeInfo::Callback {
102143
parentheses,
103144
arguments,

src/formatters/trivia_util.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::formatters::trivia::{FormatTriviaType, UpdateTrailingTrivia};
1+
use crate::formatters::trivia::{FormatTriviaType, UpdateLeadingTrivia, UpdateTrailingTrivia};
22
#[cfg(feature = "luau")]
33
use full_moon::ast::span::ContainedSpan;
44
#[cfg(feature = "luau")]
@@ -383,6 +383,23 @@ pub fn expression_leading_comments<'ast>(expression: &Expression<'ast>) -> Vec<T
383383
.collect()
384384
}
385385

386+
pub fn take_expression_leading_comments<'ast>(
387+
expression: &Expression<'ast>,
388+
) -> (Expression<'ast>, Vec<Token<'ast>>) {
389+
let trailing_comments = get_expression_leading_trivia(expression)
390+
.iter()
391+
.filter(|token| trivia_is_comment(token))
392+
.map(|x| x.to_owned())
393+
.collect();
394+
395+
(
396+
expression.update_leading_trivia(
397+
FormatTriviaType::Replace(vec![]), // TODO: Do we need to keep some trivia?
398+
),
399+
trailing_comments,
400+
)
401+
}
402+
386403
pub fn take_expression_trailing_comments<'ast>(
387404
expression: &Expression<'ast>,
388405
) -> (Expression<'ast>, Vec<Token<'ast>>) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
local foo = (bar :: any) :: number

tests/inputs-luau/types_comments.lua

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,10 @@ export type IntrospectionNamedTypeRef<
55
kind: any, -- deviation: add this type spec later: $PropertyType<T, 'kind'>,
66
name: string,
77
ofType: T -- TODO: this field is missing
8-
}
8+
}
9+
10+
export type ReactScopeQuery = (
11+
string, -- type
12+
{ [any]: any }, -- props
13+
any -- instance
14+
) -> boolean
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
local isValid =
2+
-- Allow nil for conditional declaration
3+
contextType == nil or
4+
(contextType["$$typeof"] == REACT_CONTEXT_TYPE and
5+
contextType._context == nil) -- Not a <Context.Consumer>
6+
7+
local isValid = -- Allow nil for conditional declaration
8+
foo
9+
10+
local isValid = -- test comment
11+
-- Allow nil for conditional declaration
12+
contextType == nil or
13+
(contextType["$$typeof"] == REACT_CONTEXT_TYPE and
14+
contextType._context == nil) -- Not a <Context.Consumer>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
source: tests/tests.rs
3+
expression: format(&contents)
4+
5+
---
6+
local foo = (bar :: any) :: number
7+

tests/snapshots/tests__luau@types_comments.lua.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ export type IntrospectionNamedTypeRef<
1212
ofType: T, -- TODO: this field is missing
1313
}
1414

15+
export type ReactScopeQuery = (
16+
string, -- type
17+
{ [any]: any }, -- props
18+
any -- instance
19+
) -> boolean
20+

0 commit comments

Comments
 (0)