diff --git a/app/gui2/shared/ast/tree.ts b/app/gui2/shared/ast/tree.ts index dbb1e0bd316d..d62c0188a9aa 100644 --- a/app/gui2/shared/ast/tree.ts +++ b/app/gui2/shared/ast/tree.ts @@ -608,7 +608,9 @@ export class App extends Ast { const spacedEquals = useParens && !!nameSpecification?.equals.whitespace if (useParens) yield ensureSpaced(parens.open, verbatim) if (nameSpecification) { - yield ensureSpacedIf(nameSpecification.name, !useParens, verbatim) + yield useParens ? + preferUnspaced(nameSpecification.name) + : ensureSpaced(nameSpecification.name, verbatim) yield ensureSpacedOnlyIf(nameSpecification.equals, spacedEquals, verbatim) } yield ensureSpacedOnlyIf(argument, !nameSpecification || spacedEquals, verbatim) @@ -626,36 +628,39 @@ export class App extends Ast { return super.printSubtree(info, offset, parentIndent, verbatim_) } } -function ensureSpacedIf( - child: NodeChild, - condition: boolean, - verbatim: boolean | undefined, -): NodeChild { - return condition ? ensureSpaced(child, verbatim) : child -} function ensureSpacedOnlyIf( child: NodeChild, condition: boolean, verbatim: boolean | undefined, -): NodeChild { +): ConcreteChild { return condition ? ensureSpaced(child, verbatim) : ensureUnspaced(child, verbatim) } -function ensureSpaced(child: NodeChild, verbatim: boolean | undefined): NodeChild { - if (verbatim && child.whitespace != null) return child - return child.whitespace ? child : { ...child, whitespace: ' ' } + +type ConcreteChild = { whitespace: string; node: T } +function isConcrete(child: NodeChild): child is ConcreteChild { + return child.whitespace !== undefined +} +function tryAsConcrete(child: NodeChild): ConcreteChild | undefined { + return isConcrete(child) ? child : undefined } -function ensureUnspaced(child: NodeChild, verbatim: boolean | undefined): NodeChild { - if (verbatim && child.whitespace != null) return child - return child.whitespace === '' ? child : { ...child, whitespace: '' } +function ensureSpaced(child: NodeChild, verbatim: boolean | undefined): ConcreteChild { + const concreteInput = tryAsConcrete(child) + if (verbatim && concreteInput) return concreteInput + return concreteInput?.whitespace ? concreteInput : { ...child, whitespace: ' ' } } -function preferSpacedIf(child: NodeChild, condition: boolean): NodeChild { +function ensureUnspaced(child: NodeChild, verbatim: boolean | undefined): ConcreteChild { + const concreteInput = tryAsConcrete(child) + if (verbatim && concreteInput) return concreteInput + return concreteInput?.whitespace === '' ? concreteInput : { ...child, whitespace: '' } +} +function preferSpacedIf(child: NodeChild, condition: boolean): ConcreteChild { return condition ? preferSpaced(child) : preferUnspaced(child) } -function preferUnspaced(child: NodeChild): NodeChild { - return child.whitespace === undefined ? { ...child, whitespace: '' } : child +function preferUnspaced(child: NodeChild): ConcreteChild { + return tryAsConcrete(child) ?? { ...child, whitespace: '' } } -function preferSpaced(child: NodeChild): NodeChild { - return child.whitespace === undefined ? { ...child, whitespace: ' ' } : child +function preferSpaced(child: NodeChild): ConcreteChild { + return tryAsConcrete(child) ?? { ...child, whitespace: ' ' } } export class MutableApp extends App implements MutableAst { declare readonly module: MutableModule @@ -1775,7 +1780,7 @@ export class Function extends Ast { } } - *concreteChildren(verbatim?: boolean): IterableIterator { + *concreteChildren(_verbatim?: boolean): IterableIterator { const { name, argumentDefinitions, equals, body } = getAll(this.fields) yield name for (const def of argumentDefinitions) yield* def @@ -1865,9 +1870,9 @@ export class Assignment extends Ast { *concreteChildren(verbatim?: boolean): IterableIterator { const { pattern, equals, expression } = getAll(this.fields) - yield pattern + yield ensureUnspaced(pattern, verbatim) yield ensureSpacedOnlyIf(equals, expression.whitespace !== '', verbatim) - yield expression + yield preferSpaced(expression) } } export class MutableAssignment extends Assignment implements MutableAst { @@ -1926,7 +1931,7 @@ export class BodyBlock extends Ast { *concreteChildren(_verbatim?: boolean): IterableIterator { for (const line of this.fields.get('lines')) { - yield line.newline ?? { node: Token.new('\n', RawAst.Token.Type.Newline) } + yield preferUnspaced(line.newline) if (line.expression) yield line.expression } } @@ -2223,15 +2228,15 @@ export class Vector extends Ast { return Vector.tryBuild(inputs, elementBuilder, edit) } - *concreteChildren(_verbatim?: boolean): IterableIterator { + *concreteChildren(verbatim?: boolean): IterableIterator { const { open, elements, close } = getAll(this.fields) - yield unspaced(open.node) + yield ensureUnspaced(open, verbatim) let isFirst = true for (const { delimiter, value } of elements) { if (isFirst && value) { yield preferUnspaced(value) } else { - yield delimiter + yield preferUnspaced(delimiter) if (value) yield preferSpaced(value) } isFirst = false