From 1952706b071028fb2d0e294dcc06cd67e2a33d68 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 16 Mar 2026 15:38:52 -0300 Subject: [PATCH 1/3] Placing comments in the middle of a member access chained expression in the correct place --- ...andle-member-access-expression-comments.ts | 39 +++++++++++++++++++ src/slang-comments/handlers/index.ts | 2 + src/slang-nodes/MemberAccessExpression.ts | 7 +++- tests/format/MemberAccess/MemberAccess.sol | 13 +++++++ .../__snapshots__/format.test.js.snap | 24 ++++++++++++ 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/slang-comments/handlers/handle-member-access-expression-comments.ts diff --git a/src/slang-comments/handlers/handle-member-access-expression-comments.ts b/src/slang-comments/handlers/handle-member-access-expression-comments.ts new file mode 100644 index 000000000..298a03e70 --- /dev/null +++ b/src/slang-comments/handlers/handle-member-access-expression-comments.ts @@ -0,0 +1,39 @@ +import { NonterminalKind } from '@nomicfoundation/slang/cst'; +import { util } from 'prettier'; + +import type { HandlerParams } from './types.d.ts'; + +const { addLeadingComment, addTrailingComment } = util; + +export default function handleMemberAccessExpressionComments({ + precedingNode, + enclosingNode, + followingNode, + comment +}: HandlerParams): boolean { + if (enclosingNode?.kind !== NonterminalKind.MemberAccessExpression) { + return false; + } + + if (followingNode !== undefined && followingNode === enclosingNode.operand) { + addLeadingComment(followingNode, comment); + return true; + } + + if ( + precedingNode !== undefined && + followingNode !== undefined && + precedingNode === enclosingNode.operand && + followingNode === enclosingNode.member + ) { + addTrailingComment(precedingNode, comment); + return true; + } + + if (precedingNode !== undefined && precedingNode === enclosingNode.member) { + addTrailingComment(precedingNode, comment); + return true; + } + + return false; +} diff --git a/src/slang-comments/handlers/index.ts b/src/slang-comments/handlers/index.ts index 901d0a767..fffe5b6df 100644 --- a/src/slang-comments/handlers/index.ts +++ b/src/slang-comments/handlers/index.ts @@ -5,6 +5,7 @@ import handleElseBranchComments from './handle-else-branch-comments.js'; import handleIfStatementComments from './handle-if-statement-comments.js'; import handleInterfaceDefinitionComments from './handle-interface-definition-comments.js'; import handleLibraryDefinitionComments from './handle-library-definition-comments.js'; +import handleMemberAccessExpressionComments from './handle-member-access-expression-comments.js'; 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'; @@ -22,6 +23,7 @@ export default [ handleIfStatementComments, handleInterfaceDefinitionComments, handleLibraryDefinitionComments, + handleMemberAccessExpressionComments, handleModifierInvocationComments, handleParametersDeclarationComments, handlePositionalArgumentsDeclarationComments, diff --git a/src/slang-nodes/MemberAccessExpression.ts b/src/slang-nodes/MemberAccessExpression.ts index c0759b746..d24723794 100644 --- a/src/slang-nodes/MemberAccessExpression.ts +++ b/src/slang-nodes/MemberAccessExpression.ts @@ -128,8 +128,13 @@ export class MemberAccessExpression extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { + let operandDoc = path.call(print, 'operand'); + if (Array.isArray(operandDoc)) { + operandDoc = operandDoc.flat(); + } + const document = [ - path.call(print, 'operand'), + operandDoc, label('separator', [softline, '.']), path.call(print, 'member') ].flat(); diff --git a/tests/format/MemberAccess/MemberAccess.sol b/tests/format/MemberAccess/MemberAccess.sol index 7b66b782b..380f60e62 100644 --- a/tests/format/MemberAccess/MemberAccess.sol +++ b/tests/format/MemberAccess/MemberAccess.sol @@ -62,6 +62,19 @@ contract MemberAccess { abi.encodeWithSelector(f.selector), abi.encode(returned1) ); +// Comments in between the chain + game. +// CONFIG +// Do not touch +config. // Window +// Resolution 1000 +resolveWindow = 1000; +config.defaultDelay = 0; +begin + .// Comment 1 + functionCall(/* Comment about parameter */ parameter) + .// Comment 2 + end; } } diff --git a/tests/format/MemberAccess/__snapshots__/format.test.js.snap b/tests/format/MemberAccess/__snapshots__/format.test.js.snap index cb5d69af3..b352f7005 100644 --- a/tests/format/MemberAccess/__snapshots__/format.test.js.snap +++ b/tests/format/MemberAccess/__snapshots__/format.test.js.snap @@ -70,6 +70,19 @@ contract MemberAccess { abi.encodeWithSelector(f.selector), abi.encode(returned1) ); +// Comments in between the chain + game. +// CONFIG +// Do not touch +config. // Window +// Resolution 1000 +resolveWindow = 1000; +config.defaultDelay = 0; +begin + .// Comment 1 + functionCall(/* Comment about parameter */ parameter) + .// Comment 2 + end; } } @@ -206,6 +219,17 @@ contract MemberAccess { abi.encodeWithSelector(f.selector), abi.encode(returned1) ); + // Comments in between the chain + game + // CONFIG + // Do not touch + .config // Window + // Resolution 1000 + .resolveWindow = 1000; + config.defaultDelay = 0; + begin // Comment 1 + .functionCall(/* Comment about parameter */ parameter) // Comment 2 + .end; } } From 9c1f96a97a3b5eff85da0ad24c2a39d65b8474ec Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 16 Mar 2026 15:47:35 -0300 Subject: [PATCH 2/3] no need to check for undefined --- .../handlers/handle-member-access-expression-comments.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/slang-comments/handlers/handle-member-access-expression-comments.ts b/src/slang-comments/handlers/handle-member-access-expression-comments.ts index 298a03e70..d07900710 100644 --- a/src/slang-comments/handlers/handle-member-access-expression-comments.ts +++ b/src/slang-comments/handlers/handle-member-access-expression-comments.ts @@ -15,14 +15,12 @@ export default function handleMemberAccessExpressionComments({ return false; } - if (followingNode !== undefined && followingNode === enclosingNode.operand) { + if (followingNode === enclosingNode.operand) { addLeadingComment(followingNode, comment); return true; } if ( - precedingNode !== undefined && - followingNode !== undefined && precedingNode === enclosingNode.operand && followingNode === enclosingNode.member ) { @@ -30,7 +28,7 @@ export default function handleMemberAccessExpressionComments({ return true; } - if (precedingNode !== undefined && precedingNode === enclosingNode.member) { + if (precedingNode === enclosingNode.member) { addTrailingComment(precedingNode, comment); return true; } From 1b5448a22de65c00f91def1e891dbe27d69b651b Mon Sep 17 00:00:00 2001 From: Klaus Date: Sun, 22 Mar 2026 22:35:21 -0300 Subject: [PATCH 3/3] moving chain printer logic into a separate function --- src/slang-nodes/FunctionCallExpression.ts | 25 ++++------------- src/slang-nodes/IndexAccessExpression.ts | 24 +++-------------- src/slang-nodes/MemberAccessExpression.ts | 11 +++++--- .../print-member-access-chain-item.ts | 27 +++++++++++++++++++ src/slang-utils/is-label.ts | 10 +++++-- 5 files changed, 50 insertions(+), 47 deletions(-) create mode 100644 src/slang-printers/print-member-access-chain-item.ts diff --git a/src/slang-nodes/FunctionCallExpression.ts b/src/slang-nodes/FunctionCallExpression.ts index edee9045b..51a885ae8 100644 --- a/src/slang-nodes/FunctionCallExpression.ts +++ b/src/slang-nodes/FunctionCallExpression.ts @@ -1,8 +1,6 @@ import { NonterminalKind } from '@nomicfoundation/slang/cst'; -import { doc } from 'prettier'; -import { isLabel } from '../slang-utils/is-label.js'; -import { printGroupAndIndentIfBreakPair } from '../slang-printers/print-group-and-indent-if-break-pair.js'; import { extractVariant } from '../slang-utils/extract-variant.js'; +import { printPossibleMemberAccessChainItem } from '../slang-printers/print-member-access-chain-item.js'; import { SlangNode } from './SlangNode.js'; import { Expression } from './Expression.js'; import { ArgumentsDeclaration } from './ArgumentsDeclaration.js'; @@ -12,8 +10,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { CollectedMetadata, PrintFunction } from '../types.d.ts'; import type { AstNode } from './types.d.ts'; -const { label } = doc.builders; - export class FunctionCallExpression extends SlangNode { readonly kind = NonterminalKind.FunctionCallExpression; @@ -39,20 +35,9 @@ export class FunctionCallExpression extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { - const operand = path.call(print, 'operand'); - const argumentsDoc = path.call(print, 'arguments'); - - // If we are at the end of a MemberAccessChain we should indent the - // arguments accordingly. - if (isLabel(operand) && operand.label === 'MemberAccessChain') { - // We wrap the expression in a label in case there is an IndexAccess or - // a FunctionCall following this IndexAccess. - return label( - 'MemberAccessChain', - printGroupAndIndentIfBreakPair(operand.contents, argumentsDoc) - ); - } - - return [operand, argumentsDoc].flat(); + return printPossibleMemberAccessChainItem( + path.call(print, 'operand'), + path.call(print, 'arguments') + ); } } diff --git a/src/slang-nodes/IndexAccessExpression.ts b/src/slang-nodes/IndexAccessExpression.ts index 065a00649..5a6e17752 100644 --- a/src/slang-nodes/IndexAccessExpression.ts +++ b/src/slang-nodes/IndexAccessExpression.ts @@ -1,9 +1,7 @@ import { NonterminalKind } from '@nomicfoundation/slang/cst'; -import { doc } from 'prettier'; 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 { extractVariant } from '../slang-utils/extract-variant.js'; +import { printPossibleMemberAccessChainItem } from '../slang-printers/print-member-access-chain-item.js'; import { SlangNode } from './SlangNode.js'; import { Expression } from './Expression.js'; import { IndexAccessEnd } from './IndexAccessEnd.js'; @@ -13,8 +11,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier'; import type { CollectedMetadata, PrintFunction } from '../types.d.ts'; import type { AstNode } from './types.d.ts'; -const { label } = doc.builders; - export class IndexAccessExpression extends SlangNode { readonly kind = NonterminalKind.IndexAccessExpression; @@ -47,24 +43,10 @@ export class IndexAccessExpression extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { - const operand = path.call(print, 'operand'); - const indexDoc = [ + return printPossibleMemberAccessChainItem(path.call(print, 'operand'), [ '[', printSeparatedItem([path.call(print, 'start'), path.call(print, 'end')]), ']' - ]; - - // If we are at the end of a MemberAccessChain we should indent the - // arguments accordingly. - if (isLabel(operand) && operand.label === 'MemberAccessChain') { - // We wrap the expression in a label in case there is an IndexAccess or - // a FunctionCall following this IndexAccess. - return label( - 'MemberAccessChain', - printGroupAndIndentIfBreakPair(operand.contents, indexDoc) - ); - } - - return [operand, indexDoc].flat(); + ]); } } diff --git a/src/slang-nodes/MemberAccessExpression.ts b/src/slang-nodes/MemberAccessExpression.ts index d24723794..3a17e0376 100644 --- a/src/slang-nodes/MemberAccessExpression.ts +++ b/src/slang-nodes/MemberAccessExpression.ts @@ -3,6 +3,7 @@ import { doc } from 'prettier'; import { isLabel } from '../slang-utils/is-label.js'; import { extractVariant } from '../slang-utils/extract-variant.js'; import { isChainableExpression } from '../slang-utils/is-chainable-expression.js'; +import { memberAccessChainLabel } from '../slang-printers/print-member-access-chain-item.js'; import { SlangNode } from './SlangNode.js'; import { Expression } from './Expression.js'; import { TerminalNode } from './TerminalNode.js'; @@ -14,6 +15,8 @@ import type { AstNode, ChainableExpression, StrictAstNode } from './types.d.ts'; const { group, indent, label, softline } = doc.builders; +const separatorLabel = Symbol('separator'); + function isEndOfChain( node: ChainableExpression, path: AstPath @@ -91,13 +94,13 @@ function isEndOfChain( * be printed. */ function processChain(chain: Doc[]): Doc { - const firstSeparatorIndex = chain.findIndex( - (element) => isLabel(element) && element.label === 'separator' + const firstSeparatorIndex = chain.findIndex((element) => + isLabel(element, separatorLabel) ); // We wrap the expression in a label in case there is an IndexAccess or // a FunctionCall following this MemberAccess. - return label('MemberAccessChain', [ + return label(memberAccessChainLabel, [ // The doc[] before the first separator chain.slice(0, firstSeparatorIndex), // The doc[] containing the rest of the chain @@ -135,7 +138,7 @@ export class MemberAccessExpression extends SlangNode { const document = [ operandDoc, - label('separator', [softline, '.']), + label(separatorLabel, [softline, '.']), path.call(print, 'member') ].flat(); diff --git a/src/slang-printers/print-member-access-chain-item.ts b/src/slang-printers/print-member-access-chain-item.ts new file mode 100644 index 000000000..f163f8ed9 --- /dev/null +++ b/src/slang-printers/print-member-access-chain-item.ts @@ -0,0 +1,27 @@ +import { doc } from 'prettier'; +import { isLabel } from '../slang-utils/is-label.js'; +import { printGroupAndIndentIfBreakPair } from './print-group-and-indent-if-break-pair.js'; + +import type { Doc } from 'prettier'; + +const { label } = doc.builders; + +export const memberAccessChainLabel = Symbol('MemberAccessChain'); + +export function printPossibleMemberAccessChainItem( + operand: Doc, + predicate: Doc +): Doc { + // If we are at the end of a MemberAccessChain we should indent the + // arguments accordingly. + if (isLabel(operand, memberAccessChainLabel)) { + // We wrap the expression in a label in case there is an IndexAccess or + // a FunctionCall following Node. + return label( + memberAccessChainLabel, + printGroupAndIndentIfBreakPair(operand.contents, predicate) + ); + } + + return [operand, predicate].flat(); +} diff --git a/src/slang-utils/is-label.ts b/src/slang-utils/is-label.ts index 4116b52b4..77a92d35d 100644 --- a/src/slang-utils/is-label.ts +++ b/src/slang-utils/is-label.ts @@ -1,5 +1,11 @@ import type { Doc, doc } from 'prettier'; -export function isLabel(document: Doc): document is doc.builders.Label { - return (document as doc.builders.DocCommand).type === 'label'; +export function isLabel( + document: Doc, + value: symbol +): document is doc.builders.Label { + return ( + (document as doc.builders.DocCommand).type === 'label' && + (document as doc.builders.Label).label === value + ); }