eclipse-langium/langium

Formatter Problem

Opened this issue · 0 comments

Given the grammar

entry ContextDefinition:
    packages+=ContextPackage+;

ContextPackage:
    'package' packageName=ID '{'
        ('nodes' '{'  '}')?
        ('files' '{'  '}')?
    '}';

hidden terminal WS: /\s+/;
terminal ID: /[_a-zA-Z][\w_]*/;

hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//;
hidden terminal SL_COMMENT: /\/\/[^\n\r]*/;

and formatter

const FormatingOptions = {
    lineBreak: Formatting.newLine({ allowMore: true }),
    lineBreakOne: Formatting.newLines(1, { allowMore: false }),
    emptyLine: Formatting.newLines(2, { allowMore: true }),
    newLineAllowMore: Formatting.newLine({ allowMore: true }),
    oneSpace: Formatting.spaces(1),
    noSpace: Formatting.noSpace(),
    indent: Formatting.indent({ allowMore: true }),
    noIndent: Formatting.noIndent({ allowMore: true }),
    noNewLines: Formatting.newLines(0),
  }

  function formatNestedBody(_: AstNode, formatter: NodeFormatter<AstNode>) {
    console.log("formatNestedBody")
    const opens = formatter.keywords("{")
    const closes = formatter.keywords("}")
    if (opens.nodes.length === closes.nodes.length && opens.nodes.length > 0) {
      const [outerOpen, ...innerOpens] = opens.nodes
      const outerClose = closes.nodes[closes.nodes.length - 1]
      const innerCloses = closes.nodes.slice(0, closes.nodes.length - 1)
  
      // format the outer {}
      console.log(outerOpen.range.start.line, outerClose.range.start.line)
      const bracesOpenOuter = formatter.cst([outerOpen])
      const bracesCloseOuter = formatter.cst([outerClose])
      formatter.interior(bracesOpenOuter, bracesCloseOuter).prepend(FormatingOptions.indent)
      bracesCloseOuter.prepend(FormatingOptions.lineBreakOne)
      bracesOpenOuter.prepend(FormatingOptions.oneSpace)
  
      // format the inner ones inputs {} ...
      for (let i = 0; i < innerOpens.length; i++) {
        const c1 = innerOpens[i]
        const c2 = innerCloses[i]
        console.log(c1.range.start.line, c2.range.start.line)
        const bracesOpen = formatter.cst([c1])
        const bracesClose = formatter.cst([c2])
        formatter.interior(bracesOpen, bracesClose).prepend(FormatingOptions.indent)
        bracesClose.prepend(FormatingOptions.lineBreakOne)
        bracesOpen.prepend(FormatingOptions.oneSpace)
      }
    }
  }

export class CustomFormatter extends AbstractFormatter {
    protected format(node: AstNode): void {
        if (node.$type === 'ContextPackage') {
            const formatter = this.getNodeFormatter(node);
            formatNestedBody(node, formatter)
            formatter
              .keywords(",")
              .append(FormatingOptions.lineBreakOne)
              .prepend(FormatingOptions.noIndent)
              .prepend(FormatingOptions.noNewLines)
              .prepend(FormatingOptions.noSpace)
        }
    }
}

sample file

package sample {
    nodes {} files {}
}

is not formatted as expected (nicely indented)

when i refactor the grammar to use fragments, it suddenly works.
any idea what the problem could be

ContextPackage:
    'package' packageName=ID '{'
        (ContextNodeList)?
        (ContextFileList)?
    '}';

fragment ContextFileList:
    x?='files' '{'  '}';

fragment ContextNodeList*:
    y?='nodes' '{'  '}';

unfortunately the workaround cannot be applied in production cause of the
AST node has no document
bug