diff --git a/deployment/schema.json b/deployment/schema.json index 50c4f0598..13e5b70d0 100644 --- a/deployment/schema.json +++ b/deployment/schema.json @@ -645,6 +645,18 @@ "description": "Ex. `function()`" }] }, + "functionExpression.flatIife": { + "description": "Whether to skip indenting the body of a function expression used in an IIFE.", + "type": "boolean", + "default": false, + "oneOf": [{ + "const": true, + "description": "Does not indent the body of the function in `(function() { ... })();`." + }, { + "const": false, + "description": "Indents the body as normal." + }] + }, "getAccessor.spaceBeforeParentheses": { "description": "Whether to add a space before the parentheses of a get accessor.", "type": "boolean", @@ -973,6 +985,9 @@ "functionExpression.spaceAfterFunctionKeyword": { "$ref": "#/definitions/functionExpression.spaceAfterFunctionKeyword" }, + "functionExpression.flatIife": { + "$ref": "#/definitions/functionExpression.flatIife" + }, "getAccessor.spaceBeforeParentheses": { "$ref": "#/definitions/getAccessor.spaceBeforeParentheses" }, diff --git a/src/configuration/builder.rs b/src/configuration/builder.rs index aeaaa4d36..bfa35df54 100644 --- a/src/configuration/builder.rs +++ b/src/configuration/builder.rs @@ -348,6 +348,14 @@ impl ConfigurationBuilder { self.insert("functionExpression.spaceAfterFunctionKeyword", value.into()) } + /// Whether to skip indenting the body of a function expression used in an IIFE. + /// + /// `true` - Does not indent the body of the function in `(function() { ... })();`. + /// `false` (default) - Indents the body as normal. + pub fn function_expression_flat_iife(&mut self, value: bool) -> &mut Self { + self.insert("functionExpression.flatIife", value.into()) + } + /// Whether to add a space before the parentheses of a get accessor. /// /// `true` - Ex. `get myProp ()` diff --git a/src/configuration/resolve_config.rs b/src/configuration/resolve_config.rs index 486b23656..ea393be31 100644 --- a/src/configuration/resolve_config.rs +++ b/src/configuration/resolve_config.rs @@ -289,6 +289,7 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration) function_declaration_space_before_parentheses: get_value(&mut config, "functionDeclaration.spaceBeforeParentheses", false, &mut diagnostics), function_expression_space_before_parentheses: get_value(&mut config, "functionExpression.spaceBeforeParentheses", false, &mut diagnostics), function_expression_space_after_function_keyword: get_value(&mut config, "functionExpression.spaceAfterFunctionKeyword", false, &mut diagnostics), + function_expression_flat_iife: get_value(&mut config, "functionExpression.flatIife", false, &mut diagnostics), get_accessor_space_before_parentheses: get_value(&mut config, "getAccessor.spaceBeforeParentheses", false, &mut diagnostics), if_statement_space_after_if_keyword: get_value(&mut config, "ifStatement.spaceAfterIfKeyword", true, &mut diagnostics), import_declaration_space_surrounding_named_imports: get_value(&mut config, "importDeclaration.spaceSurroundingNamedImports", true, &mut diagnostics), @@ -353,8 +354,8 @@ pub fn resolve_config(config: ConfigKeyMap, global_config: &GlobalConfiguration) #[cfg(test)] mod tests { - use dprint_core::configuration::resolve_global_config; use dprint_core::configuration::NewLineKind; + use dprint_core::configuration::resolve_global_config; use super::super::builder::ConfigurationBuilder; use super::*; diff --git a/src/configuration/types.rs b/src/configuration/types.rs index 1e67d7b77..61b08bf7a 100644 --- a/src/configuration/types.rs +++ b/src/configuration/types.rs @@ -604,6 +604,8 @@ pub struct Configuration { pub function_expression_space_before_parentheses: bool, #[serde(rename = "functionExpression.spaceAfterFunctionKeyword")] pub function_expression_space_after_function_keyword: bool, + #[serde(rename = "functionExpression.flatIife")] + pub function_expression_flat_iife: bool, #[serde(rename = "getAccessor.spaceBeforeParentheses")] pub get_accessor_space_before_parentheses: bool, #[serde(rename = "ifStatement.spaceAfterIfKeyword")] diff --git a/src/generation/context.rs b/src/generation/context.rs index 13b23f7f8..2f50f4c5e 100644 --- a/src/generation/context.rs +++ b/src/generation/context.rs @@ -68,6 +68,7 @@ pub struct Context<'a> { stored_lsil: FxHashMap<(SourcePos, SourcePos), LineStartIndentLevel>, stored_ln: FxHashMap<(SourcePos, SourcePos), LineNumber>, stored_il: FxHashMap<(SourcePos, SourcePos), IndentLevel>, + pub skip_iife_body_indent: bool, pub end_statement_or_member_lns: Stack, before_comments_start_info_stack: Stack<(SourceRange, LineNumber, IsStartOfLine)>, if_stmt_last_brace_condition_ref: Option, @@ -102,6 +103,7 @@ impl<'a> Context<'a> { stored_lsil: FxHashMap::default(), stored_ln: FxHashMap::default(), stored_il: FxHashMap::default(), + skip_iife_body_indent: false, end_statement_or_member_lns: Default::default(), before_comments_start_info_stack: Default::default(), if_stmt_last_brace_condition_ref: None, diff --git a/src/generation/generate.rs b/src/generation/generate.rs index 5c5ad0772..47ca3b54b 100644 --- a/src/generation/generate.rs +++ b/src/generation/generate.rs @@ -2638,7 +2638,27 @@ fn gen_expr_with_type_args<'a>(node: &TsExprWithTypeArgs<'a>, context: &mut Cont items } +fn is_iife_fn_expr(node: &FnExpr) -> bool { + let mut current: Node = node.into(); + while let Some(parent) = current.parent() { + if parent.is::() { + current = parent; + continue; + } + return match parent { + Node::CallExpr(call) => call.callee.range() == current.range(), + Node::OptCall(call) => call.callee.range() == current.range(), + _ => false, + }; + } + false +} + fn gen_fn_expr<'a>(node: &FnExpr<'a>, context: &mut Context<'a>) -> PrintItems { + if context.config.function_expression_flat_iife && is_iife_fn_expr(node) { + context.skip_iife_body_indent = true; + } + let items = gen_function_decl_or_expr( FunctionDeclOrExprNode { node: node.into(), @@ -9475,6 +9495,8 @@ struct GenBlockOptions<'a> { fn gen_block<'a>(gen_inner: impl FnOnce(Vec>, &mut Context<'a>) -> PrintItems, opts: GenBlockOptions<'a>, context: &mut Context<'a>) -> PrintItems { let mut items = PrintItems::new(); + let skip_indent = context.skip_iife_body_indent; + context.skip_iife_body_indent = false; let before_open_token_ln = LineNumber::new("after_open_token_info"); let first_member_range = opts.children.first().map(|x| x.range()); let range = opts.range; @@ -9493,7 +9515,8 @@ fn gen_block<'a>(gen_inner: impl FnOnce(Vec>, &mut Context<'a>) -> Prin items.push_signal(Signal::NewLine); } items.push_line_and_column(start_inner_lc); - items.extend(ir_helpers::with_indent(gen_inner(opts.children, context))); + let inner = gen_inner(opts.children, context); + items.extend(if skip_indent { inner } else { ir_helpers::with_indent(inner) }); items.push_line_and_column(end_inner_lc); if is_tokens_same_line_and_empty { diff --git a/tests/specs/expressions/FunctionExpression/FunctionExpression_FlatIife_True.txt b/tests/specs/expressions/FunctionExpression/FunctionExpression_FlatIife_True.txt new file mode 100644 index 000000000..38190c727 --- /dev/null +++ b/tests/specs/expressions/FunctionExpression/FunctionExpression_FlatIife_True.txt @@ -0,0 +1,154 @@ +~~ functionExpression.flatIife: true ~~ +== should not indent body of IIFE == +(function() { + const a = "foobar"; + return { a }; +})(); + +[expect] +(function() { +const a = "foobar"; +return { a }; +})(); + +== should not indent body of named IIFE == +(function init() { + const a = "foobar"; + return { a }; +})(); + +[expect] +(function init() { +const a = "foobar"; +return { a }; +})(); + +== should not indent body of IIFE with args == +(function(x) { + const a = x; + return { a }; +})(42); + +[expect] +(function(x) { +const a = x; +return { a }; +})(42); + +== should still indent body of regular function expression == +const fn = function() { + const a = "foobar"; + return { a }; +}; + +[expect] +const fn = function() { + const a = "foobar"; + return { a }; +}; + +== should still indent body of function expression in call argument == +call(function() { + const a = "foobar"; + return { a }; +}); + +[expect] +call(function() { + const a = "foobar"; + return { a }; +}); + +== should not affect arrow function IIFE == +(() => { + const a = "foobar"; + return { a }; +})(); + +[expect] +(() => { + const a = "foobar"; + return { a }; +})(); + +== should handle IIFE with nested function == +(function() { + function inner() { + return 1; + } + return inner(); +})(); + +[expect] +(function() { +function inner() { + return 1; +} +return inner(); +})(); + +== should handle empty IIFE == +(function() {})(); + +[expect] +(function() {})(); + +== should not indent body of IIFE wrapped in extra parens == +((function() { + const a = "foobar"; + return { a }; +}))(); + +[expect] +(function() { +const a = "foobar"; +return { a }; +})(); + +== should not indent body of optional IIFE == +(function() { + const a = "foobar"; + return { a }; +})?.(); + +[expect] +(function() { +const a = "foobar"; +return { a }; +})?.(); + +== should not indent body of bare-callee IIFE == +void function() { + const a = "foobar"; + return { a }; +}(); + +[expect] +void function() { +const a = "foobar"; +return { a }; +}(); + +== should still indent body of function expression in member call == +(function() { + const a = "foobar"; + return { a }; +}).call(null); + +[expect] +(function() { + const a = "foobar"; + return { a }; +}).call(null); + +== should still indent body of function expression as call argument == +foo((function() { + const a = "foobar"; + return { a }; +})); + +[expect] +foo(function() { + const a = "foobar"; + return { a }; +});