From c27d103cc0d9f55b4bccf3f2ca8b1bb25649fe12 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 21 Jul 2025 22:38:55 +0100 Subject: [PATCH 01/11] small refactor --- src/slang-nodes/StateVariableDefinitionValue.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/slang-nodes/StateVariableDefinitionValue.ts b/src/slang-nodes/StateVariableDefinitionValue.ts index d082c71f5..a5c0ae881 100644 --- a/src/slang-nodes/StateVariableDefinitionValue.ts +++ b/src/slang-nodes/StateVariableDefinitionValue.ts @@ -31,8 +31,11 @@ export class StateVariableDefinitionValue extends SlangNode { print: PrintFunction ): Doc { const value = path.call(print, 'value'); - return this.value.variant.kind === NonterminalKind.ArrayExpression - ? [' = ', value] - : group([' =', indent([line, value])]); + return [ + ' =', + this.value.variant.kind === NonterminalKind.ArrayExpression + ? [' ', value] + : group(indent([line, value])) + ]; } } From a8c169a2c061096bce5d9d9532c3588f74f569b2 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 21 Jul 2025 23:06:13 +0100 Subject: [PATCH 02/11] a printer for a pattern where a condition forks into a space or an indented line prepending the same item --- src/slang-nodes/AssignmentExpression.ts | 14 ++++++-------- src/slang-nodes/ElseBranch.ts | 12 +++++------- src/slang-nodes/ForStatement.ts | 11 ++++++----- src/slang-nodes/IfStatement.ts | 15 ++++++++------- src/slang-nodes/ReturnStatement.ts | 16 +++++++--------- src/slang-nodes/StateVariableDefinitionValue.ts | 12 +++++------- src/slang-nodes/WhileStatement.ts | 12 +++++------- .../print-indented-group-or-spaced-document.ts | 15 +++++++++++++++ 8 files changed, 57 insertions(+), 50 deletions(-) create mode 100644 src/slang-printers/print-indented-group-or-spaced-document.ts diff --git a/src/slang-nodes/AssignmentExpression.ts b/src/slang-nodes/AssignmentExpression.ts index 1fdc186d1..38aa55e19 100644 --- a/src/slang-nodes/AssignmentExpression.ts +++ b/src/slang-nodes/AssignmentExpression.ts @@ -1,6 +1,6 @@ import { NonterminalKind, TerminalKind } from '@nomicfoundation/slang/cst'; -import { doc } from 'prettier'; import { isBinaryOperation } from '../slang-utils/is-binary-operation.js'; +import { printIndentedGroupOrSpacedDocument } from '../slang-printers/print-indented-group-or-spaced-document.js'; import { SlangNode } from './SlangNode.js'; import { Expression } from './Expression.js'; @@ -9,8 +9,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, indent, line } = doc.builders; - export class AssignmentExpression extends SlangNode { readonly kind = NonterminalKind.AssignmentExpression; @@ -32,14 +30,14 @@ export class AssignmentExpression extends SlangNode { print(path: AstPath, print: PrintFunction): Doc { const rightOperandVariant = this.rightOperand.variant; - const rightOperand = path.call(print, 'rightOperand'); return [ path.call(print, 'leftOperand'), ` ${this.operator}`, - rightOperandVariant.kind !== TerminalKind.Identifier && - isBinaryOperation(rightOperandVariant) - ? group(indent([line, rightOperand])) - : [' ', rightOperand] + printIndentedGroupOrSpacedDocument( + path.call(print, 'rightOperand'), + rightOperandVariant.kind !== TerminalKind.Identifier && + isBinaryOperation(rightOperandVariant) + ) ]; } } diff --git a/src/slang-nodes/ElseBranch.ts b/src/slang-nodes/ElseBranch.ts index 4c00ea025..3f7e1b074 100644 --- a/src/slang-nodes/ElseBranch.ts +++ b/src/slang-nodes/ElseBranch.ts @@ -1,6 +1,6 @@ -import { doc } from 'prettier'; import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { createKindCheckFunction } from '../slang-utils/create-kind-check-function.js'; +import { printIndentedGroupOrSpacedDocument } from '../slang-printers/print-indented-group-or-spaced-document.js'; import { SlangNode } from './SlangNode.js'; import { Statement } from './Statement.js'; @@ -9,8 +9,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, indent, line } = doc.builders; - const isIfStatementOrBlock = createKindCheckFunction([ NonterminalKind.Block, NonterminalKind.IfStatement @@ -30,12 +28,12 @@ export class ElseBranch extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { - const body = path.call(print, 'body'); return [ 'else', - isIfStatementOrBlock(this.body.variant) - ? [' ', body] - : group(indent([line, body])) + printIndentedGroupOrSpacedDocument( + path.call(print, 'body'), + !isIfStatementOrBlock(this.body.variant) + ) ]; } } diff --git a/src/slang-nodes/ForStatement.ts b/src/slang-nodes/ForStatement.ts index 5e682e3aa..4364494a1 100644 --- a/src/slang-nodes/ForStatement.ts +++ b/src/slang-nodes/ForStatement.ts @@ -1,6 +1,7 @@ import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { doc } from 'prettier'; import { printSeparatedList } from '../slang-printers/print-separated-list.js'; +import { printIndentedGroupOrSpacedDocument } from '../slang-printers/print-indented-group-or-spaced-document.js'; import { SlangNode } from './SlangNode.js'; import { ForStatementInitialization } from './ForStatementInitialization.js'; import { ForStatementCondition } from './ForStatementCondition.js'; @@ -12,7 +13,7 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, indent, line } = doc.builders; +const { line } = doc.builders; export class ForStatement extends SlangNode { readonly kind = NonterminalKind.ForStatement; @@ -50,7 +51,6 @@ export class ForStatement extends SlangNode { const initialization = path.call(print, 'initialization'); const condition = path.call(print, 'condition'); const iterator = path.call(print, 'iterator'); - const body = path.call(print, 'body'); return [ 'for (', @@ -61,9 +61,10 @@ export class ForStatement extends SlangNode { : '' }), ')', - this.body.variant.kind === NonterminalKind.Block - ? [' ', body] - : group(indent([line, body])) + printIndentedGroupOrSpacedDocument( + path.call(print, 'body'), + this.body.variant.kind !== NonterminalKind.Block + ) ]; } } diff --git a/src/slang-nodes/IfStatement.ts b/src/slang-nodes/IfStatement.ts index 9f7ca7b36..0ffa8dd60 100644 --- a/src/slang-nodes/IfStatement.ts +++ b/src/slang-nodes/IfStatement.ts @@ -1,6 +1,7 @@ import { doc } from 'prettier'; import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { printSeparatedItem } from '../slang-printers/print-separated-item.js'; +import { printIndentedGroupOrSpacedDocument } from '../slang-printers/print-indented-group-or-spaced-document.js'; import { isBlockComment } from '../slang-utils/is-comment.js'; import { SlangNode } from './SlangNode.js'; import { Expression } from './Expression.js'; @@ -12,7 +13,7 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, hardline, indent, line } = doc.builders; +const { hardline } = doc.builders; export class IfStatement extends SlangNode { readonly kind = NonterminalKind.IfStatement; @@ -37,16 +38,16 @@ export class IfStatement extends SlangNode { print(path: AstPath, print: PrintFunction): Doc { const { kind: bodyKind, comments: bodyComments } = this.body.variant; - const body = path.call(print, 'body'); return [ 'if (', printSeparatedItem(path.call(print, 'condition')), ')', - bodyKind === NonterminalKind.Block - ? [' ', body] - : group(indent([line, body]), { - shouldBreak: bodyKind === NonterminalKind.IfStatement // `if` within `if` - }), + printIndentedGroupOrSpacedDocument( + path.call(print, 'body'), + bodyKind !== NonterminalKind.Block, + // `if` within `if` + { shouldBreak: bodyKind === NonterminalKind.IfStatement } + ), this.elseBranch ? [ bodyKind !== NonterminalKind.Block || // else on a new line if body is not a block diff --git a/src/slang-nodes/ReturnStatement.ts b/src/slang-nodes/ReturnStatement.ts index 2d729aa47..3cd3d0c50 100644 --- a/src/slang-nodes/ReturnStatement.ts +++ b/src/slang-nodes/ReturnStatement.ts @@ -1,5 +1,5 @@ -import { doc } from 'prettier'; import { NonterminalKind } from '@nomicfoundation/slang/cst'; +import { printIndentedGroupOrSpacedDocument } from '../slang-printers/print-indented-group-or-spaced-document.js'; import { SlangNode } from './SlangNode.js'; import { Expression } from './Expression.js'; @@ -8,8 +8,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, indent, line } = doc.builders; - function printExpression( node: ReturnStatement, path: AstPath, @@ -17,13 +15,13 @@ function printExpression( options: ParserOptions ): Doc { const expressionVariantKind = node.expression?.variant.kind; - const expression = path.call(print, 'expression'); if (expressionVariantKind) { - return expressionVariantKind === NonterminalKind.TupleExpression || - (options.experimentalTernaries && - expressionVariantKind === NonterminalKind.ConditionalExpression) - ? [' ', expression] - : group(indent([line, expression])); + return printIndentedGroupOrSpacedDocument( + path.call(print, 'expression'), + expressionVariantKind !== NonterminalKind.TupleExpression && + (!options.experimentalTernaries || + expressionVariantKind !== NonterminalKind.ConditionalExpression) + ); } return ''; } diff --git a/src/slang-nodes/StateVariableDefinitionValue.ts b/src/slang-nodes/StateVariableDefinitionValue.ts index a5c0ae881..637b10fde 100644 --- a/src/slang-nodes/StateVariableDefinitionValue.ts +++ b/src/slang-nodes/StateVariableDefinitionValue.ts @@ -1,5 +1,5 @@ -import { doc } from 'prettier'; import { NonterminalKind } from '@nomicfoundation/slang/cst'; +import { printIndentedGroupOrSpacedDocument } from '../slang-printers/print-indented-group-or-spaced-document.js'; import { SlangNode } from './SlangNode.js'; import { Expression } from './Expression.js'; @@ -8,8 +8,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, indent, line } = doc.builders; - export class StateVariableDefinitionValue extends SlangNode { readonly kind = NonterminalKind.StateVariableDefinitionValue; @@ -30,12 +28,12 @@ export class StateVariableDefinitionValue extends SlangNode { path: AstPath, print: PrintFunction ): Doc { - const value = path.call(print, 'value'); return [ ' =', - this.value.variant.kind === NonterminalKind.ArrayExpression - ? [' ', value] - : group(indent([line, value])) + printIndentedGroupOrSpacedDocument( + path.call(print, 'value'), + this.value.variant.kind !== NonterminalKind.ArrayExpression + ) ]; } } diff --git a/src/slang-nodes/WhileStatement.ts b/src/slang-nodes/WhileStatement.ts index 6a9514464..121d44b23 100644 --- a/src/slang-nodes/WhileStatement.ts +++ b/src/slang-nodes/WhileStatement.ts @@ -1,6 +1,6 @@ -import { doc } from 'prettier'; import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { printSeparatedItem } from '../slang-printers/print-separated-item.js'; +import { printIndentedGroupOrSpacedDocument } from '../slang-printers/print-indented-group-or-spaced-document.js'; import { SlangNode } from './SlangNode.js'; import { Expression } from './Expression.js'; import { Statement } from './Statement.js'; @@ -10,8 +10,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, indent, line } = doc.builders; - export class WhileStatement extends SlangNode { readonly kind = NonterminalKind.WhileStatement; @@ -29,14 +27,14 @@ export class WhileStatement extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { - const body = path.call(print, 'body'); return [ 'while (', printSeparatedItem(path.call(print, 'condition')), ')', - this.body.variant.kind === NonterminalKind.Block - ? [' ', body] - : group(indent([line, body])) + printIndentedGroupOrSpacedDocument( + path.call(print, 'body'), + this.body.variant.kind !== NonterminalKind.Block + ) ]; } } diff --git a/src/slang-printers/print-indented-group-or-spaced-document.ts b/src/slang-printers/print-indented-group-or-spaced-document.ts new file mode 100644 index 000000000..eedea56c7 --- /dev/null +++ b/src/slang-printers/print-indented-group-or-spaced-document.ts @@ -0,0 +1,15 @@ +import { doc } from 'prettier'; + +import type { Doc } from 'prettier'; + +const { group, indent, line } = doc.builders; + +export function printIndentedGroupOrSpacedDocument( + document: Doc, + shouldGroup = true, + groupOptions: doc.builders.GroupOptions = {} +): doc.builders.Group | [' ', Doc] { + return shouldGroup + ? group(indent([line, document]), groupOptions) + : [' ', document]; +} From 0a7af8c774663b69c24f5f18a9da2d6f3e695c38 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Jul 2025 12:48:56 +0100 Subject: [PATCH 03/11] a printer for a pattern where a group is followed by an indentIfBreak pointing to said group. --- src/slang-nodes/AddressType.ts | 3 +- src/slang-nodes/EventDefinition.ts | 3 +- src/slang-nodes/FunctionCallExpression.ts | 12 +++---- src/slang-nodes/IndexAccessExpression.ts | 12 +++---- src/slang-nodes/StateVariableDefinition.ts | 21 ++++++------ .../TupleDeconstructionStatement.ts | 16 +++------- src/slang-nodes/UsingDirective.ts | 14 +++----- .../VariableDeclarationStatement.ts | 32 ++++++++----------- src/slang-printers/print-function.ts | 4 +-- .../print-group-and-indent-if-break-pair.ts | 16 ++++++++++ 10 files changed, 66 insertions(+), 67 deletions(-) create mode 100644 src/slang-printers/print-group-and-indent-if-break-pair.ts diff --git a/src/slang-nodes/AddressType.ts b/src/slang-nodes/AddressType.ts index 735bb13cb..86862359a 100644 --- a/src/slang-nodes/AddressType.ts +++ b/src/slang-nodes/AddressType.ts @@ -1,5 +1,4 @@ import { NonterminalKind } from '@nomicfoundation/slang/cst'; -import { joinExisting } from '../slang-utils/join-existing.js'; import { SlangNode } from './SlangNode.js'; import type * as ast from '@nomicfoundation/slang/ast'; @@ -17,6 +16,6 @@ export class AddressType extends SlangNode { } print(): Doc { - return joinExisting(' ', ['address', this.payableKeyword]); + return ['address', this.payableKeyword ? ' payable' : '']; } } diff --git a/src/slang-nodes/EventDefinition.ts b/src/slang-nodes/EventDefinition.ts index 77837800e..f981bece9 100644 --- a/src/slang-nodes/EventDefinition.ts +++ b/src/slang-nodes/EventDefinition.ts @@ -32,8 +32,7 @@ export class EventDefinition extends SlangNode { 'event ', path.call(print, 'name'), path.call(print, 'parameters'), - this.anonymousKeyword ? ' anonymous' : '', - ';' + this.anonymousKeyword ? ' anonymous;' : ';' ]; } } diff --git a/src/slang-nodes/FunctionCallExpression.ts b/src/slang-nodes/FunctionCallExpression.ts index a115160ab..8451b4f10 100644 --- a/src/slang-nodes/FunctionCallExpression.ts +++ b/src/slang-nodes/FunctionCallExpression.ts @@ -1,6 +1,7 @@ import { doc } from 'prettier'; import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { isLabel } from '../slang-utils/is-label.js'; +import { printGroupAndIndentIfBreakPair } from '../slang-printers/print-group-and-indent-if-break-pair.js'; import { SlangNode } from './SlangNode.js'; import { Expression } from './Expression.js'; import { ArgumentsDeclaration } from './ArgumentsDeclaration.js'; @@ -10,7 +11,7 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, indentIfBreak, label } = doc.builders; +const { label } = doc.builders; export class FunctionCallExpression extends SlangNode { readonly kind = NonterminalKind.FunctionCallExpression; @@ -38,13 +39,12 @@ export class FunctionCallExpression extends SlangNode { // If we are at the end of a MemberAccessChain we should indent the // arguments accordingly. if (isLabel(operand) && operand.label === 'MemberAccessChain') { - const groupId = Symbol('Slang.FunctionCallExpression.operand'); // We wrap the expression in a label in case there is an IndexAccess or // a FunctionCall following this IndexAccess. - return label('MemberAccessChain', [ - group(operand.contents, { id: groupId }), - indentIfBreak(argumentsDoc, { groupId }) - ]); + return label( + 'MemberAccessChain', + printGroupAndIndentIfBreakPair(operand.contents, argumentsDoc) + ); } return [operand, argumentsDoc].flat(); diff --git a/src/slang-nodes/IndexAccessExpression.ts b/src/slang-nodes/IndexAccessExpression.ts index 1ba396516..8dc39f95a 100644 --- a/src/slang-nodes/IndexAccessExpression.ts +++ b/src/slang-nodes/IndexAccessExpression.ts @@ -1,6 +1,7 @@ import { doc } from 'prettier'; import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { printSeparatedItem } from '../slang-printers/print-separated-item.js'; +import { printGroupAndIndentIfBreakPair } from '../slang-printers/print-group-and-indent-if-break-pair.js'; import { isLabel } from '../slang-utils/is-label.js'; import { SlangNode } from './SlangNode.js'; import { Expression } from './Expression.js'; @@ -11,7 +12,7 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, indentIfBreak, label } = doc.builders; +const { label } = doc.builders; export class IndexAccessExpression extends SlangNode { readonly kind = NonterminalKind.IndexAccessExpression; @@ -47,13 +48,12 @@ export class IndexAccessExpression extends SlangNode { // If we are at the end of a MemberAccessChain we should indent the // arguments accordingly. if (isLabel(operand) && operand.label === 'MemberAccessChain') { - const groupId = Symbol('Slang.IndexAccessExpression.operand'); // We wrap the expression in a label in case there is an IndexAccess or // a FunctionCall following this IndexAccess. - return label('MemberAccessChain', [ - group(operand.contents, { id: groupId }), - indentIfBreak(indexDoc, { groupId }) - ]); + return label( + 'MemberAccessChain', + printGroupAndIndentIfBreakPair(operand.contents, indexDoc) + ); } return [operand, indexDoc].flat(); diff --git a/src/slang-nodes/StateVariableDefinition.ts b/src/slang-nodes/StateVariableDefinition.ts index a8b406716..49bb4e628 100644 --- a/src/slang-nodes/StateVariableDefinition.ts +++ b/src/slang-nodes/StateVariableDefinition.ts @@ -1,5 +1,6 @@ import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { doc } from 'prettier'; +import { printGroupAndIndentIfBreakPair } from '../slang-printers/print-group-and-indent-if-break-pair.js'; import { SlangNode } from './SlangNode.js'; import { TypeName } from './TypeName.js'; import { StateVariableAttributes } from './StateVariableAttributes.js'; @@ -11,7 +12,7 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, indent, indentIfBreak } = doc.builders; +const { indent } = doc.builders; export class StateVariableDefinition extends SlangNode { readonly kind = NonterminalKind.StateVariableDefinition; @@ -41,14 +42,14 @@ export class StateVariableDefinition extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { - const groupId = Symbol('Slang.StateVariableDefinition.attributes'); - return [ - path.call(print, 'typeName'), - group(indent(path.call(print, 'attributes')), { id: groupId }), - ' ', - path.call(print, 'name'), - this.value ? indentIfBreak(path.call(print, 'value'), { groupId }) : '', - ';' - ]; + return printGroupAndIndentIfBreakPair( + [ + path.call(print, 'typeName'), + indent(path.call(print, 'attributes')), + ' ', + path.call(print, 'name') + ], + [path.call(print, 'value'), ';'] + ); } } diff --git a/src/slang-nodes/TupleDeconstructionStatement.ts b/src/slang-nodes/TupleDeconstructionStatement.ts index 74e167602..eddb26316 100644 --- a/src/slang-nodes/TupleDeconstructionStatement.ts +++ b/src/slang-nodes/TupleDeconstructionStatement.ts @@ -1,5 +1,5 @@ import { NonterminalKind } from '@nomicfoundation/slang/cst'; -import { doc } from 'prettier'; +import { printGroupAndIndentIfBreakPair } from '../slang-printers/print-group-and-indent-if-break-pair.js'; import { SlangNode } from './SlangNode.js'; import { TupleDeconstructionElements } from './TupleDeconstructionElements.js'; import { Expression } from './Expression.js'; @@ -9,8 +9,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, indentIfBreak } = doc.builders; - export class TupleDeconstructionStatement extends SlangNode { readonly kind = NonterminalKind.TupleDeconstructionStatement; @@ -37,13 +35,9 @@ export class TupleDeconstructionStatement extends SlangNode { path: AstPath, print: PrintFunction ): Doc { - const groupId = Symbol('Slang.VariableDeclarationStatement.variables'); - return [ - group( - [this.varKeyword ? 'var (' : '(', path.call(print, 'elements'), ')'], - { id: groupId } - ), - indentIfBreak([' = ', path.call(print, 'expression'), ';'], { groupId }) - ]; + return printGroupAndIndentIfBreakPair( + [this.varKeyword ? 'var (' : '(', path.call(print, 'elements'), ') = '], + [path.call(print, 'expression'), ';'] + ); } } diff --git a/src/slang-nodes/UsingDirective.ts b/src/slang-nodes/UsingDirective.ts index 6201516d0..279131986 100644 --- a/src/slang-nodes/UsingDirective.ts +++ b/src/slang-nodes/UsingDirective.ts @@ -1,5 +1,4 @@ import { NonterminalKind } from '@nomicfoundation/slang/cst'; -import { joinExisting } from '../slang-utils/join-existing.js'; import { SlangNode } from './SlangNode.js'; import { UsingClause } from './UsingClause.js'; import { UsingTarget } from './UsingTarget.js'; @@ -30,14 +29,11 @@ export class UsingDirective extends SlangNode { print(path: AstPath, print: PrintFunction): Doc { return [ - joinExisting(' ', [ - 'using', - path.call(print, 'clause'), - 'for', - path.call(print, 'target'), - this.globalKeyword - ]), - ';' + 'using ', + path.call(print, 'clause'), + ' for ', + path.call(print, 'target'), + this.globalKeyword ? ' global;' : ';' ]; } } diff --git a/src/slang-nodes/VariableDeclarationStatement.ts b/src/slang-nodes/VariableDeclarationStatement.ts index 4312cc45f..e808e47db 100644 --- a/src/slang-nodes/VariableDeclarationStatement.ts +++ b/src/slang-nodes/VariableDeclarationStatement.ts @@ -1,5 +1,6 @@ import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { doc } from 'prettier'; +import { printGroupAndIndentIfBreakPair } from '../slang-printers/print-group-and-indent-if-break-pair.js'; import { SlangNode } from './SlangNode.js'; import { VariableDeclarationType } from './VariableDeclarationType.js'; import { StorageLocation } from './StorageLocation.js'; @@ -11,7 +12,7 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group, indent, indentIfBreak, line } = doc.builders; +const { indent, line } = doc.builders; export class VariableDeclarationStatement extends SlangNode { readonly kind = NonterminalKind.VariableDeclarationStatement; @@ -46,23 +47,16 @@ export class VariableDeclarationStatement extends SlangNode { path: AstPath, print: PrintFunction ): Doc { - const groupId = Symbol('Slang.VariableDeclarationStatement.variables'); - return [ - group( - [ - path.call(print, 'variableType'), - indent([ - this.storageLocation - ? [line, path.call(print, 'storageLocation')] - : '', - ' ', - path.call(print, 'name') - ]) - ], - { id: groupId } - ), - indentIfBreak(path.call(print, 'value'), { groupId }), - ';' - ]; + return printGroupAndIndentIfBreakPair( + [ + path.call(print, 'variableType'), + this.storageLocation + ? indent([line, path.call(print, 'storageLocation')]) + : '', + ' ', + path.call(print, 'name') + ], + [path.call(print, 'value'), ';'] + ); } } diff --git a/src/slang-printers/print-function.ts b/src/slang-printers/print-function.ts index e95ea50f4..a379de4c6 100644 --- a/src/slang-printers/print-function.ts +++ b/src/slang-printers/print-function.ts @@ -18,8 +18,8 @@ export function printFunction( group([ functionName, path.call(print, 'parameters'), - indent( - group([ + group( + indent([ joinExisting(line, [ path.call(print, 'attributes'), path.call(print, 'returns') diff --git a/src/slang-printers/print-group-and-indent-if-break-pair.ts b/src/slang-printers/print-group-and-indent-if-break-pair.ts new file mode 100644 index 000000000..222d4be03 --- /dev/null +++ b/src/slang-printers/print-group-and-indent-if-break-pair.ts @@ -0,0 +1,16 @@ +import { doc } from 'prettier'; + +import type { Doc } from 'prettier'; + +const { group, indentIfBreak } = doc.builders; + +export function printGroupAndIndentIfBreakPair( + groupDoc: Doc, + indentIfBreakDoc: Doc +): [doc.builders.Group, doc.builders.IndentIfBreak] { + const groupId = Symbol('Slang.GroupAndIndentIfBreakPair'); + return [ + group(groupDoc, { id: groupId }), + indentIfBreak(indentIfBreakDoc, { groupId }) + ]; +} From ac4b9764afa7350ed1a34740d041ca435266538f Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 22 Jul 2025 14:10:17 +0100 Subject: [PATCH 04/11] standardising `printPreservingEmptyLines` --- src/nodes/SourceUnit.js | 8 +++- .../handle-source-unit-members-comments.ts | 28 ++++++++++++ src/slang-comments/handlers/index.ts | 2 + src/slang-nodes/ContractMembers.ts | 16 +++---- src/slang-nodes/InterfaceMembers.ts | 2 +- src/slang-nodes/LibraryMembers.ts | 2 +- src/slang-nodes/Statements.ts | 16 +++---- src/slang-nodes/YulStatements.ts | 16 +++---- src/slang-printers/print-comments.ts | 10 ++--- .../print-preserving-empty-lines.ts | 44 ++++++++++--------- tests/format/Empty/EmptyFile.sol | 0 tests/format/Empty/EmptyFileWithComments.sol | 5 +++ .../Empty/__snapshots__/format.test.js.snap | 33 ++++++++++++++ tests/format/Empty/format.test.js | 1 + 14 files changed, 122 insertions(+), 61 deletions(-) create mode 100644 src/slang-comments/handlers/handle-source-unit-members-comments.ts create mode 100644 tests/format/Empty/EmptyFile.sol create mode 100644 tests/format/Empty/EmptyFileWithComments.sol create mode 100644 tests/format/Empty/__snapshots__/format.test.js.snap create mode 100644 tests/format/Empty/format.test.js diff --git a/src/nodes/SourceUnit.js b/src/nodes/SourceUnit.js index 413f97393..604cd3f31 100644 --- a/src/nodes/SourceUnit.js +++ b/src/nodes/SourceUnit.js @@ -1,11 +1,15 @@ import { doc } from 'prettier'; -import { printPreservingEmptyLines } from '../common/printer-helpers.js'; +import { + printComments, + printPreservingEmptyLines +} from '../common/printer-helpers.js'; const { line } = doc.builders; export const SourceUnit = { - print: ({ options, path, print }) => [ + print: ({ node, options, path, print }) => [ printPreservingEmptyLines(path, 'children', options, print), + printComments(node, path, options), options.parentParser ? '' : line ] }; diff --git a/src/slang-comments/handlers/handle-source-unit-members-comments.ts b/src/slang-comments/handlers/handle-source-unit-members-comments.ts new file mode 100644 index 000000000..7b38df78a --- /dev/null +++ b/src/slang-comments/handlers/handle-source-unit-members-comments.ts @@ -0,0 +1,28 @@ +import { NonterminalKind } from '@nomicfoundation/slang/cst'; +import addCollectionFirstComment from './add-collection-first-comment.js'; +import addCollectionLastComment from './add-collection-last-comment.js'; + +import type { HandlerParams } from './types.js'; + +export default function handleSourceUnitMembersComments({ + precedingNode, + followingNode, + comment +}: HandlerParams): boolean { + if ( + followingNode && + followingNode.kind === NonterminalKind.SourceUnitMembers + ) { + addCollectionFirstComment(followingNode, comment); + return true; + } + if ( + precedingNode && + precedingNode.kind === NonterminalKind.SourceUnitMembers + ) { + addCollectionLastComment(precedingNode, comment); + return true; + } + + return false; +} diff --git a/src/slang-comments/handlers/index.ts b/src/slang-comments/handlers/index.ts index dbc5f4c92..901d0a767 100644 --- a/src/slang-comments/handlers/index.ts +++ b/src/slang-comments/handlers/index.ts @@ -8,6 +8,7 @@ import handleLibraryDefinitionComments from './handle-library-definition-comment import handleModifierInvocationComments from './handle-modifier-invocation-comments.js'; import handleParametersDeclarationComments from './handle-parameters-declaration-comments.js'; import handlePositionalArgumentsDeclarationComments from './handle-positional-arguments-declaration-comments.js'; +import handleSourceUnitMembersComments from './handle-source-unit-members-comments.js'; import handleStorageLayoutSpecifierComments from './handle-storage-layout-specifier-comments.js'; import handleStructComments from './handle-struct-comments.js'; import handleWhileStatementComments from './handle-while-statement-comments.js'; @@ -24,6 +25,7 @@ export default [ handleModifierInvocationComments, handleParametersDeclarationComments, handlePositionalArgumentsDeclarationComments, + handleSourceUnitMembersComments, handleStorageLayoutSpecifierComments, handleStructComments, handleWhileStatementComments, diff --git a/src/slang-nodes/ContractMembers.ts b/src/slang-nodes/ContractMembers.ts index cd82c186c..d187b5afc 100644 --- a/src/slang-nodes/ContractMembers.ts +++ b/src/slang-nodes/ContractMembers.ts @@ -1,7 +1,6 @@ import { doc } from 'prettier'; import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { printSeparatedItem } from '../slang-printers/print-separated-item.js'; -import { printComments } from '../slang-printers/print-comments.js'; import { printPreservingEmptyLines } from '../slang-printers/print-preserving-empty-lines.js'; import { SlangNode } from './SlangNode.js'; import { ContractMember } from './ContractMember.js'; @@ -31,14 +30,11 @@ export class ContractMembers extends SlangNode { print: PrintFunction, options: ParserOptions ): Doc { - return this.items.length === 0 && this.comments.length === 0 - ? '' - : printSeparatedItem( - [ - printPreservingEmptyLines(path, print, options), - printComments(path) - ], - { firstSeparator: hardline, grouped: false } - ); + return this.items.length > 0 || this.comments.length > 0 + ? printSeparatedItem(printPreservingEmptyLines(path, print, options), { + firstSeparator: hardline, + grouped: false + }) + : ''; } } diff --git a/src/slang-nodes/InterfaceMembers.ts b/src/slang-nodes/InterfaceMembers.ts index 6ee398a26..7f53bc6ce 100644 --- a/src/slang-nodes/InterfaceMembers.ts +++ b/src/slang-nodes/InterfaceMembers.ts @@ -30,7 +30,7 @@ export class InterfaceMembers extends SlangNode { print: PrintFunction, options: ParserOptions ): Doc { - return this.items.length > 0 + return this.items.length > 0 || this.comments.length > 0 ? printSeparatedItem(printPreservingEmptyLines(path, print, options), { firstSeparator: hardline, grouped: false diff --git a/src/slang-nodes/LibraryMembers.ts b/src/slang-nodes/LibraryMembers.ts index 92ccfd0ed..0d2af052f 100644 --- a/src/slang-nodes/LibraryMembers.ts +++ b/src/slang-nodes/LibraryMembers.ts @@ -30,7 +30,7 @@ export class LibraryMembers extends SlangNode { print: PrintFunction, options: ParserOptions ): Doc { - return this.items.length > 0 + return this.items.length > 0 || this.comments.length > 0 ? printSeparatedItem(printPreservingEmptyLines(path, print, options), { firstSeparator: hardline, grouped: false diff --git a/src/slang-nodes/Statements.ts b/src/slang-nodes/Statements.ts index 0c32e3c18..d213c2541 100644 --- a/src/slang-nodes/Statements.ts +++ b/src/slang-nodes/Statements.ts @@ -1,7 +1,6 @@ import { doc } from 'prettier'; import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { printSeparatedItem } from '../slang-printers/print-separated-item.js'; -import { printComments } from '../slang-printers/print-comments.js'; import { printPreservingEmptyLines } from '../slang-printers/print-preserving-empty-lines.js'; import { SlangNode } from './SlangNode.js'; import { Statement } from './Statement.js'; @@ -31,14 +30,11 @@ export class Statements extends SlangNode { print: PrintFunction, options: ParserOptions ): Doc { - return this.items.length === 0 && this.comments.length === 0 - ? '' - : printSeparatedItem( - [ - printPreservingEmptyLines(path, print, options), - printComments(path) - ], - { firstSeparator: hardline, grouped: false } - ); + return this.items.length > 0 || this.comments.length > 0 + ? printSeparatedItem(printPreservingEmptyLines(path, print, options), { + firstSeparator: hardline, + grouped: false + }) + : ''; } } diff --git a/src/slang-nodes/YulStatements.ts b/src/slang-nodes/YulStatements.ts index 516a0485e..115afb949 100644 --- a/src/slang-nodes/YulStatements.ts +++ b/src/slang-nodes/YulStatements.ts @@ -1,7 +1,6 @@ import { doc } from 'prettier'; import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { printSeparatedItem } from '../slang-printers/print-separated-item.js'; -import { printComments } from '../slang-printers/print-comments.js'; import { printPreservingEmptyLines } from '../slang-printers/print-preserving-empty-lines.js'; import { SlangNode } from './SlangNode.js'; import { YulStatement } from './YulStatement.js'; @@ -31,14 +30,11 @@ export class YulStatements extends SlangNode { print: PrintFunction, options: ParserOptions ): Doc { - return this.items.length === 0 && this.comments.length === 0 - ? '' - : printSeparatedItem( - [ - printPreservingEmptyLines(path, print, options), - printComments(path) - ], - { firstSeparator: hardline, grouped: false } - ); + return this.items.length > 0 || this.comments.length > 0 + ? printSeparatedItem(printPreservingEmptyLines(path, print, options), { + firstSeparator: hardline, + grouped: false + }) + : ''; } } diff --git a/src/slang-printers/print-comments.ts b/src/slang-printers/print-comments.ts index ee660df14..1bf98c92e 100644 --- a/src/slang-printers/print-comments.ts +++ b/src/slang-printers/print-comments.ts @@ -1,15 +1,14 @@ import { doc } from 'prettier'; import { printComment } from '../slang-comments/printer.js'; -import { isBlockComment } from '../slang-utils/is-comment.js'; import { joinExisting } from '../slang-utils/join-existing.js'; import type { AstPath, Doc } from 'prettier'; import type { AstNode } from '../slang-nodes/types.d.ts'; -const { breakParent, line } = doc.builders; +const { line } = doc.builders; export function printComments(path: AstPath): Doc[] { - const document = joinExisting( + return joinExisting( line, path.map((commentPath) => { const comment = commentPath.node; @@ -17,10 +16,7 @@ export function printComments(path: AstPath): Doc[] { return ''; } comment.printed = true; - const printed = printComment(commentPath); - return isBlockComment(comment) ? printed : [printed, breakParent]; + return printComment(commentPath); }, 'comments') ); - - return document; } diff --git a/src/slang-printers/print-preserving-empty-lines.ts b/src/slang-printers/print-preserving-empty-lines.ts index bd43449db..8acdce5ca 100644 --- a/src/slang-printers/print-preserving-empty-lines.ts +++ b/src/slang-printers/print-preserving-empty-lines.ts @@ -1,6 +1,7 @@ import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { doc, util } from 'prettier'; import { locEnd } from '../slang-utils/loc.js'; +import { printComments } from './print-comments.js'; import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode, LineCollection } from '../slang-nodes/types.d.ts'; @@ -13,25 +14,28 @@ export function printPreservingEmptyLines( print: PrintFunction, options: ParserOptions ): Doc { - return path.map((childPath) => { - const node = childPath.node; + return [ + path.map((childPath) => { + const node = childPath.node; - return [ - // Only attempt to prepend an empty line if `node` is not the first item - !childPath.isFirst && - // YulLabel adds a dedented line so we don't have to prepend a hardline. - (node.kind !== NonterminalKind.YulStatement || - node.variant.kind !== NonterminalKind.YulLabel) - ? hardline - : '', - print(childPath), - // Only attempt to append an empty line if `node` is not the last item - !childPath.isLast && - // Append an empty line if the original text already had an one after the - // current `node` - util.isNextLineEmpty(options.originalText, locEnd(node)) - ? hardline - : '' - ]; - }, 'items'); + return [ + // Only attempt to prepend an empty line if `node` is not the first item + !childPath.isFirst && + // YulLabel adds a dedented line so we don't have to prepend a hardline. + (node.kind !== NonterminalKind.YulStatement || + node.variant.kind !== NonterminalKind.YulLabel) + ? hardline + : '', + print(childPath), + // Only attempt to append an empty line if `node` is not the last item + !childPath.isLast && + // Append an empty line if the original text already had an one after the + // current `node` + util.isNextLineEmpty(options.originalText, locEnd(node)) + ? hardline + : '' + ]; + }, 'items'), + printComments(path) + ]; } diff --git a/tests/format/Empty/EmptyFile.sol b/tests/format/Empty/EmptyFile.sol new file mode 100644 index 000000000..e69de29bb diff --git a/tests/format/Empty/EmptyFileWithComments.sol b/tests/format/Empty/EmptyFileWithComments.sol new file mode 100644 index 000000000..a2f2f52a3 --- /dev/null +++ b/tests/format/Empty/EmptyFileWithComments.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT + +/** + * TODO: Write contract. + */ \ No newline at end of file diff --git a/tests/format/Empty/__snapshots__/format.test.js.snap b/tests/format/Empty/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..85b5731b1 --- /dev/null +++ b/tests/format/Empty/__snapshots__/format.test.js.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`EmptyFile.sol format 1`] = ` +====================================options===================================== +parsers: ["slang"] +printWidth: 80 + | printWidth +=====================================input====================================== + +=====================================output===================================== + +================================================================================ +`; + +exports[`EmptyFileWithComments.sol format 1`] = ` +====================================options===================================== +parsers: ["slang"] +printWidth: 80 + | printWidth +=====================================input====================================== +// SPDX-License-Identifier: MIT + +/** + * TODO: Write contract. + */ +=====================================output===================================== +// SPDX-License-Identifier: MIT +/** + * TODO: Write contract. + */ + +================================================================================ +`; diff --git a/tests/format/Empty/format.test.js b/tests/format/Empty/format.test.js new file mode 100644 index 000000000..6021bbb0d --- /dev/null +++ b/tests/format/Empty/format.test.js @@ -0,0 +1 @@ +runFormatTest(import.meta, ['slang']); From d39dba87ab00c86e1abd06205e61cb266d376281 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Jul 2025 13:23:58 +0100 Subject: [PATCH 05/11] automatically set `grouped: false` when the `firstSeparator` is a `hardline` --- src/slang-nodes/ContractMembers.ts | 3 +-- src/slang-nodes/ImportDeconstructionSymbols.ts | 3 +-- src/slang-nodes/InterfaceMembers.ts | 3 +-- src/slang-nodes/LibraryMembers.ts | 3 +-- src/slang-nodes/Statements.ts | 3 +-- src/slang-nodes/YulStatements.ts | 3 +-- src/slang-printers/print-separated-item.ts | 4 ++-- 7 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/slang-nodes/ContractMembers.ts b/src/slang-nodes/ContractMembers.ts index d187b5afc..90ddcde58 100644 --- a/src/slang-nodes/ContractMembers.ts +++ b/src/slang-nodes/ContractMembers.ts @@ -32,8 +32,7 @@ export class ContractMembers extends SlangNode { ): Doc { return this.items.length > 0 || this.comments.length > 0 ? printSeparatedItem(printPreservingEmptyLines(path, print, options), { - firstSeparator: hardline, - grouped: false + firstSeparator: hardline }) : ''; } diff --git a/src/slang-nodes/ImportDeconstructionSymbols.ts b/src/slang-nodes/ImportDeconstructionSymbols.ts index a5ebf0f7b..bcdb32dee 100644 --- a/src/slang-nodes/ImportDeconstructionSymbols.ts +++ b/src/slang-nodes/ImportDeconstructionSymbols.ts @@ -37,8 +37,7 @@ export class ImportDeconstructionSymbols extends SlangNode { ? { // if the compiler exists and is greater than or equal to 0.7.4 we will // split the ImportDirective. - firstSeparator: bracketSpacing ? line : softline, - separator: [',', line] + firstSeparator: bracketSpacing ? line : softline } : { // if the compiler is not given or is lower than 0.7.4 we will not diff --git a/src/slang-nodes/InterfaceMembers.ts b/src/slang-nodes/InterfaceMembers.ts index 7f53bc6ce..657078bc4 100644 --- a/src/slang-nodes/InterfaceMembers.ts +++ b/src/slang-nodes/InterfaceMembers.ts @@ -32,8 +32,7 @@ export class InterfaceMembers extends SlangNode { ): Doc { return this.items.length > 0 || this.comments.length > 0 ? printSeparatedItem(printPreservingEmptyLines(path, print, options), { - firstSeparator: hardline, - grouped: false + firstSeparator: hardline }) : ''; } diff --git a/src/slang-nodes/LibraryMembers.ts b/src/slang-nodes/LibraryMembers.ts index 0d2af052f..8cb97a6b0 100644 --- a/src/slang-nodes/LibraryMembers.ts +++ b/src/slang-nodes/LibraryMembers.ts @@ -32,8 +32,7 @@ export class LibraryMembers extends SlangNode { ): Doc { return this.items.length > 0 || this.comments.length > 0 ? printSeparatedItem(printPreservingEmptyLines(path, print, options), { - firstSeparator: hardline, - grouped: false + firstSeparator: hardline }) : ''; } diff --git a/src/slang-nodes/Statements.ts b/src/slang-nodes/Statements.ts index d213c2541..1f56baaf9 100644 --- a/src/slang-nodes/Statements.ts +++ b/src/slang-nodes/Statements.ts @@ -32,8 +32,7 @@ export class Statements extends SlangNode { ): Doc { return this.items.length > 0 || this.comments.length > 0 ? printSeparatedItem(printPreservingEmptyLines(path, print, options), { - firstSeparator: hardline, - grouped: false + firstSeparator: hardline }) : ''; } diff --git a/src/slang-nodes/YulStatements.ts b/src/slang-nodes/YulStatements.ts index 115afb949..6afaca46c 100644 --- a/src/slang-nodes/YulStatements.ts +++ b/src/slang-nodes/YulStatements.ts @@ -32,8 +32,7 @@ export class YulStatements extends SlangNode { ): Doc { return this.items.length > 0 || this.comments.length > 0 ? printSeparatedItem(printPreservingEmptyLines(path, print, options), { - firstSeparator: hardline, - grouped: false + firstSeparator: hardline }) : ''; } diff --git a/src/slang-printers/print-separated-item.ts b/src/slang-printers/print-separated-item.ts index 873c079d6..c8f71190f 100644 --- a/src/slang-printers/print-separated-item.ts +++ b/src/slang-printers/print-separated-item.ts @@ -3,7 +3,7 @@ import { doc } from 'prettier'; import type { Doc } from 'prettier'; import type { PrintSeparatedOptions } from './types.d.ts'; -const { group, indent, softline } = doc.builders; +const { group, hardline, indent, softline } = doc.builders; // This function will add an indentation to the `item` and separate it from the // rest of the `doc` in most cases by a `softline`. @@ -12,7 +12,7 @@ export function printSeparatedItem( { firstSeparator = softline, lastSeparator = firstSeparator, - grouped = true + grouped = firstSeparator !== hardline }: PrintSeparatedOptions = {} ): Doc { return grouped From ecdf9548256a868504f9b1ffe0dc8d492ed2d60f Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Jul 2025 13:43:21 +0100 Subject: [PATCH 06/11] unnecessary group since the contents are already being grouped --- src/slang-nodes/ArrayExpression.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/slang-nodes/ArrayExpression.ts b/src/slang-nodes/ArrayExpression.ts index 0f7bb46f2..27ac10d45 100644 --- a/src/slang-nodes/ArrayExpression.ts +++ b/src/slang-nodes/ArrayExpression.ts @@ -1,4 +1,3 @@ -import { doc } from 'prettier'; import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { SlangNode } from './SlangNode.js'; import { ArrayValues } from './ArrayValues.js'; @@ -8,8 +7,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -const { group } = doc.builders; - export class ArrayExpression extends SlangNode { readonly kind = NonterminalKind.ArrayExpression; @@ -24,6 +21,6 @@ export class ArrayExpression extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { - return group(['[', path.call(print, 'items'), ']']); + return ['[', path.call(print, 'items'), ']']; } } From c01d5a079ed368e0b2cd51737f181ac2ca9a4391 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Jul 2025 13:44:47 +0100 Subject: [PATCH 07/11] `expression` is not that complicated that it deserves its own function --- src/slang-nodes/ReturnStatement.ts | 32 ++++++++++++------------------ 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/slang-nodes/ReturnStatement.ts b/src/slang-nodes/ReturnStatement.ts index 3cd3d0c50..8c8bf301f 100644 --- a/src/slang-nodes/ReturnStatement.ts +++ b/src/slang-nodes/ReturnStatement.ts @@ -8,24 +8,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { AstNode } from './types.d.ts'; import type { PrintFunction } from '../types.d.ts'; -function printExpression( - node: ReturnStatement, - path: AstPath, - print: PrintFunction, - options: ParserOptions -): Doc { - const expressionVariantKind = node.expression?.variant.kind; - if (expressionVariantKind) { - return printIndentedGroupOrSpacedDocument( - path.call(print, 'expression'), - expressionVariantKind !== NonterminalKind.TupleExpression && - (!options.experimentalTernaries || - expressionVariantKind !== NonterminalKind.ConditionalExpression) - ); - } - return ''; -} - export class ReturnStatement extends SlangNode { readonly kind = NonterminalKind.ReturnStatement; @@ -46,6 +28,18 @@ export class ReturnStatement extends SlangNode { print: PrintFunction, options: ParserOptions ): Doc { - return ['return', printExpression(this, path, print, options), ';']; + const expressionVariantKind = this.expression?.variant.kind; + return [ + 'return', + expressionVariantKind + ? printIndentedGroupOrSpacedDocument( + path.call(print, 'expression'), + expressionVariantKind !== NonterminalKind.TupleExpression && + (!options.experimentalTernaries || + expressionVariantKind !== NonterminalKind.ConditionalExpression) + ) + : '', + ';' + ]; } } From 51bd27b4a6dd779f271c304c31e8671c8e8c580d Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 23 Jul 2025 16:06:15 +0100 Subject: [PATCH 08/11] more accurate return types for printers --- src/slang-printers/print-separated-item.ts | 2 +- src/slang-printers/print-separated-list.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slang-printers/print-separated-item.ts b/src/slang-printers/print-separated-item.ts index c8f71190f..640f5456d 100644 --- a/src/slang-printers/print-separated-item.ts +++ b/src/slang-printers/print-separated-item.ts @@ -14,7 +14,7 @@ export function printSeparatedItem( lastSeparator = firstSeparator, grouped = firstSeparator !== hardline }: PrintSeparatedOptions = {} -): Doc { +): doc.builders.Group | [doc.builders.Indent, Doc] { return grouped ? group([indent([firstSeparator, item]), lastSeparator]) : [indent([firstSeparator, item]), lastSeparator]; diff --git a/src/slang-printers/print-separated-list.ts b/src/slang-printers/print-separated-list.ts index 6cc472aa8..3d07b3dbf 100644 --- a/src/slang-printers/print-separated-list.ts +++ b/src/slang-printers/print-separated-list.ts @@ -18,7 +18,7 @@ export function printSeparatedList( lastSeparator, grouped }: PrintSeparatedOptions = {} -): Doc { +): doc.builders.Group | [doc.builders.Indent, Doc] { return printSeparatedItem(join(separator, list), { firstSeparator, lastSeparator, From 80449cee06a9be7286e0bd10650008f3cf747c3f Mon Sep 17 00:00:00 2001 From: Klaus Date: Sun, 27 Jul 2025 15:35:25 +0100 Subject: [PATCH 09/11] storing document in const --- src/slang-printers/print-separated-item.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slang-printers/print-separated-item.ts b/src/slang-printers/print-separated-item.ts index 640f5456d..34912723e 100644 --- a/src/slang-printers/print-separated-item.ts +++ b/src/slang-printers/print-separated-item.ts @@ -15,7 +15,9 @@ export function printSeparatedItem( grouped = firstSeparator !== hardline }: PrintSeparatedOptions = {} ): doc.builders.Group | [doc.builders.Indent, Doc] { - return grouped - ? group([indent([firstSeparator, item]), lastSeparator]) - : [indent([firstSeparator, item]), lastSeparator]; + const document: [doc.builders.Indent, Doc] = [ + indent([firstSeparator, item]), + lastSeparator + ]; + return grouped ? group(document) : document; } From fe7376147d48fca3d814ecfd20ea20461312a18c Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 28 Jul 2025 08:38:56 +0100 Subject: [PATCH 10/11] preserve empty lines between dangling comments --- src/common/printer-helpers.js | 26 +++++++++++---- src/slang-nodes/Parameters.ts | 8 +++-- src/slang-nodes/PositionalArguments.ts | 8 +++-- src/slang-printers/print-comments.ts | 32 ++++++++++++++----- .../print-preserving-empty-lines.ts | 2 +- .../Empty/__snapshots__/format.test.js.snap | 1 + 6 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/common/printer-helpers.js b/src/common/printer-helpers.js index bfd6f146f..74a6a180c 100644 --- a/src/common/printer-helpers.js +++ b/src/common/printer-helpers.js @@ -5,19 +5,33 @@ const { isNextLineEmpty } = util; export const printComments = (node, path, options, filter = () => true) => { if (!node.comments) return ''; + function isPrintable(comment) { + return ( + !comment.trailing && + !comment.leading && + !comment.printed && + filter(comment) + ); + } const document = join( line, path - .map((commentPath) => { + .map((commentPath, index, comments) => { const comment = commentPath.getValue(); - if (comment.trailing || comment.leading || comment.printed) { - return null; - } - if (!filter(comment)) { + if (!isPrintable(comment)) { return null; } comment.printed = true; - return options.printer.printComment(commentPath, options); + const isLast = + index === comments.length - 1 || + comments.slice(index + 1).findIndex(isPrintable) === -1; + return [ + options.printer.printComment(commentPath, options), + !isLast && + util.isNextLineEmpty(options.originalText, options.locEnd(comment)) + ? hardline + : '' + ]; }, 'comments') .filter(Boolean) ); diff --git a/src/slang-nodes/Parameters.ts b/src/slang-nodes/Parameters.ts index 9d95fabd7..9c32afa3e 100644 --- a/src/slang-nodes/Parameters.ts +++ b/src/slang-nodes/Parameters.ts @@ -23,12 +23,16 @@ export class Parameters extends SlangNode { this.updateMetadata(this.items); } - print(path: AstPath, print: PrintFunction): Doc { + print( + path: AstPath, + print: PrintFunction, + options: ParserOptions + ): Doc { if (this.items.length > 0) { return printSeparatedList(path.map(print, 'items'), { grouped: false }); } - const parameterComments = printComments(path); + const parameterComments = printComments(path, options); return parameterComments.length > 0 ? printSeparatedItem(parameterComments) diff --git a/src/slang-nodes/PositionalArguments.ts b/src/slang-nodes/PositionalArguments.ts index 51a198e73..c56346575 100644 --- a/src/slang-nodes/PositionalArguments.ts +++ b/src/slang-nodes/PositionalArguments.ts @@ -23,11 +23,15 @@ export class PositionalArguments extends SlangNode { this.updateMetadata(this.items); } - print(path: AstPath, print: PrintFunction): Doc { + print( + path: AstPath, + print: PrintFunction, + options: ParserOptions + ): Doc { if (this.items.length > 0) { return printSeparatedList(path.map(print, 'items')); } - const argumentComments = printComments(path); + const argumentComments = printComments(path, options); return argumentComments.length > 0 ? printSeparatedItem(argumentComments) diff --git a/src/slang-printers/print-comments.ts b/src/slang-printers/print-comments.ts index 1bf98c92e..596cc7d14 100644 --- a/src/slang-printers/print-comments.ts +++ b/src/slang-printers/print-comments.ts @@ -1,22 +1,38 @@ -import { doc } from 'prettier'; +import { doc, util } from 'prettier'; import { printComment } from '../slang-comments/printer.js'; import { joinExisting } from '../slang-utils/join-existing.js'; -import type { AstPath, Doc } from 'prettier'; -import type { AstNode } from '../slang-nodes/types.d.ts'; +import type { AstPath, Doc, ParserOptions } from 'prettier'; +import type { AstNode, Comment } from '../slang-nodes/types.d.ts'; +import { locEnd } from '../slang-utils/loc.js'; -const { line } = doc.builders; +const { hardline, line } = doc.builders; -export function printComments(path: AstPath): Doc[] { +function isPrintable(comment: Comment): boolean { + return !comment.trailing && !comment.leading && !comment.printed; +} + +export function printComments( + path: AstPath, + options: ParserOptions +): Doc[] { return joinExisting( line, - path.map((commentPath) => { + path.map((commentPath, index, comments: Comment[]) => { const comment = commentPath.node; - if (comment.trailing || comment.leading || comment.printed) { + if (!isPrintable(comment)) { return ''; } comment.printed = true; - return printComment(commentPath); + const isLast = + index === comments.length - 1 || + comments.slice(index + 1).findIndex(isPrintable) === -1; + return [ + printComment(commentPath), + !isLast && util.isNextLineEmpty(options.originalText, locEnd(comment)) + ? hardline + : '' + ]; }, 'comments') ); } diff --git a/src/slang-printers/print-preserving-empty-lines.ts b/src/slang-printers/print-preserving-empty-lines.ts index 8acdce5ca..9a9cb0590 100644 --- a/src/slang-printers/print-preserving-empty-lines.ts +++ b/src/slang-printers/print-preserving-empty-lines.ts @@ -36,6 +36,6 @@ export function printPreservingEmptyLines( : '' ]; }, 'items'), - printComments(path) + printComments(path, options) ]; } diff --git a/tests/format/Empty/__snapshots__/format.test.js.snap b/tests/format/Empty/__snapshots__/format.test.js.snap index 85b5731b1..7b6677462 100644 --- a/tests/format/Empty/__snapshots__/format.test.js.snap +++ b/tests/format/Empty/__snapshots__/format.test.js.snap @@ -25,6 +25,7 @@ printWidth: 80 */ =====================================output===================================== // SPDX-License-Identifier: MIT + /** * TODO: Write contract. */ From 6c5f5909f6c705b2c08666febead07105a93219b Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 29 Jul 2025 15:50:47 +0100 Subject: [PATCH 11/11] only attempt to print the dangling comments if there are no items to be printed --- .../print-preserving-empty-lines.ts | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/slang-printers/print-preserving-empty-lines.ts b/src/slang-printers/print-preserving-empty-lines.ts index 9a9cb0590..41bc29495 100644 --- a/src/slang-printers/print-preserving-empty-lines.ts +++ b/src/slang-printers/print-preserving-empty-lines.ts @@ -14,28 +14,27 @@ export function printPreservingEmptyLines( print: PrintFunction, options: ParserOptions ): Doc { - return [ - path.map((childPath) => { - const node = childPath.node; + return path.node.items.length > 0 + ? path.map((childPath) => { + const node = childPath.node; - return [ - // Only attempt to prepend an empty line if `node` is not the first item - !childPath.isFirst && - // YulLabel adds a dedented line so we don't have to prepend a hardline. - (node.kind !== NonterminalKind.YulStatement || - node.variant.kind !== NonterminalKind.YulLabel) - ? hardline - : '', - print(childPath), - // Only attempt to append an empty line if `node` is not the last item - !childPath.isLast && - // Append an empty line if the original text already had an one after the - // current `node` - util.isNextLineEmpty(options.originalText, locEnd(node)) - ? hardline - : '' - ]; - }, 'items'), - printComments(path, options) - ]; + return [ + // Only attempt to prepend an empty line if `node` is not the first item + !childPath.isFirst && + // YulLabel adds a dedented line so we don't have to prepend a hardline. + (node.kind !== NonterminalKind.YulStatement || + node.variant.kind !== NonterminalKind.YulLabel) + ? hardline + : '', + print(childPath), + // Only attempt to append an empty line if `node` is not the last item + !childPath.isLast && + // Append an empty line if the original text already had an one after the + // current `node` + util.isNextLineEmpty(options.originalText, locEnd(node)) + ? hardline + : '' + ]; + }, 'items') + : printComments(path, options); }