Skip to content

Commit fe90f8f

Browse files
committed
support continue and const stat
1 parent 35cca53 commit fe90f8f

8 files changed

Lines changed: 409 additions & 28 deletions

File tree

crates/emmylua_formatter/src/formatter/expr.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ fn format_index_expr(ctx: &FormatContext, plan: &FormatPlan, expr: &LuaIndexExpr
343343
let indent_tail = matches!(
344344
access_docs.first(),
345345
Some(DocIR::SyntaxToken(
346-
LuaTokenKind::TkDot | LuaTokenKind::TkColon
346+
LuaTokenKind::TkDot | LuaTokenKind::TkColon | LuaTokenKind::TkSafeNavigation
347347
))
348348
);
349349

@@ -2347,22 +2347,34 @@ fn format_ternary_expr(
23472347
plan: &FormatPlan,
23482348
expr: &LuaTernaryExpr,
23492349
) -> Vec<DocIR> {
2350+
if node_has_direct_comment_child(expr.syntax()) {
2351+
return vec![ir::source_node_trimmed(expr.syntax().clone())];
2352+
}
2353+
23502354
let Some(cond_expr) = expr.get_condition_expr() else {
23512355
return vec![ir::source_node(expr.syntax().clone())];
23522356
};
23532357
let Some((true_expr, false_expr)) = expr.get_true_false_exprs() else {
23542358
return vec![ir::source_node(expr.syntax().clone())];
23552359
};
2356-
let mut docs = format_expr(ctx, plan, &cond_expr);
2357-
docs.push(ir::space());
2358-
docs.push(ir::syntax_token(LuaTokenKind::TkTernary));
2359-
docs.push(ir::space());
2360-
docs.extend(format_expr(ctx, plan, &true_expr));
2361-
docs.push(ir::space());
2362-
docs.push(ir::syntax_token(LuaTokenKind::TkColon));
2363-
docs.push(ir::space());
2364-
docs.extend(format_expr(ctx, plan, &false_expr));
2365-
docs
2360+
2361+
let cond_docs = format_expr(ctx, plan, &cond_expr);
2362+
let true_docs = format_expr(ctx, plan, &true_expr);
2363+
let false_docs = format_expr(ctx, plan, &false_expr);
2364+
2365+
vec![ir::group(vec![
2366+
ir::list(cond_docs),
2367+
ir::indent(vec![
2368+
continuation_break_ir(true),
2369+
ir::syntax_token(LuaTokenKind::TkTernary),
2370+
ir::space(),
2371+
ir::list(true_docs),
2372+
continuation_break_ir(true),
2373+
ir::syntax_token(LuaTokenKind::TkColon),
2374+
ir::space(),
2375+
ir::list(false_docs),
2376+
]),
2377+
])]
23662378
}
23672379

23682380
fn try_format_simple_inline_closure_expr(
@@ -3249,6 +3261,9 @@ fn format_index_access_ir(
32493261
} else if index_token.is_colon() {
32503262
docs.push(ir::syntax_token(LuaTokenKind::TkColon));
32513263
docs.extend(format_named_index_key_ir(expr));
3264+
} else if index_token.is_safe_navigation() {
3265+
docs.push(ir::syntax_token(LuaTokenKind::TkSafeNavigation));
3266+
docs.extend(format_named_index_key_ir(expr));
32523267
} else if index_token.is_left_bracket() {
32533268
docs.push(ir::syntax_token(LuaTokenKind::TkLeftBracket));
32543269
if ctx.config.spacing.space_inside_brackets {

crates/emmylua_formatter/src/formatter/render/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ use self::control::{
3030
use self::helpers::*;
3131
pub(super) use self::statements::{
3232
format_statement_value_expr, has_direct_comment_before_token, render_assign_stat,
33-
render_call_expr_stat, render_empty_stat, render_header_exprs_with_leading_docs,
34-
render_local_stat, render_return_stat,
33+
render_break_stat, render_call_expr_stat, render_continue_stat, render_empty_stat,
34+
render_header_exprs_with_leading_docs, render_local_stat, render_return_stat,
3535
};
3636
use self::statements::{render_statement_align_split, render_statement_line_content};
3737

@@ -105,8 +105,11 @@ fn render_layout_node(
105105
render_aligned_block_layout_nodes(ctx, root, &syntax_plan.children, plan)
106106
}
107107
LuaSyntaxKind::LocalStat => render_local_stat(ctx, root, syntax_plan.syntax_id, plan),
108+
LuaSyntaxKind::ConstStat => render_local_stat(ctx, root, syntax_plan.syntax_id, plan),
108109
LuaSyntaxKind::AssignStat => render_assign_stat(ctx, root, syntax_plan.syntax_id, plan),
109110
LuaSyntaxKind::ReturnStat => render_return_stat(ctx, root, syntax_plan.syntax_id, plan),
111+
LuaSyntaxKind::BreakStat => render_break_stat(root, syntax_plan.syntax_id),
112+
LuaSyntaxKind::ContinueStat => render_continue_stat(root, syntax_plan.syntax_id),
110113
LuaSyntaxKind::WhileStat => render_while_stat(ctx, root, syntax_plan, plan),
111114
LuaSyntaxKind::ForStat => render_for_stat(ctx, root, syntax_plan, plan),
112115
LuaSyntaxKind::ForRangeStat => render_for_range_stat(ctx, root, syntax_plan, plan),

crates/emmylua_formatter/src/formatter/render/statements.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,21 @@ pub(crate) fn render_local_stat(
2424
return Vec::new();
2525
};
2626

27+
let is_const = stat.syntax().kind() == LuaKind::Syntax(LuaSyntaxKind::ConstStat);
28+
2729
if node_has_direct_comment_child(stat.syntax()) {
2830
return format_local_stat_trivia_aware(ctx, plan, &stat);
2931
}
3032

31-
let local_token = first_direct_token(stat.syntax(), LuaTokenKind::TkLocal);
33+
let keyword_kind = if is_const {
34+
LuaTokenKind::TkConst
35+
} else {
36+
LuaTokenKind::TkLocal
37+
};
38+
let local_token = first_direct_token(stat.syntax(), keyword_kind);
3239
let comma_token = first_direct_token(stat.syntax(), LuaTokenKind::TkComma);
3340
let assign_token = first_direct_token(stat.syntax(), LuaTokenKind::TkAssign);
34-
let mut docs = vec![token_or_kind_doc(
35-
local_token.as_ref(),
36-
LuaTokenKind::TkLocal,
37-
)];
41+
let mut docs = vec![token_or_kind_doc(local_token.as_ref(), keyword_kind)];
3842
docs.extend(token_right_spacing_docs(plan, local_token.as_ref()));
3943
let local_names: Vec<_> = stat.get_local_name_list().collect();
4044
for (index, local_name) in local_names.iter().enumerate() {
@@ -267,6 +271,14 @@ pub(crate) fn render_empty_stat(root: &LuaSyntaxNode, syntax_id: LuaSyntaxId) ->
267271
vec![ir::source_node_trimmed(node)]
268272
}
269273

274+
pub(crate) fn render_break_stat(_root: &LuaSyntaxNode, _syntax_id: LuaSyntaxId) -> Vec<DocIR> {
275+
vec![ir::syntax_token(LuaTokenKind::TkBreak)]
276+
}
277+
278+
pub(crate) fn render_continue_stat(_root: &LuaSyntaxNode, _syntax_id: LuaSyntaxId) -> Vec<DocIR> {
279+
vec![ir::syntax_token(LuaTokenKind::TkContinue)]
280+
}
281+
270282
fn format_local_stat_trivia_aware(
271283
ctx: &FormatContext,
272284
plan: &FormatPlan,
@@ -278,11 +290,14 @@ fn format_local_stat_trivia_aware(
278290
rhs_entries,
279291
} = collect_local_stat_entries(ctx, plan, stat);
280292
let syntax_id = stat.get_syntax_id();
281-
let local_token = first_direct_token(stat.syntax(), LuaTokenKind::TkLocal);
282-
let mut docs = vec![token_or_kind_doc(
283-
local_token.as_ref(),
284-
LuaTokenKind::TkLocal,
285-
)];
293+
let is_const = stat.syntax().kind() == LuaKind::Syntax(LuaSyntaxKind::ConstStat);
294+
let keyword_kind = if is_const {
295+
LuaTokenKind::TkConst
296+
} else {
297+
LuaTokenKind::TkLocal
298+
};
299+
let local_token = first_direct_token(stat.syntax(), keyword_kind);
300+
let mut docs = vec![token_or_kind_doc(local_token.as_ref(), keyword_kind)];
286301
let has_inline_comment = plan
287302
.layout
288303
.statement_trivia

crates/emmylua_formatter/src/formatter/spacing.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ fn analyze_token_spacing(ctx: &FormatContext, spacing: &mut SpacingModel, token:
220220
apply_space_rule(spacing, syntax_id, space_around_assign(ctx.config));
221221
}
222222
LuaTokenKind::TkLocal
223+
| LuaTokenKind::TkConst
224+
| LuaTokenKind::TkBreak
225+
| LuaTokenKind::TkContinue
223226
| LuaTokenKind::TkFunction
224227
| LuaTokenKind::TkIf
225228
| LuaTokenKind::TkWhile

crates/emmylua_formatter/src/test/statement_tests.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1904,4 +1904,102 @@ end
19041904
"#
19051905
);
19061906
}
1907+
1908+
// ========== LuaJIT extension: const ==========
1909+
1910+
#[test]
1911+
fn test_const_stat_format() {
1912+
use crate::format_text;
1913+
use emmylua_parser::LuaLanguageLevel;
1914+
let config = LuaFormatConfig::default();
1915+
let input = "const x = 1\n";
1916+
let expected = "const x = 1\n";
1917+
let result = format_text(input, LuaLanguageLevel::LuaJITExt, &config).formatted;
1918+
assert_eq!(result, expected);
1919+
}
1920+
1921+
#[test]
1922+
fn test_const_stat_assign_format() {
1923+
use crate::format_text;
1924+
use emmylua_parser::LuaLanguageLevel;
1925+
let config = LuaFormatConfig::default();
1926+
let input = "const a,b = 1,2\n";
1927+
let expected = "const a, b = 1, 2\n";
1928+
let result = format_text(input, LuaLanguageLevel::LuaJITExt, &config).formatted;
1929+
assert_eq!(result, expected);
1930+
}
1931+
1932+
// ========== LuaJIT extension: continue ==========
1933+
1934+
#[test]
1935+
fn test_continue_stat_format() {
1936+
use crate::format_text;
1937+
use emmylua_parser::LuaLanguageLevel;
1938+
let config = LuaFormatConfig::default();
1939+
let input = "while true do\n if x then\n continue\n end\nend\n";
1940+
let expected = "while true do\n if x then\n continue\n end\nend\n";
1941+
let result = format_text(input, LuaLanguageLevel::LuaJITExt, &config).formatted;
1942+
assert_eq!(result, expected);
1943+
}
1944+
1945+
// ========== LuaJIT extension: ternary ==========
1946+
1947+
#[test]
1948+
fn test_ternary_format_inline() {
1949+
use crate::format_text;
1950+
use emmylua_parser::LuaLanguageLevel;
1951+
let config = LuaFormatConfig::default();
1952+
let input = "local x = a ? b : c\n";
1953+
let expected = "local x = a ? b : c\n";
1954+
let result = format_text(input, LuaLanguageLevel::LuaJITExt, &config).formatted;
1955+
assert_eq!(result, expected);
1956+
}
1957+
1958+
#[test]
1959+
fn test_ternary_format_nested() {
1960+
use crate::format_text;
1961+
use emmylua_parser::LuaLanguageLevel;
1962+
let config = LuaFormatConfig::default();
1963+
let input = "local x = a ? b ? c : d : e\n";
1964+
let expected = "local x = a ? b ? c : d : e\n";
1965+
let result = format_text(input, LuaLanguageLevel::LuaJITExt, &config).formatted;
1966+
assert_eq!(result, expected);
1967+
}
1968+
1969+
// ========== LuaJIT extension: safe navigation ==========
1970+
1971+
#[test]
1972+
fn test_safe_navigation_dot_format() {
1973+
use crate::format_text;
1974+
use emmylua_parser::LuaLanguageLevel;
1975+
let config = LuaFormatConfig::default();
1976+
let input = "local x = obj?.field\n";
1977+
let expected = "local x = obj?.field\n";
1978+
let result = format_text(input, LuaLanguageLevel::LuaJITExt, &config).formatted;
1979+
assert_eq!(result, expected);
1980+
}
1981+
1982+
// ========== LuaJIT extension: nil-coalescing ==========
1983+
1984+
#[test]
1985+
fn test_nil_coalescing_format() {
1986+
use crate::format_text;
1987+
use emmylua_parser::LuaLanguageLevel;
1988+
let config = LuaFormatConfig::default();
1989+
let input = "local x = a ?? b\n";
1990+
let expected = "local x = a ?? b\n";
1991+
let result = format_text(input, LuaLanguageLevel::LuaJITExt, &config).formatted;
1992+
assert_eq!(result, expected);
1993+
}
1994+
1995+
#[test]
1996+
fn test_nil_coalescing_chain_format() {
1997+
use crate::format_text;
1998+
use emmylua_parser::LuaLanguageLevel;
1999+
let config = LuaFormatConfig::default();
2000+
let input = "local x = a ?? b ?? c\n";
2001+
let expected = "local x = a ?? b ?? c\n";
2002+
let result = format_text(input, LuaLanguageLevel::LuaJITExt, &config).formatted;
2003+
assert_eq!(result, expected);
2004+
}
19072005
}

crates/emmylua_parser/src/grammar/lua/expr.rs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -556,23 +556,50 @@ fn parse_suffixed_expr(p: &mut LuaParser) -> ParseResult {
556556

557557
loop {
558558
match p.current_token() {
559-
LuaTokenKind::TkDot | LuaTokenKind::TkColon | LuaTokenKind::TkLeftBracket => {
559+
LuaTokenKind::TkDot | LuaTokenKind::TkLeftBracket => {
560560
let m = cm.precede(p, LuaSyntaxKind::IndexExpr);
561561
if let Err(err) = parse_index_struct(p) {
562562
m.complete(p);
563563
return Err(err);
564564
}
565565
cm = m.complete(p);
566566
}
567-
LuaTokenKind::TkSafeNavigation => {
568-
let m = cm.precede(p, LuaSyntaxKind::SafeIndexExpr);
569-
p.bump(); // consume '?.'
570-
if let Err(err) = parse_safe_index_struct(p) {
567+
LuaTokenKind::TkColon => {
568+
if !is_colon_call_lookahead(p) {
569+
return Ok(cm);
570+
}
571+
let m = cm.precede(p, LuaSyntaxKind::IndexExpr);
572+
if let Err(err) = parse_index_struct(p) {
571573
m.complete(p);
572574
return Err(err);
573575
}
574576
cm = m.complete(p);
575577
}
578+
LuaTokenKind::TkSafeNavigation => {
579+
if matches!(
580+
p.peek_next_token(),
581+
LuaTokenKind::TkLeftParen
582+
| LuaTokenKind::TkLeftBrace
583+
| LuaTokenKind::TkString
584+
| LuaTokenKind::TkLongString
585+
) {
586+
let m = cm.precede(p, LuaSyntaxKind::CallExpr);
587+
p.bump(); // consume '?.'
588+
if let Err(err) = parse_args(p) {
589+
m.complete(p);
590+
return Err(err);
591+
}
592+
cm = m.complete(p);
593+
} else {
594+
let m = cm.precede(p, LuaSyntaxKind::SafeIndexExpr);
595+
p.bump(); // consume '?.'
596+
if let Err(err) = parse_safe_index_struct(p) {
597+
m.complete(p);
598+
return Err(err);
599+
}
600+
cm = m.complete(p);
601+
}
602+
}
576603
LuaTokenKind::TkLeftParen
577604
| LuaTokenKind::TkLongString
578605
| LuaTokenKind::TkString
@@ -625,6 +652,25 @@ fn parse_name_or_special_function(p: &mut LuaParser) -> ParseResult {
625652
Ok(cm)
626653
}
627654

655+
fn is_colon_call_lookahead(p: &LuaParser) -> bool {
656+
let name_token = p.peek_next_token();
657+
match name_token {
658+
LuaTokenKind::TkName => {
659+
let after_name = p.peek_nth_token(1);
660+
matches!(
661+
after_name,
662+
LuaTokenKind::TkLeftParen
663+
| LuaTokenKind::TkLeftBrace
664+
| LuaTokenKind::TkString
665+
| LuaTokenKind::TkLongString
666+
| LuaTokenKind::TkSafeNavigation
667+
)
668+
}
669+
LuaTokenKind::None | LuaTokenKind::TkEof => true,
670+
_ => false,
671+
}
672+
}
673+
628674
fn parse_index_struct(p: &mut LuaParser) -> Result<(), ParseFailReason> {
629675
let index_op_range = p.current_token_range();
630676
match p.current_token() {
@@ -683,6 +729,7 @@ fn parse_index_struct(p: &mut LuaParser) -> Result<(), ParseFailReason> {
683729
| LuaTokenKind::TkLeftBrace
684730
| LuaTokenKind::TkString
685731
| LuaTokenKind::TkLongString
732+
| LuaTokenKind::TkSafeNavigation
686733
) {
687734
p.push_error(LuaParseError::syntax_error_from(
688735
&t!(

0 commit comments

Comments
 (0)