From 18bd2c40250801de45a90fb0e014574f7378d2ba Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 10 Jul 2025 00:27:59 +0100 Subject: [PATCH 1/4] storing printed node in const when it's used in a conditional operation --- src/slang-nodes/AssignmentExpression.ts | 5 +++-- src/slang-nodes/DoWhileStatement.ts | 5 +++-- src/slang-nodes/ElseBranch.ts | 5 +++-- src/slang-nodes/ForStatement.ts | 5 +++-- src/slang-nodes/FunctionCallExpression.ts | 8 ++++---- src/slang-nodes/IfStatement.ts | 5 +++-- src/slang-nodes/IndexAccessExpression.ts | 8 ++++---- src/slang-nodes/ReturnStatement.ts | 5 +++-- src/slang-nodes/StateVariableDefinitionValue.ts | 5 +++-- src/slang-nodes/TupleValues.ts | 5 +++-- src/slang-nodes/WhileStatement.ts | 5 +++-- src/slang-printers/create-binary-operation-printer.ts | 9 +++++---- 12 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/slang-nodes/AssignmentExpression.ts b/src/slang-nodes/AssignmentExpression.ts index aecb2ad64..53f349689 100644 --- a/src/slang-nodes/AssignmentExpression.ts +++ b/src/slang-nodes/AssignmentExpression.ts @@ -31,13 +31,14 @@ export class AssignmentExpression extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { + const rightOperand = path.call(print, 'rightOperand'); return [ path.call(print, 'leftOperand'), ` ${this.operator}`, this.rightOperand.variant.kind !== TerminalKind.Identifier && isBinaryOperation(this.rightOperand.variant) - ? group(indent([line, path.call(print, 'rightOperand')])) - : [' ', path.call(print, 'rightOperand')] + ? group(indent([line, rightOperand])) + : [' ', rightOperand] ]; } } diff --git a/src/slang-nodes/DoWhileStatement.ts b/src/slang-nodes/DoWhileStatement.ts index 2685f206b..a195d2512 100644 --- a/src/slang-nodes/DoWhileStatement.ts +++ b/src/slang-nodes/DoWhileStatement.ts @@ -29,11 +29,12 @@ export class DoWhileStatement extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { + const body = path.call(print, 'body'); return [ 'do', this.body.variant.kind === NonterminalKind.Block - ? [' ', path.call(print, 'body'), ' '] - : group([indent([line, path.call(print, 'body')]), line]), + ? [' ', body, ' '] + : group([indent([line, body]), line]), 'while (', printSeparatedItem(path.call(print, 'condition')), ');' diff --git a/src/slang-nodes/ElseBranch.ts b/src/slang-nodes/ElseBranch.ts index dc657f05f..4c00ea025 100644 --- a/src/slang-nodes/ElseBranch.ts +++ b/src/slang-nodes/ElseBranch.ts @@ -30,11 +30,12 @@ export class ElseBranch extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { + const body = path.call(print, 'body'); return [ 'else', isIfStatementOrBlock(this.body.variant) - ? [' ', path.call(print, 'body')] - : group(indent([line, path.call(print, 'body')])) + ? [' ', body] + : group(indent([line, body])) ]; } } diff --git a/src/slang-nodes/ForStatement.ts b/src/slang-nodes/ForStatement.ts index 26d3cb9ca..5e682e3aa 100644 --- a/src/slang-nodes/ForStatement.ts +++ b/src/slang-nodes/ForStatement.ts @@ -50,6 +50,7 @@ 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,8 +62,8 @@ export class ForStatement extends SlangNode { }), ')', this.body.variant.kind === NonterminalKind.Block - ? [' ', path.call(print, 'body')] - : group(indent([line, path.call(print, 'body')])) + ? [' ', body] + : group(indent([line, body])) ]; } } diff --git a/src/slang-nodes/FunctionCallExpression.ts b/src/slang-nodes/FunctionCallExpression.ts index 7facaa062..a115160ab 100644 --- a/src/slang-nodes/FunctionCallExpression.ts +++ b/src/slang-nodes/FunctionCallExpression.ts @@ -32,21 +32,21 @@ export class FunctionCallExpression extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { - const operandDoc = path.call(print, 'operand'); + 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(operandDoc) && operandDoc.label === 'MemberAccessChain') { + 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(operandDoc.contents, { id: groupId }), + group(operand.contents, { id: groupId }), indentIfBreak(argumentsDoc, { groupId }) ]); } - return [operandDoc, argumentsDoc].flat(); + return [operand, argumentsDoc].flat(); } } diff --git a/src/slang-nodes/IfStatement.ts b/src/slang-nodes/IfStatement.ts index ddbf54446..9f7ca7b36 100644 --- a/src/slang-nodes/IfStatement.ts +++ b/src/slang-nodes/IfStatement.ts @@ -37,13 +37,14 @@ 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 - ? [' ', path.call(print, 'body')] - : group(indent([line, path.call(print, 'body')]), { + ? [' ', body] + : group(indent([line, body]), { shouldBreak: bodyKind === NonterminalKind.IfStatement // `if` within `if` }), this.elseBranch diff --git a/src/slang-nodes/IndexAccessExpression.ts b/src/slang-nodes/IndexAccessExpression.ts index 4efc5a4dc..1ba396516 100644 --- a/src/slang-nodes/IndexAccessExpression.ts +++ b/src/slang-nodes/IndexAccessExpression.ts @@ -37,7 +37,7 @@ export class IndexAccessExpression extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { - const operandDoc = path.call(print, 'operand'); + const operand = path.call(print, 'operand'); const indexDoc = [ '[', printSeparatedItem([path.call(print, 'start'), path.call(print, 'end')]), @@ -46,16 +46,16 @@ export class IndexAccessExpression extends SlangNode { // If we are at the end of a MemberAccessChain we should indent the // arguments accordingly. - if (isLabel(operandDoc) && operandDoc.label === 'MemberAccessChain') { + 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(operandDoc.contents, { id: groupId }), + group(operand.contents, { id: groupId }), indentIfBreak(indexDoc, { groupId }) ]); } - return [operandDoc, indexDoc].flat(); + return [operand, indexDoc].flat(); } } diff --git a/src/slang-nodes/ReturnStatement.ts b/src/slang-nodes/ReturnStatement.ts index 8523d0f4f..d470cb5f6 100644 --- a/src/slang-nodes/ReturnStatement.ts +++ b/src/slang-nodes/ReturnStatement.ts @@ -17,12 +17,13 @@ function printExpression( options: ParserOptions ): Doc { const expressionVariant = node.expression?.variant; + const expression = path.call(print, 'expression'); if (expressionVariant) { return expressionVariant.kind === NonterminalKind.TupleExpression || (options.experimentalTernaries && expressionVariant.kind === NonterminalKind.ConditionalExpression) - ? [' ', path.call(print, 'expression')] - : group(indent([line, path.call(print, 'expression')])); + ? [' ', expression] + : group(indent([line, expression])); } return ''; } diff --git a/src/slang-nodes/StateVariableDefinitionValue.ts b/src/slang-nodes/StateVariableDefinitionValue.ts index 77ef4c41f..d082c71f5 100644 --- a/src/slang-nodes/StateVariableDefinitionValue.ts +++ b/src/slang-nodes/StateVariableDefinitionValue.ts @@ -30,8 +30,9 @@ export class StateVariableDefinitionValue extends SlangNode { path: AstPath, print: PrintFunction ): Doc { + const value = path.call(print, 'value'); return this.value.variant.kind === NonterminalKind.ArrayExpression - ? [' = ', path.call(print, 'value')] - : group([' =', indent([line, path.call(print, 'value')])]); + ? [' = ', value] + : group([' =', indent([line, value])]); } } diff --git a/src/slang-nodes/TupleValues.ts b/src/slang-nodes/TupleValues.ts index 686dca1b0..fb707fb61 100644 --- a/src/slang-nodes/TupleValues.ts +++ b/src/slang-nodes/TupleValues.ts @@ -30,10 +30,11 @@ export class TupleValues extends SlangNode { print(path: AstPath, print: PrintFunction): Doc { const singleExpressionVariant = this.getSingleExpression()?.variant; + const items = path.map(print, 'items'); return singleExpressionVariant && singleExpressionVariant.kind !== TerminalKind.Identifier && isBinaryOperation(singleExpressionVariant) - ? path.map(print, 'items') - : printSeparatedList(path.map(print, 'items')); + ? items + : printSeparatedList(items); } } diff --git a/src/slang-nodes/WhileStatement.ts b/src/slang-nodes/WhileStatement.ts index f303b5ce1..6a9514464 100644 --- a/src/slang-nodes/WhileStatement.ts +++ b/src/slang-nodes/WhileStatement.ts @@ -29,13 +29,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 - ? [' ', path.call(print, 'body')] - : group(indent([line, path.call(print, 'body')])) + ? [' ', body] + : group(indent([line, body])) ]; } } diff --git a/src/slang-printers/create-binary-operation-printer.ts b/src/slang-printers/create-binary-operation-printer.ts index b33f23c32..18ddd8952 100644 --- a/src/slang-printers/create-binary-operation-printer.ts +++ b/src/slang-printers/create-binary-operation-printer.ts @@ -18,10 +18,11 @@ function rightOperandPrint( print: PrintFunction, options: ParserOptions ): Doc { - const rightOperand = + const rightOperand = path.call(print, 'rightOperand'); + const rightOperandDoc = options.experimentalOperatorPosition === 'end' - ? [` ${node.operator}`, line, path.call(print, 'rightOperand')] - : [line, `${node.operator} `, path.call(print, 'rightOperand')]; + ? [` ${node.operator}`, line, rightOperand] + : [line, `${node.operator} `, rightOperand]; // If there's only a single binary expression, we want to create a group in // order to avoid having a small right part like -1 be on its own line. @@ -33,7 +34,7 @@ function rightOperandPrint( (!isBinaryOperation(grandparentNode) || grandparentNode.kind === NonterminalKind.AssignmentExpression); - return shouldGroup ? group(rightOperand) : rightOperand; + return shouldGroup ? group(rightOperandDoc) : rightOperandDoc; } export const createBinaryOperationPrinter = From 404759d67f1dc5a0ab3c9403cab68f91c07975b0 Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 10 Jul 2025 00:29:01 +0100 Subject: [PATCH 2/4] the `flat()` call never does anything --- src/slang-nodes/MemberAccessExpression.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/slang-nodes/MemberAccessExpression.ts b/src/slang-nodes/MemberAccessExpression.ts index f71e1782c..159644f24 100644 --- a/src/slang-nodes/MemberAccessExpression.ts +++ b/src/slang-nodes/MemberAccessExpression.ts @@ -131,13 +131,8 @@ 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 = [ - operandDoc, + path.call(print, 'operand'), label('separator', [softline, '.']), path.call(print, 'member') ].flat(); From 713c2b97682e296fb29582d125e39b3160e8fc41 Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 10 Jul 2025 11:21:41 +0100 Subject: [PATCH 3/4] small cases where we gain from storing value in a variable --- src/clean.ts | 9 +------ src/slang-nodes/AssignmentExpression.ts | 5 ++-- src/slang-nodes/ConditionalExpression.ts | 24 ++++++++++--------- src/slang-nodes/ContractSpecifiers.ts | 10 ++++---- src/slang-nodes/ModifierDefinition.ts | 3 ++- src/slang-nodes/ModifierInvocation.ts | 7 +++--- .../PositionalArgumentsDeclaration.ts | 4 +--- src/slang-nodes/ReturnStatement.ts | 8 +++---- .../create-binary-operation-printer.ts | 12 +++++----- 9 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/clean.ts b/src/clean.ts index fee102793..2205dea7e 100644 --- a/src/clean.ts +++ b/src/clean.ts @@ -1,12 +1,5 @@ // Prettier offers a clean way to define ignored properties. -const ignoredProperties = new Set([ - 'loc', - 'range', - 'comments', - // this function is defined at constructor time so it won't pass AST - // comparisons. - 'isEmpty' -]); +const ignoredProperties = new Set(['loc', 'range', 'comments']); // eslint-disable-next-line @typescript-eslint/no-empty-function function clean(/* ast, newObj, parent */): void {} clean.ignoredProperties = ignoredProperties; diff --git a/src/slang-nodes/AssignmentExpression.ts b/src/slang-nodes/AssignmentExpression.ts index 53f349689..1fdc186d1 100644 --- a/src/slang-nodes/AssignmentExpression.ts +++ b/src/slang-nodes/AssignmentExpression.ts @@ -31,12 +31,13 @@ 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}`, - this.rightOperand.variant.kind !== TerminalKind.Identifier && - isBinaryOperation(this.rightOperand.variant) + rightOperandVariant.kind !== TerminalKind.Identifier && + isBinaryOperation(rightOperandVariant) ? group(indent([line, rightOperand])) : [' ', rightOperand] ]; diff --git a/src/slang-nodes/ConditionalExpression.ts b/src/slang-nodes/ConditionalExpression.ts index 102635b5b..049739d07 100644 --- a/src/slang-nodes/ConditionalExpression.ts +++ b/src/slang-nodes/ConditionalExpression.ts @@ -11,26 +11,20 @@ import type { PrintFunction } from '../types.d.ts'; const { group, hardline, ifBreak, indent, line, softline } = doc.builders; -function fillTab({ useTabs, tabWidth }: ParserOptions): Doc { - if (useTabs) return '\t'; - // For the odd case of `tabWidth` of 1 or 0 we initiate `fillTab` as a single - // space. - return tabWidth > 2 ? ' '.repeat(tabWidth - 1) : ' '; -} - function experimentalTernaries( node: ConditionalExpression, path: AstPath, print: PrintFunction, - options: ParserOptions + { useTabs, tabWidth }: ParserOptions ): Doc { const grandparent = path.grandparent as StrictAstNode; const isNested = grandparent.kind === NonterminalKind.ConditionalExpression; const isNestedAsTrueExpression = isNested && grandparent.trueExpression.variant === node; + const falseExpressionVariantKind = node.falseExpression.variant.kind; const falseExpressionInSameLine = - node.falseExpression.variant.kind === NonterminalKind.TupleExpression || - node.falseExpression.variant.kind === NonterminalKind.ConditionalExpression; + falseExpressionVariantKind === NonterminalKind.TupleExpression || + falseExpressionVariantKind === NonterminalKind.ConditionalExpression; // If the `condition` breaks into multiple lines, we add parentheses, // unless it already is a `TupleExpression`. @@ -56,6 +50,14 @@ function experimentalTernaries( { id: groupId } ); + // For the odd case of `tabWidth` of 1 or 0 we initiate `fillTab` as a single + // space. + const fillTab = useTabs + ? '\t' + : tabWidth > 2 + ? ' '.repeat(tabWidth - 1) + : ' '; + const falseExpression = path.call(print, 'falseExpression'); const falseExpressionDoc = [ isNested ? hardline : line, @@ -63,7 +65,7 @@ function experimentalTernaries( falseExpressionInSameLine ? [' ', falseExpression] : ifBreak( - [fillTab(options), indent(falseExpression)], + [fillTab, indent(falseExpression)], [' ', falseExpression], // We only add `fillTab` if we are sure the trueExpression is // indented. diff --git a/src/slang-nodes/ContractSpecifiers.ts b/src/slang-nodes/ContractSpecifiers.ts index fc3cae149..b94f514f3 100644 --- a/src/slang-nodes/ContractSpecifiers.ts +++ b/src/slang-nodes/ContractSpecifiers.ts @@ -28,18 +28,16 @@ export class ContractSpecifiers extends SlangNode { } print(path: AstPath, print: PrintFunction): Doc { - if (this.items.length === 0) return ''; - const [specifier1, specifier2] = path.map(print, 'items'); + + if (typeof specifier1 === 'undefined') return ''; + if (typeof specifier2 === 'undefined') return [' ', specifier1]; const groupId = Symbol('Slang.ContractSpecifiers.inheritance'); return printSeparatedList( [group(specifier1, { id: groupId }), specifier2], - { - firstSeparator: line, - separator: ifBreak('', softline, { groupId }) - } + { firstSeparator: line, separator: ifBreak('', softline, { groupId }) } ); } } diff --git a/src/slang-nodes/ModifierDefinition.ts b/src/slang-nodes/ModifierDefinition.ts index dc290a8eb..f3f1d52ff 100644 --- a/src/slang-nodes/ModifierDefinition.ts +++ b/src/slang-nodes/ModifierDefinition.ts @@ -36,8 +36,9 @@ export class ModifierDefinition extends SlangNode { this.updateMetadata(this.parameters, this.attributes, this.body); if (!this.parameters) { + const attributesLoc = this.attributes.loc; const parametersOffset = - this.attributes.loc.start - this.attributes.loc.leadingOffset; + attributesLoc.start - attributesLoc.leadingOffset; const parametersLoc = { start: parametersOffset, end: parametersOffset, diff --git a/src/slang-nodes/ModifierInvocation.ts b/src/slang-nodes/ModifierInvocation.ts index b5404623b..2148419f1 100644 --- a/src/slang-nodes/ModifierInvocation.ts +++ b/src/slang-nodes/ModifierInvocation.ts @@ -27,11 +27,12 @@ export class ModifierInvocation extends SlangNode { } cleanModifierInvocationArguments(): void { + const argumentsVariant = this.arguments?.variant; if ( - this.arguments && - this.arguments.variant.kind === + argumentsVariant && + argumentsVariant.kind === NonterminalKind.PositionalArgumentsDeclaration && - this.arguments.variant.isEmpty() + argumentsVariant.isEmpty ) { delete this.arguments; } diff --git a/src/slang-nodes/PositionalArgumentsDeclaration.ts b/src/slang-nodes/PositionalArgumentsDeclaration.ts index 01f7871b3..0e1962e62 100644 --- a/src/slang-nodes/PositionalArgumentsDeclaration.ts +++ b/src/slang-nodes/PositionalArgumentsDeclaration.ts @@ -27,11 +27,9 @@ export class PositionalArgumentsDeclaration extends SlangNode { // We need to check the comments at this point because they will be removed // from this node into the root node. - const empty = + this.isEmpty = this.arguments.items.length === 0 && // no arguments !this.comments.some((comment) => isBlockComment(comment)); // no block comments - - this.isEmpty = (): boolean => empty; } print( diff --git a/src/slang-nodes/ReturnStatement.ts b/src/slang-nodes/ReturnStatement.ts index d470cb5f6..2d729aa47 100644 --- a/src/slang-nodes/ReturnStatement.ts +++ b/src/slang-nodes/ReturnStatement.ts @@ -16,12 +16,12 @@ function printExpression( print: PrintFunction, options: ParserOptions ): Doc { - const expressionVariant = node.expression?.variant; + const expressionVariantKind = node.expression?.variant.kind; const expression = path.call(print, 'expression'); - if (expressionVariant) { - return expressionVariant.kind === NonterminalKind.TupleExpression || + if (expressionVariantKind) { + return expressionVariantKind === NonterminalKind.TupleExpression || (options.experimentalTernaries && - expressionVariant.kind === NonterminalKind.ConditionalExpression) + expressionVariantKind === NonterminalKind.ConditionalExpression) ? [' ', expression] : group(indent([line, expression])); } diff --git a/src/slang-printers/create-binary-operation-printer.ts b/src/slang-printers/create-binary-operation-printer.ts index 18ddd8952..f0fda3855 100644 --- a/src/slang-printers/create-binary-operation-printer.ts +++ b/src/slang-printers/create-binary-operation-printer.ts @@ -13,7 +13,7 @@ import type { PrintFunction } from '../types.d.ts'; const { group, line } = doc.builders; function rightOperandPrint( - node: BinaryOperation, + { operator, leftOperand }: BinaryOperation, path: AstPath, print: PrintFunction, options: ParserOptions @@ -21,16 +21,16 @@ function rightOperandPrint( const rightOperand = path.call(print, 'rightOperand'); const rightOperandDoc = options.experimentalOperatorPosition === 'end' - ? [` ${node.operator}`, line, rightOperand] - : [line, `${node.operator} `, rightOperand]; + ? [` ${operator}`, line, rightOperand] + : [line, `${operator} `, rightOperand]; // If there's only a single binary expression, we want to create a group in // order to avoid having a small right part like -1 be on its own line. - const leftOperand = node.leftOperand.variant; + const leftOperandVariant = leftOperand.variant; const grandparentNode = path.grandparent as StrictAstNode; const shouldGroup = - (leftOperand.kind === TerminalKind.Identifier || - !isBinaryOperation(leftOperand)) && + (leftOperandVariant.kind === TerminalKind.Identifier || + !isBinaryOperation(leftOperandVariant)) && (!isBinaryOperation(grandparentNode) || grandparentNode.kind === NonterminalKind.AssignmentExpression); From 18b4607d9b3e9ee7b39e889d782d7fb6aec5104c Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 21 Jul 2025 22:08:02 +0100 Subject: [PATCH 4/4] spotted a `printSeparatedItem` pattern that I missed --- src/slang-nodes/DoWhileStatement.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slang-nodes/DoWhileStatement.ts b/src/slang-nodes/DoWhileStatement.ts index a195d2512..efec4a89e 100644 --- a/src/slang-nodes/DoWhileStatement.ts +++ b/src/slang-nodes/DoWhileStatement.ts @@ -10,7 +10,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 DoWhileStatement extends SlangNode { readonly kind = NonterminalKind.DoWhileStatement; @@ -34,7 +34,7 @@ export class DoWhileStatement extends SlangNode { 'do', this.body.variant.kind === NonterminalKind.Block ? [' ', body, ' '] - : group([indent([line, body]), line]), + : printSeparatedItem(body, { firstSeparator: line }), 'while (', printSeparatedItem(path.call(print, 'condition')), ');'