blob: 227e3a80b8172378add1e07b3693324e08af6666 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package formatter
import (
"bytes"
"fmt"
"mojom/mojom_tool/lexer"
"mojom/mojom_tool/mojom"
"sort"
"strings"
)
// printer is a single-use struct that keeps track of the state necessary to
// pretty-print a mojom file.
// It is used by FormatMojom (see formatter.go)
//
// Internall, the entry point is writeMojomFile and the result is obtained by
// calling result.
//
// The pretty-printing of individual elements is implemented using the write.*
// methods. Those methods accept the element to be printed (or the element
// of which a portion is to be printed) and write the pretty-printed output
// to |buffer|.
type printer struct {
// used indicates whether the printer has been used or not. If set to true,
// writeMojomFile will panic.
used bool
// buffer is the buffer to which the write.* methods write the pretty-printed
// mojom file element.
buffer bytes.Buffer
// indentSize is the current size of the indentation in number of space runes.
indentSize int
// eolComment is the comment to be printed at the end of the current line.
eolComment *lexer.Token
// linePos is the number of runes that have been written to the current line.
linePos int
// maxLineLength is the maximum number of runes that should be printed on a line.
// A negative maxLineLength indicates no maximum line length should be enforced.
// This is currently only used to decide when to break up a method on different lines.
maxLineLength int
// mojomFile is the file being printed.
mojomFile *mojom.MojomFile
// noFormat indicates we are in a block where formatting has been disabled.
noFormat bool
// token that begins a block where formatting has been disabled.
noFormatStart lexer.Token
// indentSize at the time noFormat is set.
noFormatStartIndent int
}
// newPrinter is a constructor for printer.
func newPrinter() (p *printer) {
p = new(printer)
p.maxLineLength = 80
return
}
// result returns the pretty-printed version of what was given to the printer.
// Usually, this is a pretty-printed MojomFile.
func (p *printer) result() string {
return p.buffer.String()
}
// writeMojomFile is the entry point of the pretty printer.
// It takes a mojom file with attached comments and creates a pretty-printed
// representation.
func (p *printer) writeMojomFile(mojomFile *mojom.MojomFile) {
if p.used {
panic("A printer can only be used once!")
}
p.used = true
p.mojomFile = mojomFile
p.writeAttributes(mojomFile.Attributes)
p.writeModuleNamespace(mojomFile.ModuleNamespace)
if len(mojomFile.Imports) > 0 {
p.writeImportedFiles(mojomFile.Imports)
}
for _, declaredObject := range mojomFile.DeclaredObjects {
p.writeDeclaredObject(declaredObject)
p.nl()
}
if mojomFile.FinalComments != nil {
finalComments := trimEmptyLinesEnd(mojomFile.FinalComments)
if len(finalComments) > 0 {
p.nl()
p.writeCommentBlocks(finalComments, true)
}
}
p.eofNoFormat()
}
// writeModuleNamespace writes a mojom file's module statement and associated
// comments.
func (p *printer) writeModuleNamespace(module *mojom.ModuleNamespace) {
if module == nil || module.Identifier == "" {
return
}
p.writeBeforeComments(module)
p.writef("module %s;", module.Identifier)
p.writeRightComments(module)
p.nl()
}
// writeImportedFiles identifies blocks of imports, sorts and writes them.
func (p *printer) writeImportedFiles(imports []*mojom.ImportedFile) {
blockStart := 0
for i, imported := range imports {
if isNewBlock(imported) {
p.writeImportedFilesBlock(imports[blockStart:i])
blockStart = i
p.nl()
}
}
p.writeImportedFilesBlock(imports[blockStart:len(imports)])
}
// writeImportedFilesBlock sorts and writes a slice of import statements.
func (p *printer) writeImportedFilesBlock(imports []*mojom.ImportedFile) {
sortImportedFiles(imports)
for _, imported := range imports {
p.writeBeforeComments(imported)
p.writef("import \"%v\";", imported.SpecifiedName)
p.writeRightComments(imported)
p.nl()
}
}
// writeDeclaredObject writes a declared object's attributes, then its
// preceeding comments and finally the object itself.
func (p *printer) writeDeclaredObject(declaredObject mojom.DeclaredObject) {
if isNewBlock(declaredObject) {
p.nl()
}
p.writeDeclaredObjectAttributes(declaredObject)
p.writeBeforeComments(declaredObject)
switch o := declaredObject.(type) {
case *mojom.MojomStruct:
p.writeMojomStruct(o)
case *mojom.StructField:
p.writeStructField(o)
case *mojom.MojomUnion:
p.writeMojomUnion(o)
case *mojom.UnionField:
p.writeUnionField(o)
case *mojom.MojomEnum:
p.writeMojomEnum(o)
case *mojom.EnumValue:
p.writeEnumValue(o)
case *mojom.UserDefinedConstant:
p.writeUserDefinedConstant(o)
case *mojom.MojomInterface:
p.writeMojomInterface(o)
case *mojom.MojomMethod:
p.writeMojomMethod(o)
default:
panic(fmt.Sprintf("writeDeclaredObject cannot write %v.", declaredObject))
}
}
func (p *printer) writeMojomStruct(mojomStruct *mojom.MojomStruct) {
p.write("struct ")
p.writeDeclaredObjectsContainer(mojomStruct)
}
func (p *printer) writeMojomUnion(mojomUnion *mojom.MojomUnion) {
p.write("union ")
p.writeDeclaredObjectsContainer(mojomUnion)
}
func (p *printer) writeMojomEnum(mojomEnum *mojom.MojomEnum) {
p.write("enum ")
p.writeDeclaredObjectsContainer(mojomEnum)
}
func (p *printer) writeMojomInterface(mojomInterface *mojom.MojomInterface) {
p.write("interface ")
p.writeDeclaredObjectsContainer(mojomInterface)
}
func (p *printer) writeMojomMethod(mojomMethod *mojom.MojomMethod) {
splitResponse := false
if p.maxLineLength > 0 {
scratch := newPrinter()
scratch.maxLineLength = -1
scratch.writeMojomMethod(mojomMethod)
if len(scratch.result())+p.lineLength() > p.maxLineLength {
splitResponse = true
}
}
p.write(mojomMethod.NameToken().Text)
if mojomMethod.DeclaredOrdinal() >= 0 {
p.writef("@%v", mojomMethod.DeclaredOrdinal())
}
p.writeMethodParams(mojomMethod.Parameters, mojomMethod)
if mojomMethod.ResponseParameters != nil {
if splitResponse {
p.nl()
p.write(" ")
}
p.write(" => ")
p.writeMethodParams(mojomMethod.ResponseParameters, mojomMethod)
}
p.write(";")
p.writeRightComments(mojomMethod)
}
// writeMethodParams writes the pretty-printed method parameters represented by
// a MojomStruct.
func (p *printer) writeMethodParams(params *mojom.MojomStruct, method *mojom.MojomMethod) {
// The presence or absence of a semi-colon needs to be taken into account when
// trying to enforce the line size limit.
semiColon := false
if params.StructType() == mojom.StructTypeSyntheticResponse || method.ResponseParameters == nil {
semiColon = true
}
ownLine := false
if p.maxLineLength > 0 {
scratch := newPrinter()
scratch.maxLineLength = -1
scratch.writeMethodParams(params, method)
scratchLineLen := len(scratch.result()) + p.lineLength()
// If a semi-colon will be added after the parameters, account for it.
if semiColon {
scratchLineLen += 1
}
if scratchLineLen > p.maxLineLength {
ownLine = true
}
}
p.write("(")
declaredObjects := params.GetDeclaredObjects()
extraIndent := p.lineLength() - p.indentSize
if ownLine {
// If we are printing every parameter on its own line, check to see if
// aligning the parameters to the right of the method name can be done.
for i, param := range declaredObjects {
scratch := newPrinter()
scratch.maxLineLength = -1
scratch.writeMethodParam(param.(*mojom.StructField))
// If any parameter would go beyond the maximum line length, start the
// list of parameters on the line after the method name and indented 4
// more than the method itself.
scratchLineLen := len(scratch.result()) + p.lineLength()
// If this is the last parameter, account for the closing parenthesis.
if i == len(declaredObjects)-1 {
scratchLineLen += 1
// If a semi-colo will be added after the parameters, account for it.
if semiColon {
scratchLineLen += 1
}
}
if scratchLineLen > p.maxLineLength {
p.nl()
extraIndent = 4
break
}
}
p.indentSize += extraIndent
}
for i, param := range declaredObjects {
p.writeMethodParam(param.(*mojom.StructField))
if i < len(declaredObjects)-1 {
p.write(",")
if ownLine {
p.nl()
} else {
p.write(" ")
}
}
}
if ownLine {
p.indentSize -= extraIndent
}
p.write(")")
}
// writeMethodParam writes a single pretty-printed method parameter represented
// by a StructField.
func (p *printer) writeMethodParam(param *mojom.StructField) {
p.writeAttributesSingleLine(param.Attributes())
p.writeBeforeComments(param)
p.writeTypeRef(param.FieldType)
p.writef(" %v", param.NameToken().Text)
if param.DeclaredOrdinal() >= 0 {
p.writef("@%v", param.DeclaredOrdinal())
}
if param.DefaultValue != nil {
p.write("=")
p.writeValueRef(param.DefaultValue)
}
p.writeRightComments(param)
}
func (p *printer) writeUserDefinedConstant(constant *mojom.UserDefinedConstant) {
p.write("const ")
p.writeTypeRef(constant.DeclaredType())
p.writef(" %s", constant.NameToken().Text)
p.write(" = ")
p.writeValueRef(constant.ValueRef())
p.write(";")
p.writeRightComments(constant)
}
func (p *printer) writeStructField(structField *mojom.StructField) {
p.writeTypeRef(structField.FieldType)
p.writef(" %v", structField.NameToken().Text)
if structField.DeclaredOrdinal() >= 0 {
p.writef("@%v", structField.DeclaredOrdinal())
}
if structField.DefaultValue != nil {
p.write(" = ")
p.writeValueRef(structField.DefaultValue)
}
p.write(";")
p.writeRightComments(structField)
}
func (p *printer) writeUnionField(unionField *mojom.UnionField) {
p.writeTypeRef(unionField.FieldType)
p.writef(" %v", unionField.NameToken().Text)
if unionField.DeclaredOrdinal() >= 0 {
p.writef("@%v", unionField.DeclaredOrdinal())
}
p.write(";")
p.writeRightComments(unionField)
}
func (p *printer) writeEnumValue(enumValue *mojom.EnumValue) {
p.write(enumValue.NameToken().Text)
if enumValue.ValueRef() != nil {
p.write(" = ")
p.writeValueRef(enumValue.ValueRef())
}
p.write(",")
p.writeRightComments(enumValue)
}
// writeDeclaredObjectsContainer
// DeclaredObjectsContainers are MojomEnum, MojomUnion, MojomInterface, MojomStruct.
// This method writes the name of the object, an opening brace, the contained
// declarations (fields, enum values, nested declarations and methods) and the
// closing brace.
func (p *printer) writeDeclaredObjectsContainer(container mojom.DeclaredObjectsContainer) {
p.writef("%v {", container.(mojom.DeclaredObject).NameToken().Text)
if len(container.GetDeclaredObjects()) == 0 && !containsFinalComments(container.(mojom.MojomElement)) {
p.writef("};")
return
}
p.writeRightComments(container.(mojom.MojomElement))
p.incIndent()
p.nl()
declaredObjects := container.GetDeclaredObjects()
for i, declaredObject := range declaredObjects {
if i == 0 {
trimEmptyLinesDeclaredObject(declaredObject)
}
p.writeDeclaredObject(declaredObject)
if i < len(declaredObjects)-1 {
p.nl()
}
}
// Write the comments at the end of the struct, enum, union or interface body.
p.writeFinalComments(container)
p.decIndent()
p.nl()
p.write("};")
}
func (p *printer) writeDeclaredObjectAttributes(declaredObject mojom.DeclaredObject) {
switch declaredObject.(type) {
case *mojom.MojomStruct:
p.writeAttributes(declaredObject.Attributes())
case *mojom.MojomUnion:
p.writeAttributes(declaredObject.Attributes())
case *mojom.MojomEnum:
p.writeAttributes(declaredObject.Attributes())
case *mojom.UserDefinedConstant:
p.writeAttributes(declaredObject.Attributes())
case *mojom.MojomInterface:
p.writeAttributes(declaredObject.Attributes())
case *mojom.MojomMethod:
p.writeAttributes(declaredObject.Attributes())
case *mojom.StructField:
p.writeAttributesSingleLine(declaredObject.Attributes())
case *mojom.UnionField:
p.writeAttributesSingleLine(declaredObject.Attributes())
case *mojom.EnumValue:
p.writeAttributesSingleLine(declaredObject.Attributes())
default:
panic(fmt.Sprintf("writeDeclaredObjectAttributes cannot write %v.", declaredObject))
}
}
// See writeAttributesBase.
func (p *printer) writeAttributes(attrs *mojom.Attributes) {
p.writeAttributesBase(attrs, false)
}
// writeAttributesSingleLine writes the provided attributes on a single line.
// This is used by the writeMethodParam to write the attributes of a parameter.
func (p *printer) writeAttributesSingleLine(attrs *mojom.Attributes) {
p.writeAttributesBase(attrs, true)
}
// writeAttributesBase writes the provided attributes. If singleLine is
// false, every attribute is written on a new line and a new line is appended
// after the attributes are written. Otherwise, the attributes are written on
// a single line.
func (p *printer) writeAttributesBase(attrs *mojom.Attributes, singleLine bool) {
if attrs == nil {
return
}
p.writeBeforeComments(attrs)
if len(attrs.List) == 0 {
return
}
p.writef("[")
for idx, attr := range attrs.List {
p.writeAttribute(&attr)
if idx < len(attrs.List)-1 {
p.writef(",")
if !singleLine {
p.nl()
}
p.write(" ")
}
}
p.writef("]")
if singleLine {
p.write(" ")
} else {
p.nl()
}
}
func (p *printer) writeAttribute(attr *mojom.MojomAttribute) {
p.writeBeforeComments(attr)
p.writef("%s=", attr.Key)
p.writeValueRef(attr.Value)
p.writeRightComments(attr)
}
func (p *printer) writeTypeRef(t mojom.TypeRef) {
switch t.TypeRefKind() {
case mojom.TypeKindUserDefined:
u := t.(*mojom.UserTypeRef)
p.write(u.Identifier())
if u.IsInterfaceRequest() {
p.write("&")
}
if u.Nullable() {
p.write("?")
}
case mojom.TypeKindArray:
a := t.(*mojom.ArrayTypeRef)
p.write("array<")
p.writeTypeRef(a.ElementType())
if a.FixedLength() >= 0 {
p.writef(", %v", a.FixedLength())
}
p.write(">")
if a.Nullable() {
p.write("?")
}
case mojom.TypeKindMap:
m := t.(*mojom.MapTypeRef)
p.write("map<")
p.writeTypeRef(m.KeyType())
p.write(", ")
p.writeTypeRef(m.ValueType())
p.write(">")
if m.Nullable() {
p.write("?")
}
case mojom.TypeKindHandle:
fallthrough
case mojom.TypeKindSimple:
fallthrough
case mojom.TypeKindString:
p.write(t.String())
default:
panic("This unhandled TypeRefKind: " + t.String())
}
}
func (p *printer) writeValueRef(value mojom.ValueRef) {
switch v := value.(type) {
case mojom.LiteralValue:
// The sign of a numeric literal is a separate token from the number itself.
// So the token that is stored does not include a sign. Here we check if
// the literal is a signed constant and if so, we print a negative sign if
// appropriate.
if _, ok := v.ValueType().(mojom.LiteralType); ok {
negative := false
switch num := v.Value().(type) {
case int8:
negative = num < 0
case int16:
negative = num < 0
case int32:
negative = num < 0
case int64:
negative = num < 0
case float32:
negative = num < 0
case float64:
negative = num < 0
}
if negative {
p.write("-")
}
}
if v.Token() == nil {
panic(fmt.Sprintf("nil token when trying to print %s.", v.String()))
}
p.write(v.Token().Text)
case *mojom.UserValueRef:
p.write(v.Identifier())
default:
panic("Cannot handle this value ref.")
}
}
// writeBeforeComments writes the comments preceeding a MojomElement.
func (p *printer) writeBeforeComments(el mojom.MojomElement) {
attachedComments := el.AttachedComments()
if attachedComments == nil {
return
}
p.writeAboveComments(el)
p.writeLeftComments(el)
}
// writeAboveComments writes the comments above of a MojomElement.
func (p *printer) writeAboveComments(el mojom.MojomElement) {
attachedComments := el.AttachedComments()
if attachedComments == nil {
return
}
p.writeCommentBlocks(attachedComments.Above, true)
}
func (p *printer) writeFinalComments(container mojom.DeclaredObjectsContainer) {
el := container.(mojom.MojomElement)
attachedComments := el.AttachedComments()
if attachedComments == nil {
return
}
finalComments := trimEmptyLinesEnd(attachedComments.Final)
if len(finalComments) == 0 {
return
}
// Only print blank lines if there is something other than comments in the
// container.
if len(container.GetDeclaredObjects()) > 0 {
p.nl()
if finalComments[0].Kind == lexer.EmptyLine {
p.nl()
}
}
p.writeCommentBlocks(finalComments, false)
}
// writeLeftComments writes the comments left of a MojomElement.
func (p *printer) writeLeftComments(el mojom.MojomElement) {
attachedComments := el.AttachedComments()
if attachedComments == nil {
return
}
for _, comment := range attachedComments.Left {
if comment.Kind == lexer.SingleLineComment {
panic("SingleLineComment cannot be on the left of an element.")
}
p.writef("%s ", comment.Text)
}
}
// writeRightComments writes the comments to the right of a MojomElement.
func (p *printer) writeRightComments(el mojom.MojomElement) {
attachedComments := el.AttachedComments()
if attachedComments == nil {
return
}
for i, comment := range attachedComments.Right {
if comment.Kind == lexer.SingleLineComment {
if i < len(attachedComments.Right)-1 {
panic("You can't have anything after a SingleLineComment!")
}
p.setEolComment(comment)
break
}
p.writef(" %s", comment.Text)
}
}
// writeCommentBlocks writes a slice of comments. If finalEol is true, a new
// line is written after all the comments are written.
func (p *printer) writeCommentBlocks(comments []lexer.Token, finalEol bool) {
for i, comment := range comments {
switch comment.Kind {
case lexer.SingleLineComment:
if comment.Text == "// no-format" {
p.startNoFormat(comment)
} else if comment.Text == "// end-no-format" {
p.endNoFormat(comment)
}
p.writeSingleLineComment(comment)
if finalEol || i < len(comments)-1 {
p.nl()
}
case lexer.MultiLineComment:
p.writeMultiLineComment(comment)
if finalEol || i < len(comments)-1 {
p.nl()
}
case lexer.EmptyLine:
// We only use EmptyLine here to separate comment blocks. And we collapse
// sequences of empty lines to a single empty line.
if i != 0 && comments[i-1].Kind != lexer.EmptyLine {
p.nl()
}
default:
panic(fmt.Sprintf("%s is not a comment.", comment))
}
}
}
func (p *printer) writeSingleLineComment(comment lexer.Token) {
if comment.Kind != lexer.SingleLineComment {
panic(fmt.Sprintf("This is not a SingleLineComment: %s", comment))
}
commentText := comment.Text
// We expect that the first 2 characters are // followed by a space or tab.
// If the third character is not a space or tab, we insert a space.
// There is an exception for three forward slashes which are allowed.
if len(commentText) > 2 && !strings.ContainsAny(" \t/", commentText[2:3]) {
commentText = "// " + commentText[2:]
}
p.write(commentText)
}
func (p *printer) writeMultiLineComment(comment lexer.Token) {
if comment.Kind != lexer.MultiLineComment {
panic(fmt.Sprintf("This is not a MultiLineComment: %s", comment))
}
lines := strings.Split(comment.Text, "\n")
for i, line := range lines {
trimmed := strings.Trim(line, " \t")
// If a line starts with * we assume the user is trying to use the pattern
// whereby they align the comment with spaces after the *. Otherwise,
// we try to align the comment by prepending 3 spaces.
if i != 0 && len(trimmed) > 0 {
if trimmed[0] == '*' {
p.write(" ")
} else {
p.write(" ")
}
}
if p.lineLength()+len(trimmed) <= p.maxLineLength || p.maxLineLength < 0 {
p.write(trimmed)
} else {
// If the comment is too long to fit it on a line, break up the line along
// spaces and wrap the line.
words := strings.Split(trimmed, " ")
for i, word := range words {
if i > 0 && len(word)+p.lineLength()+1 > p.maxLineLength {
p.nl()
if trimmed[0] == '*' {
p.write(" *")
} else {
p.write(" ")
}
}
if i > 0 {
p.write(" ")
}
p.write(word)
}
}
if i < len(lines)-1 {
p.nl()
}
}
}
// Utility functions
// writef writes according to the format specifier. See fmt.Printf for the
// format specifier.
func (p *printer) writef(format string, a ...interface{}) {
p.write(fmt.Sprintf(format, a...))
}
// write writes the provided string to the buffer.
// If nothing has been written yet on the current line write also writes the
// current indentation level.
func (p *printer) write(s string) {
if strings.ContainsRune(s, '\n') {
panic(fmt.Sprintf("Only the nl method can write a new line: %q", s))
}
// We only print the indentation if the line is not empty.
if p.linePos == 0 {
if !p.noFormat {
p.buffer.WriteString(strings.Repeat(" ", p.indentSize))
}
p.linePos += p.indentSize
}
p.linePos += len(s)
if !p.noFormat {
p.buffer.WriteString(s)
}
}
// nl writes a new line. Before writing the new line, nl writes the last
// comment on the line.
func (p *printer) nl() {
// Before going to the next line, print the last comment on the line.
if p.eolComment != nil {
if !p.noFormat {
p.write(" ")
p.writeSingleLineComment(*p.eolComment)
}
p.eolComment = nil
}
if !p.noFormat {
p.buffer.WriteString("\n")
}
p.linePos = 0
}
func (p *printer) incIndent() {
p.indentSize += 2
}
func (p *printer) decIndent() {
p.indentSize -= 2
if p.indentSize < 0 {
panic("The printer is attempting to use negative indentation!")
}
}
// setEolComment sets the comment that is to be printed at the end of the current line.
// The last comment on the line is not necessarily associated with the last
// element on the line. So when the last comment is found, we record that
// comment so we may print it right before going to the next line.
func (p *printer) setEolComment(comment lexer.Token) {
if p.eolComment != nil {
panic("There is space for only one comment at the end of the line!")
}
if comment.Kind != lexer.SingleLineComment {
panic("Only SingleLineComments need to be handled specially at the end of line.")
}
p.eolComment = &comment
}
// lineLength returns the length of the line so far. If nothing has been written
// to the line yet, it returns the indent size.
// The purpose of lineLenght is to answer the question: If I write something now
// how far on the current line will it be written?
func (p *printer) lineLength() int {
if p.linePos == 0 {
return p.indentSize
}
return p.linePos
}
// startNoFormat disables writes to the buffer (future writes are discarded).
func (p *printer) startNoFormat(startToken lexer.Token) {
if p.noFormat {
return
}
p.noFormat = true
p.noFormatStart = startToken
p.noFormatStartIndent = p.indentSize
}
// endNoFormat re-enables writes to the buffer and writes the source starting
// at the beginning of p.startToken and ending right before endToken.
func (p *printer) endNoFormat(endToken lexer.Token) {
if !p.noFormat {
return
}
fileContents := p.mojomFile.FileContents()
notFormatted := fileContents[p.noFormatStart.SourcePosBytes:endToken.SourcePosBytes]
// Remove any unformatted indentation right before the end of the unformatted block.
notFormatted = strings.TrimRight(notFormatted, " \t")
p.buffer.WriteString(strings.Repeat(" ", p.noFormatStartIndent))
p.buffer.WriteString(notFormatted)
p.noFormat = false
}
// eofNoFormat handles the case where formatting had been disabled but not
// re-enabled. It writes everything after the formatting was ended.
func (p *printer) eofNoFormat() {
if !p.noFormat {
return
}
fileContents := p.mojomFile.FileContents()
p.endNoFormat(lexer.Token{SourcePosBytes: len(fileContents)})
}
// Standalone utilities that do not operate on the buffer.
// isNewBlock determines if an empty line should be printed above the element
// under consideration. The purpose is to respect user-intention to separate
// elements in a mojom file.
//
// If the element has attributes, and the first "comment" attached above the
// attributes is an empty line, the element is a new block.
// If the element has no attributes and the first "comment" attached above the
// element itself is an empty line, the element is a new block.
// Else, the element is not a new block.
func isNewBlock(mojomElement mojom.MojomElement) bool {
if declaredObject, ok := mojomElement.(mojom.DeclaredObject); ok {
if declaredObject.Attributes() != nil {
comments := declaredObject.Attributes().AttachedComments()
if comments == nil || len(comments.Above) == 0 {
return false
}
return comments.Above[0].Kind == lexer.EmptyLine
}
}
comments := mojomElement.AttachedComments()
if comments == nil || len(comments.Above) == 0 {
return false
}
return comments.Above[0].Kind == lexer.EmptyLine
}
// If the element has AttachedComments and there is at least one comment
// in AttachedComments.Final which is not an EmptyLine, containsFinalComments
// returns true. Otherwise, it returns false.
func containsFinalComments(el mojom.MojomElement) bool {
attachedComments := el.AttachedComments()
if attachedComments == nil {
return false
}
for _, comment := range attachedComments.Final {
if comment.Kind != lexer.EmptyLine {
return true
}
}
return false
}
// trimEmptyLinesEnd trims out empty line comments at the end of a slice of comments.
func trimEmptyLinesEnd(comments []lexer.Token) []lexer.Token {
lastNonEmpty := -1
for i, comment := range comments {
if comment.Kind != lexer.EmptyLine {
lastNonEmpty = i
}
}
return comments[:lastNonEmpty+1]
}
// trimEmptyLinesBegin trims out empty line comments at the beginning of a slice of comments.
func trimEmptyLinesBegin(comments []lexer.Token) []lexer.Token {
for i, comment := range comments {
if comment.Kind != lexer.EmptyLine {
return comments[i:]
}
}
return comments[len(comments):]
}
// trimEmptyLinesDeclaredObject trims out empty lines from the beginning of the
// comments on a DeclaredObject or its Attributes.
func trimEmptyLinesDeclaredObject(declaredObject mojom.DeclaredObject) {
if declaredObject.Attributes() != nil {
comments := declaredObject.Attributes().AttachedComments()
if comments == nil {
return
}
comments.Above = trimEmptyLinesBegin(comments.Above)
return
}
comments := declaredObject.AttachedComments()
if comments == nil || len(comments.Above) == 0 {
return
}
comments.Above = trimEmptyLinesBegin(comments.Above)
}
// Following is a utility to sort slices of |ImportedFile|s.
// sortImportedFiles sorts the slice of imported files it receives.
func sortImportedFiles(imports []*mojom.ImportedFile) {
sort.Sort(&importedFilesSorter{imports})
}
type importedFilesSorter struct {
imports []*mojom.ImportedFile
}
// See sort.Interface.
func (ifs *importedFilesSorter) Len() int {
return len(ifs.imports)
}
// See sort.Interface.
func (ifs *importedFilesSorter) Less(i, j int) bool {
return ifs.imports[i].SpecifiedName < ifs.imports[j].SpecifiedName
}
// See sort.Interface.
func (ifs *importedFilesSorter) Swap(i, j int) {
ifs.imports[i], ifs.imports[j] = ifs.imports[j], ifs.imports[i]
}