github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e

Used only by github.com/bazelbuild/bazel-gazelle, expecting 80c7f0d45d7e
This commit is contained in:
Jordan Liggitt 2019-04-05 10:33:56 -04:00
parent 2cbf496c8e
commit 4bd9dcb855
21 changed files with 1566 additions and 766 deletions

2
go.mod
View File

@ -234,7 +234,7 @@ replace (
github.com/auth0/go-jwt-middleware => github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7 github.com/auth0/go-jwt-middleware => github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7
github.com/aws/aws-sdk-go => github.com/aws/aws-sdk-go v1.16.26 github.com/aws/aws-sdk-go => github.com/aws/aws-sdk-go v1.16.26
github.com/bazelbuild/bazel-gazelle => github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e github.com/bazelbuild/bazel-gazelle => github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e
github.com/bazelbuild/buildtools => github.com/bazelbuild/buildtools v0.0.0-20171220125010-1a9c38e0df93 github.com/bazelbuild/buildtools => github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e
github.com/beorn7/perks => github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1 github.com/beorn7/perks => github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1
github.com/blang/semver => github.com/blang/semver v3.5.0+incompatible github.com/blang/semver => github.com/blang/semver v3.5.0+incompatible
github.com/boltdb/bolt => github.com/boltdb/bolt v1.3.1 github.com/boltdb/bolt => github.com/boltdb/bolt v1.3.1

4
go.sum
View File

@ -44,8 +44,8 @@ github.com/aws/aws-sdk-go v1.16.26 h1:GWkl3rkRO/JGRTWoLLIqwf7AWC4/W/1hMOUZqmX0js
github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e h1:k7E/Rdb9kYVDDrdCX46/GLgHhbxBs4BQz7+FDRf8lcg= github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e h1:k7E/Rdb9kYVDDrdCX46/GLgHhbxBs4BQz7+FDRf8lcg=
github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e/go.mod h1:uHBSeeATKpVazAACZBDPL/Nk/UhQDDsJWDlqYJo8/Us= github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e/go.mod h1:uHBSeeATKpVazAACZBDPL/Nk/UhQDDsJWDlqYJo8/Us=
github.com/bazelbuild/buildtools v0.0.0-20171220125010-1a9c38e0df93 h1:HOcA9ovaT+3lN0RGVE8NpLHLU2poukBPujkIQOGI40U= github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e h1:VuTBHPJNCQ88Okm9ld5SyLCvU50soWJYQYjQFdcDxew=
github.com/bazelbuild/buildtools v0.0.0-20171220125010-1a9c38e0df93/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU= github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU=
github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1 h1:OnJHjoVbY69GG4gclp0ngXfywigLhR6rrgUxmxQRWO4= github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1 h1:OnJHjoVbY69GG4gclp0ngXfywigLhR6rrgUxmxQRWO4=
github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs= github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs=

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-go. // Code generated by protoc-gen-go. DO NOT EDIT.
// source: api_proto/api.proto // source: api_proto/api.proto
// DO NOT EDIT!
/* /*
Package devtools_buildozer is a generated protocol buffer package. Package devtools_buildozer is a generated protocol buffer package.
@ -101,9 +100,7 @@ func (m *Output_Record_Field) String() string { return proto.CompactT
func (*Output_Record_Field) ProtoMessage() {} func (*Output_Record_Field) ProtoMessage() {}
func (*Output_Record_Field) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0, 0} } func (*Output_Record_Field) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0, 0} }
type isOutput_Record_Field_Value interface { type isOutput_Record_Field_Value interface{ isOutput_Record_Field_Value() }
isOutput_Record_Field_Value()
}
type Output_Record_Field_Text struct { type Output_Record_Field_Text struct {
Text string `protobuf:"bytes,1,opt,name=text,oneof"` Text string `protobuf:"bytes,1,opt,name=text,oneof"`

View File

@ -49,4 +49,4 @@ def genfile_check_test(src, gen):
data = [src, gen], data = [src, gen],
args = ["$(location " + src + ")", "$(location " + gen + ")"], args = ["$(location " + src + ")", "$(location " + gen + ")"],
) )

View File

@ -22,6 +22,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"github.com/bazelbuild/buildtools/tables"
) )
// Parse parses the input data and returns the corresponding parse tree. // Parse parses the input data and returns the corresponding parse tree.
@ -35,15 +37,19 @@ func Parse(filename string, data []byte) (*File, error) {
// An input represents a single input file being parsed. // An input represents a single input file being parsed.
type input struct { type input struct {
// Lexing state. // Lexing state.
filename string // name of input file, for errors filename string // name of input file, for errors
complete []byte // entire input complete []byte // entire input
remaining []byte // remaining input remaining []byte // remaining input
token []byte // token being scanned token []byte // token being scanned
lastToken string // most recently returned token, for error messages lastToken string // most recently returned token, for error messages
pos Position // current input position pos Position // current input position
comments []Comment // accumulated comments lineComments []Comment // accumulated line comments
endRule int // position of end of current rule suffixComments []Comment // accumulated suffix comments
depth int // nesting of [ ] { } ( ) endStmt int // position of the end of the current statement
depth int // nesting of [ ] { } ( )
cleanLine bool // true if the current line only contains whitespace before the current position
indent int // current line indentation in spaces
indents []int // stack of indentation levels in spaces
// Parser state. // Parser state.
file *File // returned top-level syntax tree file *File // returned top-level syntax tree
@ -55,14 +61,26 @@ type input struct {
} }
func newInput(filename string, data []byte) *input { func newInput(filename string, data []byte) *input {
// The syntax requires that each simple statement ends with '\n', however it's optional at EOF.
// If `data` doesn't end with '\n' we add it here to keep parser simple.
// It shouldn't affect neither the parsed tree nor its formatting.
data = append(data, '\n')
return &input{ return &input{
filename: filename, filename: filename,
complete: data, complete: data,
remaining: data, remaining: data,
pos: Position{Line: 1, LineRune: 1, Byte: 0}, pos: Position{Line: 1, LineRune: 1, Byte: 0},
cleanLine: true,
indents: []int{0},
endStmt: -1, // -1 denotes it's not inside a statement
} }
} }
func (in *input) currentIndent() int {
return in.indents[len(in.indents)-1]
}
// parse parses the input file. // parse parses the input file.
func (in *input) parse() (f *File, err error) { func (in *input) parse() (f *File, err error) {
// The parser panics for both routine errors like syntax errors // The parser panics for both routine errors like syntax errors
@ -169,29 +187,23 @@ func (in *input) Lex(val *yySymType) int {
// Skip past spaces, stopping at non-space or EOF. // Skip past spaces, stopping at non-space or EOF.
countNL := 0 // number of newlines we've skipped past countNL := 0 // number of newlines we've skipped past
for !in.eof() { for !in.eof() {
// The parser does not track indentation, because for the most part // If a single statement is split into multiple lines, we don't need
// BUILD expressions don't care about how they are indented. // to track indentations and unindentations within these lines. For example:
// However, we do need to be able to distinguish
// //
// x = y[0] // def f(
// # This indentation should be ignored
// x):
// # This unindentation should be ignored
// # Actual indentation is from 0 to 2 spaces here
// return x
// //
// from the occasional // To handle this case, when we reach the beginning of a statement we scan forward to see where
// it should end and record the number of input bytes remaining at that endpoint.
// //
// x = y // If --format_bzl is set to false, top level blocks (e.g. an entire function definition)
// [0] // is considered as a single statement.
// if in.endStmt != -1 && len(in.remaining) == in.endStmt {
// To handle this one case, when we reach the beginning of a in.endStmt = -1
// top-level BUILD expression, we scan forward to see where
// it should end and record the number of input bytes remaining
// at that endpoint. When we reach that point in the input, we
// insert an implicit semicolon to force the two expressions
// to stay separate.
//
if in.endRule != 0 && len(in.remaining) == in.endRule {
in.endRule = 0
in.lastToken = "implicit ;"
val.tok = ";"
return ';'
} }
// Skip over spaces. Count newlines so we can give the parser // Skip over spaces. Count newlines so we can give the parser
@ -199,15 +211,19 @@ func (in *input) Lex(val *yySymType) int {
// for top-level comment assignment. // for top-level comment assignment.
c := in.peekRune() c := in.peekRune()
if c == ' ' || c == '\t' || c == '\r' || c == '\n' { if c == ' ' || c == '\t' || c == '\r' || c == '\n' {
if c == '\n' && in.endRule == 0 {
// Not in a rule. Tell parser about top-level blank line.
in.startToken(val)
in.readRune()
in.endToken(val)
return '\n'
}
if c == '\n' { if c == '\n' {
in.indent = 0
in.cleanLine = true
if in.endStmt == -1 {
// Not in a statememt. Tell parser about top-level blank line.
in.startToken(val)
in.readRune()
in.endToken(val)
return '\n'
}
countNL++ countNL++
} else if c == ' ' && in.cleanLine {
in.indent++
} }
in.readRune() in.readRune()
continue continue
@ -215,6 +231,11 @@ func (in *input) Lex(val *yySymType) int {
// Comment runs to end of line. // Comment runs to end of line.
if c == '#' { if c == '#' {
// If a line contains just a comment its indentation level doesn't matter.
// Reset it to zero.
in.indent = 0
in.cleanLine = true
// Is this comment the only thing on its line? // Is this comment the only thing on its line?
// Find the last \n before this # and see if it's all // Find the last \n before this # and see if it's all
// spaces from there to here. // spaces from there to here.
@ -231,10 +252,12 @@ func (in *input) Lex(val *yySymType) int {
isSuffix = false isSuffix = false
} }
// Consume comment. // Consume comment without the \n it ends with.
in.startToken(val) in.startToken(val)
for len(in.remaining) > 0 && in.readRune() != '\n' { for len(in.remaining) > 0 && in.peekRune() != '\n' {
in.readRune()
} }
in.endToken(val) in.endToken(val)
val.tok = strings.TrimRight(val.tok, "\n") val.tok = strings.TrimRight(val.tok, "\n")
@ -243,22 +266,27 @@ func (in *input) Lex(val *yySymType) int {
// If we are at top level (not in a rule), hand the comment to // If we are at top level (not in a rule), hand the comment to
// the parser as a _COMMENT token. The grammar is written // the parser as a _COMMENT token. The grammar is written
// to handle top-level comments itself. // to handle top-level comments itself.
if in.endRule == 0 { if in.endStmt == -1 {
// Not in a rule. Tell parser about top-level comment. // Not in a statement. Tell parser about top-level comment.
return _COMMENT return _COMMENT
} }
// Otherwise, save comment for later attachment to syntax tree. // Otherwise, save comment for later attachment to syntax tree.
if countNL > 1 { if countNL > 1 {
in.comments = append(in.comments, Comment{val.pos, "", false}) in.lineComments = append(in.lineComments, Comment{val.pos, ""})
} }
in.comments = append(in.comments, Comment{val.pos, val.tok, isSuffix}) if isSuffix {
countNL = 1 in.suffixComments = append(in.suffixComments, Comment{val.pos, val.tok})
} else {
in.lineComments = append(in.lineComments, Comment{val.pos, val.tok})
}
countNL = 0
continue continue
} }
if c == '\\' && len(in.remaining) >= 2 && in.remaining[1] == '\n' { if c == '\\' && len(in.remaining) >= 2 && in.remaining[1] == '\n' {
// We can ignore a trailing \ at end of line. // We can ignore a trailing \ at end of line together with the \n.
in.readRune()
in.readRune() in.readRune()
continue continue
} }
@ -267,6 +295,41 @@ func (in *input) Lex(val *yySymType) int {
break break
} }
// Check for changes in indentation
// Skip if --format_bzl is set to false, if we're inside a statement, or if there were non-space
// characters before in the current line.
if tables.FormatBzlFiles && in.endStmt == -1 && in.cleanLine {
if in.indent > in.currentIndent() {
// A new indentation block starts
in.indents = append(in.indents, in.indent)
in.lastToken = "indent"
in.cleanLine = false
return _INDENT
} else if in.indent < in.currentIndent() {
// An indentation block ends
in.indents = in.indents[:len(in.indents)-1]
// It's a syntax error if the current line indentation level in now greater than
// currentIndent(), should be either equal (a parent block continues) or still less
// (need to unindent more).
if in.indent > in.currentIndent() {
in.pos = val.pos
in.Error("unexpected indentation")
}
in.lastToken = "unindent"
return _UNINDENT
}
}
in.cleanLine = false
// If the file ends with an indented block, return the corresponding amounts of unindents.
if in.eof() && in.currentIndent() > 0 {
in.indents = in.indents[:len(in.indents)-1]
in.lastToken = "unindent"
return _UNINDENT
}
// Found the beginning of the next token. // Found the beginning of the next token.
in.startToken(val) in.startToken(val)
defer in.endToken(val) defer in.endToken(val)
@ -277,16 +340,9 @@ func (in *input) Lex(val *yySymType) int {
return _EOF return _EOF
} }
// If endRule is 0, we need to recompute where the end // If endStmt is 0, we need to recompute where the end of the next statement is.
// of the next rule (Python expression) is, so that we can if in.endStmt == -1 {
// generate a virtual end-of-rule semicolon (see above). in.endStmt = len(in.skipStmt(in.remaining))
if in.endRule == 0 {
in.endRule = len(in.skipPython(in.remaining))
if in.endRule == 0 {
// skipPython got confused.
// No more virtual semicolons.
in.endRule = -1
}
} }
// Punctuation tokens. // Punctuation tokens.
@ -301,12 +357,17 @@ func (in *input) Lex(val *yySymType) int {
in.readRune() in.readRune()
return c return c
case '.', '-', '%', ':', ';', ',', '/', '*': // single-char tokens case '.', ':', ';', ',': // single-char tokens
in.readRune() in.readRune()
return c return c
case '<', '>', '=', '!', '+': // possibly followed by = case '<', '>', '=', '!', '+', '-', '*', '/', '%': // possibly followed by =
in.readRune() in.readRune()
if c == '/' && in.peekRune() == '/' {
// integer division
in.readRune()
}
if in.peekRune() == '=' { if in.peekRune() == '=' {
in.readRune() in.readRune()
switch c { switch c {
@ -318,8 +379,8 @@ func (in *input) Lex(val *yySymType) int {
return _EQ return _EQ
case '!': case '!':
return _NE return _NE
case '+': default:
return _ADDEQ return _AUGM
} }
} }
return c return c
@ -395,15 +456,17 @@ func (in *input) Lex(val *yySymType) int {
in.Error(fmt.Sprintf("unexpected input character %#q", c)) in.Error(fmt.Sprintf("unexpected input character %#q", c))
} }
// Look for raw Python block (class, def, if, etc at beginning of line) and pass through. if !tables.FormatBzlFiles {
if in.depth == 0 && in.pos.LineRune == 1 && hasPythonPrefix(in.remaining) { // Look for raw Python block (class, def, if, etc at beginning of line) and pass through.
// Find end of Python block and advance input beyond it. if in.depth == 0 && in.pos.LineRune == 1 && hasPythonPrefix(in.remaining) {
// Have to loop calling readRune in order to maintain line number info. // Find end of Python block and advance input beyond it.
rest := in.skipPython(in.remaining) // Have to loop calling readRune in order to maintain line number info.
for len(in.remaining) > len(rest) { rest := in.skipStmt(in.remaining)
in.readRune() for len(in.remaining) > len(rest) {
in.readRune()
}
return _PYTHON
} }
return _PYTHON
} }
// Scan over alphanumeric identifier. // Scan over alphanumeric identifier.
@ -442,11 +505,15 @@ var keywordToken = map[string]int{
"for": _FOR, "for": _FOR,
"if": _IF, "if": _IF,
"else": _ELSE, "else": _ELSE,
"elif": _ELIF,
"in": _IN, "in": _IN,
"is": _IS, "is": _IS,
"lambda": _LAMBDA, "lambda": _LAMBDA,
"load": _LOAD,
"not": _NOT, "not": _NOT,
"or": _OR, "or": _OR,
"def": _DEF,
"return": _RETURN,
} }
// Python scanning. // Python scanning.
@ -457,6 +524,10 @@ var keywordToken = map[string]int{
// hasPythonPrefix reports whether p begins with a keyword that would // hasPythonPrefix reports whether p begins with a keyword that would
// introduce an uninterpreted Python block. // introduce an uninterpreted Python block.
func hasPythonPrefix(p []byte) bool { func hasPythonPrefix(p []byte) bool {
if tables.FormatBzlFiles {
return false
}
for _, pre := range prefixes { for _, pre := range prefixes {
if hasPrefixSpace(p, pre) { if hasPrefixSpace(p, pre) {
return true return true
@ -474,10 +545,14 @@ var prefixes = []string{
"for", "for",
"if", "if",
"try", "try",
"else",
"elif",
"except",
} }
// hasPrefixSpace reports whether p begins with pre followed by a space or colon. // hasPrefixSpace reports whether p begins with pre followed by a space or colon.
func hasPrefixSpace(p []byte, pre string) bool { func hasPrefixSpace(p []byte, pre string) bool {
if len(p) <= len(pre) || p[len(pre)] != ' ' && p[len(pre)] != '\t' && p[len(pre)] != ':' { if len(p) <= len(pre) || p[len(pre)] != ' ' && p[len(pre)] != '\t' && p[len(pre)] != ':' {
return false return false
} }
@ -489,44 +564,46 @@ func hasPrefixSpace(p []byte, pre string) bool {
return true return true
} }
func isBlankOrComment(b []byte) bool { // A utility function for the legacy formatter.
// Returns whether a given code starts with a top-level statement (maybe with some preceeding
// comments and blank lines)
func isOutsideBlock(b []byte) bool {
isBlankLine := true
isComment := false
for _, c := range b { for _, c := range b {
if c == '#' || c == '\n' { switch {
return true case c == ' ' || c == '\t' || c == '\r':
} isBlankLine = false
if c != ' ' && c != '\t' && c != '\r' { case c == '#':
return false isBlankLine = false
isComment = true
case c == '\n':
isBlankLine = true
isComment = false
default:
if !isComment {
return isBlankLine
}
} }
} }
return true return true
} }
// hasPythonContinuation reports whether p begins with a keyword that // skipStmt returns the data remaining after the statement beginning at p.
// continues an uninterpreted Python block. // It does not advance the input position.
func hasPythonContinuation(p []byte) bool {
for _, pre := range continuations {
if hasPrefixSpace(p, pre) {
return true
}
}
return false
}
// These keywords continue uninterpreted Python blocks.
var continuations = []string{
"except",
"else",
}
// skipPython returns the data remaining after the uninterpreted
// Python block beginning at p. It does not advance the input position.
// (The only reason for the input receiver is to be able to call in.Error.) // (The only reason for the input receiver is to be able to call in.Error.)
func (in *input) skipPython(p []byte) []byte { func (in *input) skipStmt(p []byte) []byte {
quote := byte(0) // if non-zero, the kind of quote we're in quote := byte(0) // if non-zero, the kind of quote we're in
tripleQuote := false // if true, the quote is a triple quote tripleQuote := false // if true, the quote is a triple quote
depth := 0 // nesting depth for ( ) [ ] { } depth := 0 // nesting depth for ( ) [ ] { }
var rest []byte // data after the Python block var rest []byte // data after the Python block
defer func() {
if quote != 0 {
in.Error("EOF scanning Python quoted string")
}
}()
// Scan over input one byte at a time until we find // Scan over input one byte at a time until we find
// an unindented, non-blank, non-comment line // an unindented, non-blank, non-comment line
// outside quoted strings and brackets. // outside quoted strings and brackets.
@ -559,20 +636,23 @@ func (in *input) skipPython(p []byte) []byte {
continue continue
} }
if depth == 0 && i > 0 && p[i-1] == '\n' && (i < 2 || p[i-2] != '\\') { if depth == 0 && i > 0 && p[i] == '\n' && p[i-1] != '\\' {
// Possible stopping point. Save the earliest one we find. // Possible stopping point. Save the earliest one we find.
if rest == nil { if rest == nil {
rest = p[i:] rest = p[i:]
} }
if !isBlankOrComment(p[i:]) { if tables.FormatBzlFiles {
if !hasPythonContinuation(p[i:]) && c != ' ' && c != '\t' { // In the bzl files mode we only care about the end of the statement, we've found it.
// Yes, stop here. return rest
break
}
// Not a stopping point after all.
rest = nil
} }
// In the legacy mode we need to find where the current block ends
if isOutsideBlock(p[i+1:]) {
return rest
}
// Not a stopping point after all.
rest = nil
} }
switch c { switch c {
@ -581,6 +661,8 @@ func (in *input) skipPython(p []byte) []byte {
for i < len(p) && p[i] != '\n' { for i < len(p) && p[i] != '\n' {
i++ i++
} }
// Rewind 1 position back because \n should be handled at the next iteration
i--
case '(', '[', '{': case '(', '[', '{':
depth++ depth++
@ -589,9 +671,6 @@ func (in *input) skipPython(p []byte) []byte {
depth-- depth--
} }
} }
if quote != 0 {
in.Error("EOF scanning Python quoted string")
}
return rest return rest
} }
@ -691,8 +770,9 @@ func (in *input) order(v Expr) {
in.order(&v.End) in.order(&v.End)
case *SliceExpr: case *SliceExpr:
in.order(v.X) in.order(v.X)
in.order(v.Y) in.order(v.From)
in.order(v.Z) in.order(v.To)
in.order(v.Step)
case *IndexExpr: case *IndexExpr:
in.order(v.X) in.order(v.X)
in.order(v.Y) in.order(v.Y)
@ -701,6 +781,32 @@ func (in *input) order(v Expr) {
in.order(name) in.order(name)
} }
in.order(v.Expr) in.order(v.Expr)
case *ReturnExpr:
if v.X != nil {
in.order(v.X)
}
case *FuncDef:
for _, x := range v.Args {
in.order(x)
}
for _, x := range v.Body.Statements {
in.order(x)
}
case *ForLoop:
for _, x := range v.LoopVars {
in.order(x)
}
in.order(v.Iterable)
for _, x := range v.Body.Statements {
in.order(x)
}
case *IfElse:
for _, condition := range v.Conditions {
in.order(condition.If)
for _, x := range condition.Then.Statements {
in.order(x)
}
}
} }
if v != nil { if v != nil {
in.post = append(in.post, v) in.post = append(in.post, v)
@ -712,17 +818,8 @@ func (in *input) assignComments() {
// Generate preorder and postorder lists. // Generate preorder and postorder lists.
in.order(in.file) in.order(in.file)
// Split into whole-line comments and suffix comments.
var line, suffix []Comment
for _, com := range in.comments {
if com.Suffix {
suffix = append(suffix, com)
} else {
line = append(line, com)
}
}
// Assign line comments to syntax immediately following. // Assign line comments to syntax immediately following.
line := in.lineComments
for _, x := range in.pre { for _, x := range in.pre {
start, _ := x.Span() start, _ := x.Span()
xcom := x.Comment() xcom := x.Comment()
@ -736,6 +833,7 @@ func (in *input) assignComments() {
in.file.After = append(in.file.After, line...) in.file.After = append(in.file.After, line...)
// Assign suffix comments to syntax immediately before. // Assign suffix comments to syntax immediately before.
suffix := in.suffixComments
for i := len(in.post) - 1; i >= 0; i-- { for i := len(in.post) - 1; i >= 0; i-- {
x := in.post[i] x := in.post[i]

View File

@ -31,6 +31,7 @@ package build
forsifs []*ForClauseWithIfClausesOpt forsifs []*ForClauseWithIfClausesOpt
string *StringExpr string *StringExpr
strings []*StringExpr strings []*StringExpr
block CodeBlock
// supporting information // supporting information
comma Position // position of trailing comma in list, if present comma Position // position of trailing comma in list, if present
@ -69,7 +70,7 @@ package build
// However, we do not want to export them from the Go package // However, we do not want to export them from the Go package
// we are creating, so prefix them all with underscores. // we are creating, so prefix them all with underscores.
%token <pos> _ADDEQ // operator += %token <pos> _AUGM // augmented assignment
%token <pos> _AND // keyword and %token <pos> _AND // keyword and
%token <pos> _COMMENT // top-level # comment %token <pos> _COMMENT // top-level # comment
%token <pos> _EOF // end of file %token <pos> _EOF // end of file
@ -79,34 +80,47 @@ package build
%token <pos> _IDENT // non-keyword identifier or number %token <pos> _IDENT // non-keyword identifier or number
%token <pos> _IF // keyword if %token <pos> _IF // keyword if
%token <pos> _ELSE // keyword else %token <pos> _ELSE // keyword else
%token <pos> _ELIF // keyword elif
%token <pos> _IN // keyword in %token <pos> _IN // keyword in
%token <pos> _IS // keyword is %token <pos> _IS // keyword is
%token <pos> _LAMBDA // keyword lambda %token <pos> _LAMBDA // keyword lambda
%token <pos> _LOAD // keyword load
%token <pos> _LE // operator <= %token <pos> _LE // operator <=
%token <pos> _NE // operator != %token <pos> _NE // operator !=
%token <pos> _NOT // keyword not %token <pos> _NOT // keyword not
%token <pos> _OR // keyword or %token <pos> _OR // keyword or
%token <pos> _PYTHON // uninterpreted Python block %token <pos> _PYTHON // uninterpreted Python block
%token <pos> _STRING // quoted string %token <pos> _STRING // quoted string
%token <pos> _DEF // keyword def
%token <pos> _RETURN // keyword return
%token <pos> _INDENT // indentation
%token <pos> _UNINDENT // unindentation
%type <pos> comma_opt %type <pos> comma_opt
%type <expr> expr %type <expr> expr
%type <expr> expr_opt %type <expr> expr_opt
%type <expr> primary_expr
%type <exprs> exprs %type <exprs> exprs
%type <exprs> exprs_opt %type <exprs> exprs_opt
%type <exprs> primary_exprs
%type <forc> for_clause %type <forc> for_clause
%type <forifs> for_clause_with_if_clauses_opt %type <forifs> for_clause_with_if_clauses_opt
%type <forsifs> for_clauses_with_if_clauses_opt %type <forsifs> for_clauses_with_if_clauses_opt
%type <expr> ident %type <expr> ident
%type <exprs> idents
%type <ifs> if_clauses_opt %type <ifs> if_clauses_opt
%type <exprs> stmts %type <exprs> stmts
%type <expr> stmt %type <exprs> stmt // a simple_stmt or a for/if/def block
%type <expr> block_stmt // a single for/if/def statement
%type <expr> if_else_block // a single if-else statement
%type <exprs> simple_stmt // One or many small_stmts on one line, e.g. 'a = f(x); return str(a)'
%type <expr> small_stmt // A single statement, e.g. 'a = f(x)'
%type <exprs> small_stmts_continuation // A sequence of `';' small_stmt`
%type <expr> keyvalue %type <expr> keyvalue
%type <exprs> keyvalues %type <exprs> keyvalues
%type <exprs> keyvalues_no_comma %type <exprs> keyvalues_no_comma
%type <string> string %type <string> string
%type <strings> strings %type <strings> strings
%type <block> suite
// Operator precedence. // Operator precedence.
// Operators listed lower in the table bind tighter. // Operators listed lower in the table bind tighter.
@ -120,25 +134,25 @@ package build
%left '\n' %left '\n'
%left _ASSERT %left _ASSERT
// '=' and '+=' have the lowest precedence // '=' and augmented assignments have the lowest precedence
// e.g. "x = a if c > 0 else 'bar'" // e.g. "x = a if c > 0 else 'bar'"
// followed by // followed by
// 'if' and 'else' which have lower precedence than all other operators. // 'if' and 'else' which have lower precedence than all other operators.
// e.g. "a, b if c > 0 else 'foo'" is either a tuple of (a,b) or 'foo' // e.g. "a, b if c > 0 else 'foo'" is either a tuple of (a,b) or 'foo'
// and not a tuple of "(a, (b if ... ))" // and not a tuple of "(a, (b if ... ))"
%left '=' _ADDEQ %left '=' _AUGM
%left _IF _ELSE %left _IF _ELSE _ELIF
%left ',' %left ','
%left ':' %left ':'
%left _IN _NOT _IS %left _IN _NOT _IS
%left _OR %left _OR
%left _AND %left _AND
%left '<' '>' _EQ _NE _LE _GE %left '<' '>' _EQ _NE _LE _GE
%left '+' '-' %left '+' '-'
%left '*' '/' '%' %left '*' '/' '%'
%left '.' '[' '(' %left '.' '[' '('
%right _UNARY %right _UNARY
%left _STRING %left _STRING
%% %%
@ -155,26 +169,46 @@ file:
return 0 return 0
} }
suite:
'\n' _INDENT stmts _UNINDENT
{
$$ = CodeBlock{
Start: $2,
Statements: $3,
End: End{Pos: $4},
}
}
| simple_stmt
{
// simple_stmt is never empty
start, _ := $1[0].Span()
_, end := $1[len($1)-1].Span()
$$ = CodeBlock{
Start: start,
Statements: $1,
End: End{Pos: end},
}
}
stmts: stmts:
{ {
$$ = nil $$ = nil
$<lastRule>$ = nil $<lastRule>$ = nil
} }
| stmts stmt comma_opt semi_opt | stmts stmt
{ {
// If this statement follows a comment block, // If this statement follows a comment block,
// attach the comments to the statement. // attach the comments to the statement.
if cb, ok := $<lastRule>1.(*CommentBlock); ok { if cb, ok := $<lastRule>1.(*CommentBlock); ok {
$$ = $1 $$ = append($1[:len($1)-1], $2...)
$$[len($1)-1] = $2 $2[0].Comment().Before = cb.After
$2.Comment().Before = cb.After $<lastRule>$ = $2[len($2)-1]
$<lastRule>$ = $2
break break
} }
// Otherwise add to list. // Otherwise add to list.
$$ = append($1, $2) $$ = append($1, $2...)
$<lastRule>$ = $2 $<lastRule>$ = $2[len($2)-1]
// Consider this input: // Consider this input:
// //
@ -187,7 +221,8 @@ stmts:
// for baz() instead. // for baz() instead.
if x := $<lastRule>1; x != nil { if x := $<lastRule>1; x != nil {
com := x.Comment() com := x.Comment()
$2.Comment().Before = com.After // stmt is never empty
$2[0].Comment().Before = com.After
com.After = nil com.After = nil
} }
} }
@ -197,7 +232,7 @@ stmts:
$$ = $1 $$ = $1
$<lastRule>$ = nil $<lastRule>$ = nil
} }
| stmts _COMMENT | stmts _COMMENT '\n'
{ {
$$ = $1 $$ = $1
$<lastRule>$ = $<lastRule>1 $<lastRule>$ = $<lastRule>1
@ -211,24 +246,209 @@ stmts:
} }
stmt: stmt:
simple_stmt
{
$$ = $1
}
| block_stmt
{
$$ = []Expr{$1}
}
block_stmt:
_DEF _IDENT '(' exprs_opt ')' ':' suite
{
$$ = &FuncDef{
Start: $1,
Name: $<tok>2,
ListStart: $3,
Args: $4,
Body: $7,
End: $7.End,
ForceCompact: forceCompact($3, $4, $5),
ForceMultiLine: forceMultiLine($3, $4, $5),
}
}
| _FOR primary_exprs _IN expr ':' suite
{
$$ = &ForLoop{
Start: $1,
LoopVars: $2,
Iterable: $4,
Body: $6,
End: $6.End,
}
}
| if_else_block
{
$$ = $1
}
if_else_block:
_IF expr ':' suite
{
$$ = &IfElse{
Start: $1,
Conditions: []Condition{
Condition{
If: $2,
Then: $4,
},
},
End: $4.End,
}
}
| if_else_block elif expr ':' suite
{
block := $1.(*IfElse)
block.Conditions = append(block.Conditions, Condition{
If: $3,
Then: $5,
})
block.End = $5.End
$$ = block
}
| if_else_block _ELSE ':' suite
{
block := $1.(*IfElse)
block.Conditions = append(block.Conditions, Condition{
Then: $4,
})
block.End = $4.End
$$ = block
}
elif:
_ELSE _IF
| _ELIF
simple_stmt:
small_stmt small_stmts_continuation semi_opt '\n'
{
$$ = append([]Expr{$1}, $2...)
$<lastRule>$ = $$[len($$)-1]
}
small_stmts_continuation:
{
$$ = []Expr{}
}
| small_stmts_continuation ';' small_stmt
{
$$ = append($1, $3)
}
small_stmt:
expr %prec ShiftInstead expr %prec ShiftInstead
| _RETURN expr
{
_, end := $2.Span()
$$ = &ReturnExpr{
X: $2,
End: end,
}
}
| _RETURN
{
$$ = &ReturnExpr{End: $1}
}
| _PYTHON | _PYTHON
{ {
$$ = &PythonBlock{Start: $1, Token: $<tok>1} $$ = &PythonBlock{Start: $1, Token: $<tok>1}
} }
semi_opt: semi_opt:
| semi_opt ';' | ';'
expr: primary_expr:
ident ident
| primary_expr '.' _IDENT
{
$$ = &DotExpr{
X: $1,
Dot: $2,
NamePos: $3,
Name: $<tok>3,
}
}
| _LOAD '(' exprs_opt ')'
{
$$ = &CallExpr{
X: &LiteralExpr{Start: $1, Token: "load"},
ListStart: $2,
List: $3,
End: End{Pos: $4},
ForceCompact: forceCompact($2, $3, $4),
ForceMultiLine: forceMultiLine($2, $3, $4),
}
}
| primary_expr '(' exprs_opt ')'
{
$$ = &CallExpr{
X: $1,
ListStart: $2,
List: $3,
End: End{Pos: $4},
ForceCompact: forceCompact($2, $3, $4),
ForceMultiLine: forceMultiLine($2, $3, $4),
}
}
| primary_expr '[' expr ']'
{
$$ = &IndexExpr{
X: $1,
IndexStart: $2,
Y: $3,
End: $4,
}
}
| primary_expr '[' expr_opt ':' expr_opt ']'
{
$$ = &SliceExpr{
X: $1,
SliceStart: $2,
From: $3,
FirstColon: $4,
To: $5,
End: $6,
}
}
| primary_expr '[' expr_opt ':' expr_opt ':' expr_opt ']'
{
$$ = &SliceExpr{
X: $1,
SliceStart: $2,
From: $3,
FirstColon: $4,
To: $5,
SecondColon: $6,
Step: $7,
End: $8,
}
}
| primary_expr '(' expr for_clauses_with_if_clauses_opt ')'
{
$$ = &CallExpr{
X: $1,
ListStart: $2,
List: []Expr{
&ListForExpr{
Brack: "",
Start: $2,
X: $3,
For: $4,
End: End{Pos: $5},
},
},
End: End{Pos: $5},
}
}
| strings %prec ShiftInstead | strings %prec ShiftInstead
{ {
if len($1) == 1 { if len($1) == 1 {
$$ = $1[0] $$ = $1[0]
break break
} }
$$ = $1[0] $$ = $1[0]
for _, x := range $1[1:] { for _, x := range $1[1:] {
_, end := $$.Span() _, end := $$.Span()
@ -322,63 +542,10 @@ expr:
} }
} }
} }
| expr '.' _IDENT | '-' primary_expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) }
{
$$ = &DotExpr{ expr:
X: $1, primary_expr
Dot: $2,
NamePos: $3,
Name: $<tok>3,
}
}
| expr '(' exprs_opt ')'
{
$$ = &CallExpr{
X: $1,
ListStart: $2,
List: $3,
End: End{Pos: $4},
ForceCompact: forceCompact($2, $3, $4),
ForceMultiLine: forceMultiLine($2, $3, $4),
}
}
| expr '(' expr for_clauses_with_if_clauses_opt ')'
{
$$ = &CallExpr{
X: $1,
ListStart: $2,
List: []Expr{
&ListForExpr{
Brack: "",
Start: $2,
X: $3,
For: $4,
End: End{Pos: $5},
},
},
End: End{Pos: $5},
}
}
| expr '[' expr ']'
{
$$ = &IndexExpr{
X: $1,
IndexStart: $2,
Y: $3,
End: $4,
}
}
| expr '[' expr_opt ':' expr_opt ']'
{
$$ = &SliceExpr{
X: $1,
SliceStart: $2,
Y: $3,
Colon: $4,
Z: $5,
End: $6,
}
}
| _LAMBDA exprs ':' expr | _LAMBDA exprs ':' expr
{ {
$$ = &LambdaExpr{ $$ = &LambdaExpr{
@ -388,7 +555,6 @@ expr:
Expr: $4, Expr: $4,
} }
} }
| '-' expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) }
| _NOT expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) } | _NOT expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) }
| '*' expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) } | '*' expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) }
| expr '*' expr { $$ = binary($1, $2, $<tok>2, $3) } | expr '*' expr { $$ = binary($1, $2, $<tok>2, $3) }
@ -403,7 +569,7 @@ expr:
| expr _NE expr { $$ = binary($1, $2, $<tok>2, $3) } | expr _NE expr { $$ = binary($1, $2, $<tok>2, $3) }
| expr _GE expr { $$ = binary($1, $2, $<tok>2, $3) } | expr _GE expr { $$ = binary($1, $2, $<tok>2, $3) }
| expr '=' expr { $$ = binary($1, $2, $<tok>2, $3) } | expr '=' expr { $$ = binary($1, $2, $<tok>2, $3) }
| expr _ADDEQ expr { $$ = binary($1, $2, $<tok>2, $3) } | expr _AUGM expr { $$ = binary($1, $2, $<tok>2, $3) }
| expr _IN expr { $$ = binary($1, $2, $<tok>2, $3) } | expr _IN expr { $$ = binary($1, $2, $<tok>2, $3) }
| expr _NOT _IN expr { $$ = binary($1, $2, "not in", $4) } | expr _NOT _IN expr { $$ = binary($1, $2, "not in", $4) }
| expr _OR expr { $$ = binary($1, $2, $<tok>2, $3) } | expr _OR expr { $$ = binary($1, $2, $<tok>2, $3) }
@ -416,15 +582,15 @@ expr:
$$ = binary($1, $2, $<tok>2, $3) $$ = binary($1, $2, $<tok>2, $3)
} }
} }
| expr _IF expr _ELSE expr | expr _IF expr _ELSE expr
{ {
$$ = &ConditionalExpr{ $$ = &ConditionalExpr{
Then: $1, Then: $1,
IfStart: $2, IfStart: $2,
Test: $3, Test: $3,
ElseStart: $4, ElseStart: $4,
Else: $5, Else: $5,
} }
} }
expr_opt: expr_opt:
@ -491,6 +657,16 @@ exprs_opt:
$$, $<comma>$ = $1, $2 $$, $<comma>$ = $1, $2
} }
primary_exprs:
primary_expr
{
$$ = []Expr{$1}
}
| primary_exprs ',' primary_expr
{
$$ = append($1, $3)
}
string: string:
_STRING _STRING
{ {
@ -519,18 +695,8 @@ ident:
$$ = &LiteralExpr{Start: $1, Token: $<tok>1} $$ = &LiteralExpr{Start: $1, Token: $<tok>1}
} }
idents:
ident
{
$$ = []Expr{$1}
}
| idents ',' ident
{
$$ = append($1, $3)
}
for_clause: for_clause:
_FOR idents _IN expr _FOR primary_exprs _IN expr
{ {
$$ = &ForClause{ $$ = &ForClause{
For: $1, For: $1,
@ -539,15 +705,6 @@ for_clause:
Expr: $4, Expr: $4,
} }
} }
| _FOR '(' idents ')' _IN expr
{
$$ = &ForClause{
For: $1,
Var: $3,
In: $5,
Expr: $6,
}
}
for_clause_with_if_clauses_opt: for_clause_with_if_clauses_opt:
for_clause if_clauses_opt { for_clause if_clauses_opt {
@ -558,13 +715,13 @@ for_clause_with_if_clauses_opt:
} }
for_clauses_with_if_clauses_opt: for_clauses_with_if_clauses_opt:
for_clause_with_if_clauses_opt for_clause_with_if_clauses_opt
{ {
$$ = []*ForClauseWithIfClausesOpt{$1} $$ = []*ForClauseWithIfClausesOpt{$1}
} }
| for_clauses_with_if_clauses_opt for_clause_with_if_clauses_opt { | for_clauses_with_if_clauses_opt for_clause_with_if_clauses_opt {
$$ = append($1, $2) $$ = append($1, $2)
} }
if_clauses_opt: if_clauses_opt:
{ {
@ -606,6 +763,30 @@ func binary(x Expr, pos Position, op string, y Expr) Expr {
} }
} }
// isSimpleExpression returns whether an expression is simple and allowed to exist in
// compact forms of sequences.
// The formal criteria are the following: an expression is considered simple if it's
// a literal (variable, string or a number), a literal with a unary operator or an empty sequence.
func isSimpleExpression(expr *Expr) bool {
switch x := (*expr).(type) {
case *LiteralExpr, *StringExpr:
return true
case *UnaryExpr:
_, ok := x.X.(*LiteralExpr)
return ok
case *ListExpr:
return len(x.List) == 0
case *TupleExpr:
return len(x.List) == 0
case *DictExpr:
return len(x.List) == 0
case *SetExpr:
return len(x.List) == 0
default:
return false
}
}
// forceCompact returns the setting for the ForceCompact field for a call or tuple. // forceCompact returns the setting for the ForceCompact field for a call or tuple.
// //
// NOTE 1: The field is called ForceCompact, not ForceSingleLine, // NOTE 1: The field is called ForceCompact, not ForceSingleLine,
@ -654,10 +835,7 @@ func forceCompact(start Position, list []Expr, end Position) bool {
return false return false
} }
line = end.Line line = end.Line
switch x.(type) { if !isSimpleExpression(&x) {
case *LiteralExpr, *StringExpr, *UnaryExpr:
// ok
default:
return false return false
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,9 @@ import (
"strings" "strings"
) )
const nestedIndentation = 2 // Indentation of nested blocks
const listIndentation = 4 // Indentation of multiline expressions
// Format returns the formatted form of the given BUILD file. // Format returns the formatted form of the given BUILD file.
func Format(f *File) []byte { func Format(f *File) []byte {
pr := &printer{} pr := &printer{}
@ -118,7 +121,19 @@ func (p *printer) file(f *File) {
p.newline() p.newline()
} }
for i, stmt := range f.Stmt { p.statements(f.Stmt)
for _, com := range f.After {
p.printf("%s", strings.TrimSpace(com.Token))
p.newline()
}
// If the last expression is in an indented code block there can be spaces in the last line.
p.trim()
}
func (p *printer) statements(stmts []Expr) {
for i, stmt := range stmts {
switch stmt := stmt.(type) { switch stmt := stmt.(type) {
case *CommentBlock: case *CommentBlock:
// comments already handled // comments already handled
@ -128,11 +143,17 @@ func (p *printer) file(f *File) {
p.printf("%s", strings.TrimSpace(com.Token)) p.printf("%s", strings.TrimSpace(com.Token))
p.newline() p.newline()
} }
p.printf("%s", stmt.Token) // includes trailing newline p.printf("%s", stmt.Token)
p.newline()
default: default:
p.expr(stmt, precLow) p.expr(stmt, precLow)
p.newline()
// Print an empty line break after the expression unless it's a code block.
// For a code block, the line break is generated by its last statement.
if !isCodeBlock(stmt) {
p.newline()
}
} }
for _, com := range stmt.Comment().After { for _, com := range stmt.Comment().After {
@ -140,28 +161,26 @@ func (p *printer) file(f *File) {
p.newline() p.newline()
} }
if i+1 < len(f.Stmt) && !compactStmt(stmt, f.Stmt[i+1]) { if i+1 < len(stmts) && !compactStmt(stmt, stmts[i+1], p.margin == 0) {
p.newline() p.newline()
} }
} }
for _, com := range f.After {
p.printf("%s", strings.TrimSpace(com.Token))
p.newline()
}
} }
// compactStmt reports whether the pair of statements s1, s2 // compactStmt reports whether the pair of statements s1, s2
// should be printed without an intervening blank line. // should be printed without an intervening blank line.
// We omit the blank line when both are subinclude statements // We omit the blank line when both are subinclude statements
// and the second one has no leading comments. // and the second one has no leading comments.
func compactStmt(s1, s2 Expr) bool { func compactStmt(s1, s2 Expr, isTopLevel bool) bool {
if len(s2.Comment().Before) > 0 { if len(s2.Comment().Before) > 0 {
return false return false
} }
return (isCall(s1, "subinclude") || isCall(s1, "load")) && if isTopLevel {
(isCall(s2, "subinclude") || isCall(s2, "load")) return isCall(s1, "load") && isCall(s2, "load")
} else {
return !(isCodeBlock(s1) || isCodeBlock(s2))
}
} }
// isCall reports whether x is a call to a function with the given name. // isCall reports whether x is a call to a function with the given name.
@ -177,6 +196,20 @@ func isCall(x Expr, name string) bool {
return nam.Token == name return nam.Token == name
} }
// isCodeBlock checks if the statement is a code block (def, if, for, etc.)
func isCodeBlock(x Expr) bool {
switch x.(type) {
case *FuncDef:
return true
case *ForLoop:
return true
case *IfElse:
return true
default:
return false
}
}
// Expression formatting. // Expression formatting.
// The expression formatter must introduce parentheses to force the // The expression formatter must introduce parentheses to force the
@ -220,6 +253,11 @@ const (
var opPrec = map[string]int{ var opPrec = map[string]int{
"=": precAssign, "=": precAssign,
"+=": precAssign, "+=": precAssign,
"-=": precAssign,
"*=": precAssign,
"/=": precAssign,
"//=": precAssign,
"%=": precAssign,
"or": precOr, "or": precOr,
"and": precAnd, "and": precAnd,
"<": precCmp, "<": precCmp,
@ -232,6 +270,7 @@ var opPrec = map[string]int{
"-": precAdd, "-": precAdd,
"*": precMultiply, "*": precMultiply,
"/": precMultiply, "/": precMultiply,
"//": precMultiply,
"%": precMultiply, "%": precMultiply,
} }
@ -327,12 +366,18 @@ func (p *printer) expr(v Expr, outerPrec int) {
addParen(precSuffix) addParen(precSuffix)
p.expr(v.X, precSuffix) p.expr(v.X, precSuffix)
p.printf("[") p.printf("[")
if v.Y != nil { if v.From != nil {
p.expr(v.Y, precLow) p.expr(v.From, precLow)
} }
p.printf(":") p.printf(":")
if v.Z != nil { if v.To != nil {
p.expr(v.Z, precLow) p.expr(v.To, precLow)
}
if v.SecondColon.Byte != 0 {
p.printf(":")
if v.Step != nil {
p.expr(v.Step, precLow)
}
} }
p.printf("]") p.printf("]")
@ -379,7 +424,7 @@ func (p *printer) expr(v Expr, outerPrec int) {
if v.LineBreak { if v.LineBreak {
p.margin = p.indent() p.margin = p.indent()
if v.Op == "=" { if v.Op == "=" {
p.margin += 4 p.margin += listIndentation
} }
} }
@ -427,6 +472,61 @@ func (p *printer) expr(v Expr, outerPrec int) {
p.expr(v.Test, precSuffix) p.expr(v.Test, precSuffix)
p.printf(" else ") p.printf(" else ")
p.expr(v.Else, precSuffix) p.expr(v.Else, precSuffix)
case *ReturnExpr:
p.printf("return")
if v.X != nil {
p.printf(" ")
p.expr(v.X, precSuffix)
}
case *FuncDef:
p.printf("def ")
p.printf(v.Name)
p.seq("()", v.Args, &v.End, modeCall, v.ForceCompact, v.ForceMultiLine)
p.printf(":")
p.margin += nestedIndentation
p.newline()
p.statements(v.Body.Statements)
p.margin -= nestedIndentation
case *ForLoop:
p.printf("for ")
for i, loopVar := range v.LoopVars {
if i > 0 {
p.printf(", ")
}
p.expr(loopVar, precLow)
}
p.printf(" in ")
p.expr(v.Iterable, precLow)
p.printf(":")
p.margin += nestedIndentation
p.newline()
p.statements(v.Body.Statements)
p.margin -= nestedIndentation
case *IfElse:
for i, block := range v.Conditions {
if i == 0 {
p.printf("if ")
} else if block.If == nil {
p.newline()
p.printf("else")
} else {
p.newline()
p.printf("elif ")
}
if block.If != nil {
p.expr(block.If, precLow)
}
p.printf(":")
p.margin += nestedIndentation
p.newline()
p.statements(block.Then.Statements)
p.margin -= nestedIndentation
}
} }
// Add closing parenthesis if needed. // Add closing parenthesis if needed.
@ -452,6 +552,7 @@ const (
modeTuple // (x,) modeTuple // (x,)
modeParen // (x) modeParen // (x)
modeDict // {x:y} modeDict // {x:y}
modeSeq // x, y
) )
// seq formats a list of values inside a given bracket pair (brack = "()", "[]", "{}"). // seq formats a list of values inside a given bracket pair (brack = "()", "[]", "{}").
@ -504,7 +605,7 @@ func (p *printer) seq(brack string, list []Expr, end *End, mode seqMode, forceCo
default: default:
// Multi-line form. // Multi-line form.
p.margin += 4 p.margin += listIndentation
for i, x := range list { for i, x := range list {
// If we are about to break the line before the first // If we are about to break the line before the first
// element and there are trailing end-of-line comments // element and there are trailing end-of-line comments
@ -528,7 +629,7 @@ func (p *printer) seq(brack string, list []Expr, end *End, mode seqMode, forceCo
p.newline() p.newline()
p.printf("%s", strings.TrimSpace(com.Token)) p.printf("%s", strings.TrimSpace(com.Token))
} }
p.margin -= 4 p.margin -= listIndentation
p.newline() p.newline()
} }
p.depth-- p.depth--
@ -566,7 +667,7 @@ func (p *printer) listFor(v *ListForExpr) {
if multiLine { if multiLine {
if v.Brack != "" { if v.Brack != "" {
p.margin += 4 p.margin += listIndentation
} }
p.newline() p.newline()
} }
@ -602,7 +703,7 @@ func (p *printer) listFor(v *ListForExpr) {
p.printf("%s", strings.TrimSpace(com.Token)) p.printf("%s", strings.TrimSpace(com.Token))
} }
if v.Brack != "" { if v.Brack != "" {
p.margin -= 4 p.margin -= listIndentation
} }
p.newline() p.newline()
} }
@ -612,3 +713,7 @@ func (p *printer) listFor(v *ListForExpr) {
p.depth-- p.depth--
} }
} }
func (p *printer) isTopLevel() bool {
return p.margin == 0
}

View File

@ -18,33 +18,48 @@ distributed under the License is distributed on an "AS IS" BASIS,
package build package build
import "strings" import (
"strings"
"path/filepath"
)
// A Rule represents a single BUILD rule. // A Rule represents a single BUILD rule.
type Rule struct { type Rule struct {
Call *CallExpr Call *CallExpr
ImplicitName string // The name which should be used if the name attribute is not set. See the comment on File.implicitRuleName.
}
func (f *File) Rule(call *CallExpr) *Rule {
r := &Rule{call, ""}
if r.AttrString("name") == "" {
r.ImplicitName = f.implicitRuleName()
}
return r
} }
// Rules returns the rules in the file of the given kind (such as "go_library"). // Rules returns the rules in the file of the given kind (such as "go_library").
// If kind == "", Rules returns all rules in the file. // If kind == "", Rules returns all rules in the file.
func (f *File) Rules(kind string) []*Rule { func (f *File) Rules(kind string) []*Rule {
var all []*Rule var all []*Rule
for _, stmt := range f.Stmt { for _, stmt := range f.Stmt {
call, ok := stmt.(*CallExpr) call, ok := stmt.(*CallExpr)
if !ok { if !ok {
continue continue
} }
rule := &Rule{call} rule := f.Rule(call)
if kind != "" && rule.Kind() != kind { if kind != "" && rule.Kind() != kind {
continue continue
} }
all = append(all, rule) all = append(all, rule)
} }
return all return all
} }
// RuleAt returns the rule in the file that starts at the specified line, or null if no such rule. // RuleAt returns the rule in the file that starts at the specified line, or null if no such rule.
func (f *File) RuleAt(linenum int) *Rule { func (f *File) RuleAt(linenum int) *Rule {
for _, stmt := range f.Stmt { for _, stmt := range f.Stmt {
call, ok := stmt.(*CallExpr) call, ok := stmt.(*CallExpr)
if !ok { if !ok {
@ -52,7 +67,7 @@ func (f *File) RuleAt(linenum int) *Rule {
} }
start, end := call.X.Span() start, end := call.X.Span()
if start.Line <= linenum && linenum <= end.Line { if start.Line <= linenum && linenum <= end.Line {
return &Rule{call} return f.Rule(call)
} }
} }
return nil return nil
@ -65,9 +80,9 @@ func (f *File) DelRules(kind, name string) int {
var i int var i int
for _, stmt := range f.Stmt { for _, stmt := range f.Stmt {
if call, ok := stmt.(*CallExpr); ok { if call, ok := stmt.(*CallExpr); ok {
r := &Rule{call} r := f.Rule(call)
if (kind == "" || r.Kind() == kind) && if (kind == "" || r.Kind() == kind) &&
(name == "" || r.AttrString("name") == name) { (name == "" || r.Name() == name) {
continue continue
} }
} }
@ -79,6 +94,42 @@ func (f *File) DelRules(kind, name string) int {
return n return n
} }
// If a build file contains exactly one unnamed rule, and no rules in the file explicitly have the
// same name as the name of the directory the build file is in, we treat the unnamed rule as if it
// had the name of the directory containing the BUILD file.
// This is following a convention used in the Pants build system to cut down on boilerplate.
func (f *File) implicitRuleName() string {
// We disallow empty names in the top-level BUILD files.
dir := filepath.Dir(f.Path)
if dir == "." {
return ""
}
sawAnonymousRule := false
possibleImplicitName := filepath.Base(dir)
for _, stmt := range f.Stmt {
call, ok := stmt.(*CallExpr)
if !ok {
continue
}
temp := &Rule{call, ""}
if temp.AttrString("name") == possibleImplicitName {
// A target explicitly has the name of the dir, so no implicit targets are allowed.
return ""
}
if temp.Kind() != "" && temp.AttrString("name") == "" {
if sawAnonymousRule {
return ""
}
sawAnonymousRule = true
}
}
if sawAnonymousRule {
return possibleImplicitName
}
return ""
}
// Kind returns the rule's kind (such as "go_library"). // Kind returns the rule's kind (such as "go_library").
// The kind of the rule may be given by a literal or it may be a sequence of dot expressions that // The kind of the rule may be given by a literal or it may be a sequence of dot expressions that
// begins with a literal, if the call expression does not conform to either of these forms, an // begins with a literal, if the call expression does not conform to either of these forms, an
@ -118,9 +169,13 @@ func (r *Rule) SetKind(kind string) {
} }
// Name returns the rule's target name. // Name returns the rule's target name.
// If the rule has no target name, Name returns the empty string. // If the rule has no explicit target name, Name returns the implicit name if there is one, else the empty string.
func (r *Rule) Name() string { func (r *Rule) Name() string {
return r.AttrString("name") explicitName := r.AttrString("name")
if explicitName == "" {
return r.ImplicitName
}
return explicitName
} }
// AttrKeys returns the keys of all the rule's attributes. // AttrKeys returns the keys of all the rule's attributes.

View File

@ -57,9 +57,8 @@ type Expr interface {
// A Comment represents a single # comment. // A Comment represents a single # comment.
type Comment struct { type Comment struct {
Start Position Start Position
Token string // without trailing newline Token string // without trailing newline
Suffix bool // an end of line (not whole line) comment
} }
// Comments collects the comments associated with an expression. // Comments collects the comments associated with an expression.
@ -87,12 +86,12 @@ type File struct {
Stmt []Expr Stmt []Expr
} }
func (x *File) Span() (start, end Position) { func (f *File) Span() (start, end Position) {
if len(x.Stmt) == 0 { if len(f.Stmt) == 0 {
return return
} }
start, _ = x.Stmt[0].Span() start, _ = f.Stmt[0].Span()
_, end = x.Stmt[len(x.Stmt)-1].Span() _, end = f.Stmt[len(f.Stmt)-1].Span()
return start, end return start, end
} }
@ -360,15 +359,17 @@ func (x *ParenExpr) Span() (start, end Position) {
return x.Start, x.End.Pos.add(")") return x.Start, x.End.Pos.add(")")
} }
// A SliceExpr represents a slice expression: X[Y:Z]. // A SliceExpr represents a slice expression: expr[from:to] or expr[from:to:step] .
type SliceExpr struct { type SliceExpr struct {
Comments Comments
X Expr X Expr
SliceStart Position SliceStart Position
Y Expr From Expr
Colon Position FirstColon Position
Z Expr To Expr
End Position SecondColon Position
Step Expr
End Position
} }
func (x *SliceExpr) Span() (start, end Position) { func (x *SliceExpr) Span() (start, end Position) {
@ -421,3 +422,74 @@ func (x *ConditionalExpr) Span() (start, end Position) {
_, end = x.Else.Span() _, end = x.Else.Span()
return start, end return start, end
} }
// A CodeBlock represents an indented code block.
type CodeBlock struct {
Statements []Expr
Start Position
End
}
func (x *CodeBlock) Span() (start, end Position) {
return x.Start, x.End.Pos
}
// A FuncDef represents a function definition expression: def foo(List):.
type FuncDef struct {
Comments
Start Position // position of def
Name string
ListStart Position // position of (
Args []Expr
Body CodeBlock
End // position of the end
ForceCompact bool // force compact (non-multiline) form when printing
ForceMultiLine bool // force multiline form when printing
}
func (x *FuncDef) Span() (start, end Position) {
return x.Start, x.End.Pos
}
// A ReturnExpr represents a return statement: return f(x).
type ReturnExpr struct {
Comments
Start Position
X Expr
End Position
}
func (x *ReturnExpr) Span() (start, end Position) {
return x.Start, x.End
}
// A ForLoop represents a for loop block: for x in range(10):.
type ForLoop struct {
Comments
Start Position // position of for
LoopVars []Expr
Iterable Expr
Body CodeBlock
End // position of the end
}
func (x *ForLoop) Span() (start, end Position) {
return x.Start, x.End.Pos
}
// An IfElse represents an if-else blocks sequence: if x: ... elif y: ... else: ... .
type IfElse struct {
Comments
Start Position // position of if
Conditions []Condition
End // position of the end
}
type Condition struct {
If Expr
Then CodeBlock
}
func (x *IfElse) Span() (start, end Position) {
return x.Start, x.End.Pos
}

View File

@ -72,11 +72,14 @@ func walk1(v *Expr, stack *[]Expr, f func(x Expr, stk []Expr) Expr) Expr {
walk1(&v.Value, stack, f) walk1(&v.Value, stack, f)
case *SliceExpr: case *SliceExpr:
walk1(&v.X, stack, f) walk1(&v.X, stack, f)
if v.Y != nil { if v.From != nil {
walk1(&v.Y, stack, f) walk1(&v.From, stack, f)
} }
if v.Z != nil { if v.To != nil {
walk1(&v.Z, stack, f) walk1(&v.To, stack, f)
}
if v.Step != nil {
walk1(&v.Step, stack, f)
} }
case *ParenExpr: case *ParenExpr:
walk1(&v.X, stack, f) walk1(&v.X, stack, f)

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-go. // Code generated by protoc-gen-go. DO NOT EDIT.
// source: build_proto/build.proto // source: build_proto/build.proto
// DO NOT EDIT!
/* /*
Package blaze_query is a generated protocol buffer package. Package blaze_query is a generated protocol buffer package.

View File

@ -92,6 +92,7 @@ Buildozer supports the following commands(`'command args'`):
* `new <rule_kind> <rule_name> [(before|after) <relative_rule_name>]`: Add a * `new <rule_kind> <rule_name> [(before|after) <relative_rule_name>]`: Add a
new rule at the end of the BUILD file (before/after `<relative_rule>`). new rule at the end of the BUILD file (before/after `<relative_rule>`).
* `print <attr(s)>` * `print <attr(s)>`
* `remove <attr>`: Removes attribute `attr`.
* `remove <attr> <value(s)>`: Removes `value(s)` from the list `attr`. The * `remove <attr> <value(s)>`: Removes `value(s)` from the list `attr`. The
wildcard `*` matches all attributes. Lists containing none of the `value(s)` are wildcard `*` matches all attributes. Lists containing none of the `value(s)` are
not modified. not modified.
@ -100,6 +101,13 @@ Buildozer supports the following commands(`'command args'`):
* `replace <attr> <old_value> <new_value>`: Replaces `old_value` with `new_value` * `replace <attr> <old_value> <new_value>`: Replaces `old_value` with `new_value`
in the list `attr`. Wildcard `*` matches all attributes. Lists not containing in the list `attr`. Wildcard `*` matches all attributes. Lists not containing
`old_value` are not modified. `old_value` are not modified.
* `substitute <attr> <old_regexp> <new_template>`: Replaces strings which
match `old_regexp` in the list `attr` according to `new_template`. Wildcard
`*` matches all attributes. The regular expression must follow
[RE2 syntax](https://github.com/google/re2/wiki/Syntax). `new_template` may
be a simple replacement string, but it may also expand numbered or named
groups using `$0` or `$x`. Lists without strings that match `old_regexp`
are not modified.
* `set <attr> <value(s)>`: Sets the value of an attribute. If the attribute * `set <attr> <value(s)>`: Sets the value of an attribute. If the attribute
was already present, its old value is replaced. was already present, its old value is replaced.
* `set_if_absent <attr> <value(s)>`: Sets the value of an attribute. If the * `set_if_absent <attr> <value(s)>`: Sets the value of an attribute. If the
@ -138,6 +146,9 @@ buildozer 'set kind java_library' //pkg:%gwt_module
# Replace the dependency on pkg_v1 with a dependency on pkg_v2 # Replace the dependency on pkg_v1 with a dependency on pkg_v2
buildozer 'replace deps //pkg_v1 //pkg_v2' //pkg:rule buildozer 'replace deps //pkg_v1 //pkg_v2' //pkg:rule
# Replace all dependencies using regular expressions.
buildozer 'substitute deps //old/(.*) //new/${1}' //pkg:rule
# Delete the dependency on foo in every cc_library in the package # Delete the dependency on foo in every cc_library in the package
buildozer 'remove deps foo' //pkg:%cc_library buildozer 'remove deps foo' //pkg:%cc_library

View File

@ -38,6 +38,7 @@ var (
editVariables = flag.Bool("edit-variables", false, "For attributes that simply assign a variable (e.g. hdrs = LIB_HDRS), edit the build variable instead of appending to the attribute.") editVariables = flag.Bool("edit-variables", false, "For attributes that simply assign a variable (e.g. hdrs = LIB_HDRS), edit the build variable instead of appending to the attribute.")
isPrintingProto = flag.Bool("output_proto", false, "output serialized devtools.buildozer.Output protos instead of human-readable strings.") isPrintingProto = flag.Bool("output_proto", false, "output serialized devtools.buildozer.Output protos instead of human-readable strings.")
tablesPath = flag.String("tables", "", "path to JSON file with custom table definitions which will replace the built-in tables") tablesPath = flag.String("tables", "", "path to JSON file with custom table definitions which will replace the built-in tables")
addTablesPath = flag.String("add_tables", "", "path to JSON file with custom table definitions which will be merged with the built-in tables")
shortenLabelsFlag = flag.Bool("shorten_labels", true, "convert added labels to short form, e.g. //foo:bar => :bar") shortenLabelsFlag = flag.Bool("shorten_labels", true, "convert added labels to short form, e.g. //foo:bar => :bar")
deleteWithComments = flag.Bool("delete_with_comments", true, "If a list attribute should be deleted even if there is a comment attached to it") deleteWithComments = flag.Bool("delete_with_comments", true, "If a list attribute should be deleted even if there is a comment attached to it")
@ -67,9 +68,16 @@ func main() {
} }
} }
if *addTablesPath != "" {
if err := tables.ParseAndUpdateJSONDefinitions(*addTablesPath, true); err != nil {
fmt.Fprintf(os.Stderr, "buildifier: failed to parse %s for -add_tables: %s\n", *addTablesPath, err)
os.Exit(2)
}
}
edit.ShortenLabelsFlag = *shortenLabelsFlag edit.ShortenLabelsFlag = *shortenLabelsFlag
edit.DeleteWithComments = *deleteWithComments edit.DeleteWithComments = *deleteWithComments
edit.Opts = edit.Options{ opts := &edit.Options{
Stdout: *stdout, Stdout: *stdout,
Buildifier: *buildifier, Buildifier: *buildifier,
Parallelism: *parallelism, Parallelism: *parallelism,
@ -83,5 +91,5 @@ func main() {
EditVariables: *editVariables, EditVariables: *editVariables,
IsPrintingProto: *isPrintingProto, IsPrintingProto: *isPrintingProto,
} }
os.Exit(edit.Buildozer(flag.Args())) os.Exit(edit.Buildozer(opts, flag.Args()))
} }

View File

@ -17,6 +17,7 @@ go_library(
"//vendor/github.com/bazelbuild/buildtools/build_proto:go_default_library", "//vendor/github.com/bazelbuild/buildtools/build_proto:go_default_library",
"//vendor/github.com/bazelbuild/buildtools/file:go_default_library", "//vendor/github.com/bazelbuild/buildtools/file:go_default_library",
"//vendor/github.com/bazelbuild/buildtools/lang:go_default_library", "//vendor/github.com/bazelbuild/buildtools/lang:go_default_library",
"//vendor/github.com/bazelbuild/buildtools/tables:go_default_library",
"//vendor/github.com/bazelbuild/buildtools/wspace:go_default_library", "//vendor/github.com/bazelbuild/buildtools/wspace:go_default_library",
"//vendor/github.com/golang/protobuf/proto:go_default_library", "//vendor/github.com/golang/protobuf/proto:go_default_library",
], ],

View File

@ -53,8 +53,10 @@ type Options struct {
IsPrintingProto bool // output serialized devtools.buildozer.Output protos instead of human-readable strings IsPrintingProto bool // output serialized devtools.buildozer.Output protos instead of human-readable strings
} }
// Opts represents the options to be used by buildozer, and can be overriden before calling Buildozer. // NewOpts returns a new Options struct with some defaults set.
var Opts = Options{NumIO: 200, PreferEOLComments: true} func NewOpts() *Options {
return &Options{NumIO: 200, PreferEOLComments: true}
}
// Usage is a user-overriden func to print the program usage. // Usage is a user-overriden func to print the program usage.
var Usage = func() {} var Usage = func() {}
@ -75,7 +77,7 @@ type CmdEnvironment struct {
// The cmdXXX functions implement the various commands. // The cmdXXX functions implement the various commands.
func cmdAdd(env CmdEnvironment) (*build.File, error) { func cmdAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
attr := env.Args[0] attr := env.Args[0]
for _, val := range env.Args[1:] { for _, val := range env.Args[1:] {
if IsIntList(attr) { if IsIntList(attr) {
@ -88,14 +90,14 @@ func cmdAdd(env CmdEnvironment) (*build.File, error) {
return env.File, nil return env.File, nil
} }
func cmdComment(env CmdEnvironment) (*build.File, error) { func cmdComment(opts *Options, env CmdEnvironment) (*build.File, error) {
// The comment string is always the last argument in the list. // The comment string is always the last argument in the list.
str := env.Args[len(env.Args)-1] str := env.Args[len(env.Args)-1]
str = strings.Replace(str, "\\n", "\n", -1) str = strings.Replace(str, "\\n", "\n", -1)
// Multiline comments should go on a separate line. // Multiline comments should go on a separate line.
fullLine := !Opts.PreferEOLComments || strings.Contains(str, "\n") fullLine := !opts.PreferEOLComments || strings.Contains(str, "\n")
str = strings.Replace("# "+str, "\n", "\n# ", -1) str = strings.Replace("# "+str, "\n", "\n# ", -1)
comment := []build.Comment{build.Comment{Token: str}} comment := []build.Comment{{Token: str}}
// The comment might be attached to a rule, an attribute, or a value in a list, // The comment might be attached to a rule, an attribute, or a value in a list,
// depending on how many arguments are passed. // depending on how many arguments are passed.
@ -139,7 +141,7 @@ func commentsText(comments []build.Comment) string {
return strings.Replace(strings.Join(segments, " "), "\n", " ", -1) return strings.Replace(strings.Join(segments, " "), "\n", " ", -1)
} }
func cmdPrintComment(env CmdEnvironment) (*build.File, error) { func cmdPrintComment(opts *Options, env CmdEnvironment) (*build.File, error) {
attrError := func() error { attrError := func() error {
return fmt.Errorf("rule \"//%s:%s\" has no attribute \"%s\"", env.Pkg, env.Rule.Name(), env.Args[0]) return fmt.Errorf("rule \"//%s:%s\" has no attribute \"%s\"", env.Pkg, env.Rule.Name(), env.Args[0])
} }
@ -147,7 +149,7 @@ func cmdPrintComment(env CmdEnvironment) (*build.File, error) {
switch len(env.Args) { switch len(env.Args) {
case 0: // Print rule comment. case 0: // Print rule comment.
env.output.Fields = []*apipb.Output_Record_Field{ env.output.Fields = []*apipb.Output_Record_Field{
&apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{commentsText(env.Rule.Call.Comments.Before)}}, {Value: &apipb.Output_Record_Field_Text{commentsText(env.Rule.Call.Comments.Before)}},
} }
case 1: // Print attribute comment. case 1: // Print attribute comment.
attr := env.Rule.AttrDefn(env.Args[0]) attr := env.Rule.AttrDefn(env.Args[0])
@ -156,7 +158,7 @@ func cmdPrintComment(env CmdEnvironment) (*build.File, error) {
} }
comments := append(attr.Before, attr.Suffix...) comments := append(attr.Before, attr.Suffix...)
env.output.Fields = []*apipb.Output_Record_Field{ env.output.Fields = []*apipb.Output_Record_Field{
&apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{commentsText(comments)}}, {Value: &apipb.Output_Record_Field_Text{commentsText(comments)}},
} }
case 2: // Print comment of a specific value in a list. case 2: // Print comment of a specific value in a list.
attr := env.Rule.Attr(env.Args[0]) attr := env.Rule.Attr(env.Args[0])
@ -170,7 +172,7 @@ func cmdPrintComment(env CmdEnvironment) (*build.File, error) {
} }
comments := append(expr.Comments.Before, expr.Comments.Suffix...) comments := append(expr.Comments.Before, expr.Comments.Suffix...)
env.output.Fields = []*apipb.Output_Record_Field{ env.output.Fields = []*apipb.Output_Record_Field{
&apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{commentsText(comments)}}, {Value: &apipb.Output_Record_Field_Text{commentsText(comments)}},
} }
default: default:
panic("cmdPrintComment") panic("cmdPrintComment")
@ -178,11 +180,11 @@ func cmdPrintComment(env CmdEnvironment) (*build.File, error) {
return nil, nil return nil, nil
} }
func cmdDelete(env CmdEnvironment) (*build.File, error) { func cmdDelete(opts *Options, env CmdEnvironment) (*build.File, error) {
return DeleteRule(env.File, env.Rule), nil return DeleteRule(env.File, env.Rule), nil
} }
func cmdMove(env CmdEnvironment) (*build.File, error) { func cmdMove(opts *Options, env CmdEnvironment) (*build.File, error) {
oldAttr := env.Args[0] oldAttr := env.Args[0]
newAttr := env.Args[1] newAttr := env.Args[1]
if len(env.Args) == 3 && env.Args[2] == "*" { if len(env.Args) == 3 && env.Args[2] == "*" {
@ -204,7 +206,7 @@ func cmdMove(env CmdEnvironment) (*build.File, error) {
return nil, nil return nil, nil
} }
func cmdNew(env CmdEnvironment) (*build.File, error) { func cmdNew(opts *Options, env CmdEnvironment) (*build.File, error) {
kind := env.Args[0] kind := env.Args[0]
name := env.Args[1] name := env.Args[1]
addAtEOF, insertionIndex, err := findInsertionIndex(env) addAtEOF, insertionIndex, err := findInsertionIndex(env)
@ -217,7 +219,7 @@ func cmdNew(env CmdEnvironment) (*build.File, error) {
} }
call := &build.CallExpr{X: &build.LiteralExpr{Token: kind}} call := &build.CallExpr{X: &build.LiteralExpr{Token: kind}}
rule := &build.Rule{Call: call} rule := &build.Rule{call, ""}
rule.SetAttr("name", &build.StringExpr{Value: name}) rule.SetAttr("name", &build.StringExpr{Value: name})
if addAtEOF { if addAtEOF {
@ -235,7 +237,7 @@ func findInsertionIndex(env CmdEnvironment) (bool, int, error) {
} }
relativeToRuleName := env.Args[3] relativeToRuleName := env.Args[3]
ruleIdx := IndexOfRuleByName(env.File, relativeToRuleName) ruleIdx, _ := IndexOfRuleByName(env.File, relativeToRuleName)
if ruleIdx == -1 { if ruleIdx == -1 {
return true, 0, nil return true, 0, nil
} }
@ -250,12 +252,12 @@ func findInsertionIndex(env CmdEnvironment) (bool, int, error) {
} }
} }
func cmdNewLoad(env CmdEnvironment) (*build.File, error) { func cmdNewLoad(opts *Options, env CmdEnvironment) (*build.File, error) {
env.File.Stmt = InsertLoad(env.File.Stmt, env.Args) env.File.Stmt = InsertLoad(env.File.Stmt, env.Args)
return env.File, nil return env.File, nil
} }
func cmdPrint(env CmdEnvironment) (*build.File, error) { func cmdPrint(opts *Options, env CmdEnvironment) (*build.File, error) {
format := env.Args format := env.Args
if len(format) == 0 { if len(format) == 0 {
format = []string{"name", "kind"} format = []string{"name", "kind"}
@ -266,8 +268,10 @@ func cmdPrint(env CmdEnvironment) (*build.File, error) {
value := env.Rule.Attr(str) value := env.Rule.Attr(str)
if str == "kind" { if str == "kind" {
fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{env.Rule.Kind()}} fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{env.Rule.Kind()}}
} else if str == "name" {
fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{env.Rule.Name()}}
} else if str == "label" { } else if str == "label" {
if env.Rule.Attr("name") != nil { if env.Rule.Name() != "" {
fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{fmt.Sprintf("//%s:%s", env.Pkg, env.Rule.Name())}} fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{fmt.Sprintf("//%s:%s", env.Pkg, env.Rule.Name())}}
} else { } else {
return nil, nil return nil, nil
@ -310,7 +314,7 @@ func attrKeysForPattern(rule *build.Rule, pattern string) []string {
return []string{pattern} return []string{pattern}
} }
func cmdRemove(env CmdEnvironment) (*build.File, error) { func cmdRemove(opts *Options, env CmdEnvironment) (*build.File, error) {
if len(env.Args) == 1 { // Remove the attribute if len(env.Args) == 1 { // Remove the attribute
if env.Rule.DelAttr(env.Args[0]) != nil { if env.Rule.DelAttr(env.Args[0]) != nil {
return env.File, nil return env.File, nil
@ -330,7 +334,7 @@ func cmdRemove(env CmdEnvironment) (*build.File, error) {
return nil, nil return nil, nil
} }
func cmdRename(env CmdEnvironment) (*build.File, error) { func cmdRename(opts *Options, env CmdEnvironment) (*build.File, error) {
oldAttr := env.Args[0] oldAttr := env.Args[0]
newAttr := env.Args[1] newAttr := env.Args[1]
if err := RenameAttribute(env.Rule, oldAttr, newAttr); err != nil { if err := RenameAttribute(env.Rule, oldAttr, newAttr); err != nil {
@ -339,7 +343,7 @@ func cmdRename(env CmdEnvironment) (*build.File, error) {
return env.File, nil return env.File, nil
} }
func cmdReplace(env CmdEnvironment) (*build.File, error) { func cmdReplace(opts *Options, env CmdEnvironment) (*build.File, error) {
oldV := env.Args[1] oldV := env.Args[1]
newV := env.Args[2] newV := env.Args[2]
for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) { for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
@ -355,7 +359,27 @@ func cmdReplace(env CmdEnvironment) (*build.File, error) {
return env.File, nil return env.File, nil
} }
func cmdSet(env CmdEnvironment) (*build.File, error) { func cmdSubstitute(opts *Options, env CmdEnvironment) (*build.File, error) {
oldRegexp, err := regexp.Compile(env.Args[1])
if err != nil {
return nil, err
}
newTemplate := env.Args[2]
for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
attr := env.Rule.Attr(key)
e, ok := attr.(*build.StringExpr)
if !ok {
ListSubstitute(attr, oldRegexp, newTemplate)
continue
}
if newValue, ok := stringSubstitute(e.Value, oldRegexp, newTemplate); ok {
env.Rule.SetAttr(key, getAttrValueExpr(key, []string{newValue}))
}
}
return env.File, nil
}
func cmdSet(opts *Options, env CmdEnvironment) (*build.File, error) {
attr := env.Args[0] attr := env.Args[0]
args := env.Args[1:] args := env.Args[1:]
if attr == "kind" { if attr == "kind" {
@ -366,7 +390,7 @@ func cmdSet(env CmdEnvironment) (*build.File, error) {
return env.File, nil return env.File, nil
} }
func cmdSetIfAbsent(env CmdEnvironment) (*build.File, error) { func cmdSetIfAbsent(opts *Options, env CmdEnvironment) (*build.File, error) {
attr := env.Args[0] attr := env.Args[0]
args := env.Args[1:] args := env.Args[1:]
if attr == "kind" { if attr == "kind" {
@ -401,14 +425,14 @@ func getAttrValueExpr(attr string, args []string) build.Expr {
} }
} }
func cmdCopy(env CmdEnvironment) (*build.File, error) { func cmdCopy(opts *Options, env CmdEnvironment) (*build.File, error) {
attrName := env.Args[0] attrName := env.Args[0]
from := env.Args[1] from := env.Args[1]
return copyAttributeBetweenRules(env, attrName, from) return copyAttributeBetweenRules(env, attrName, from)
} }
func cmdCopyNoOverwrite(env CmdEnvironment) (*build.File, error) { func cmdCopyNoOverwrite(opts *Options, env CmdEnvironment) (*build.File, error) {
attrName := env.Args[0] attrName := env.Args[0]
from := env.Args[1] from := env.Args[1]
@ -438,7 +462,7 @@ func copyAttributeBetweenRules(env CmdEnvironment, attrName string, from string)
return env.File, nil return env.File, nil
} }
func cmdFix(env CmdEnvironment) (*build.File, error) { func cmdFix(opts *Options, env CmdEnvironment) (*build.File, error) {
// Fix the whole file // Fix the whole file
if env.Rule.Kind() == "package" { if env.Rule.Kind() == "package" {
return FixFile(env.File, env.Pkg, env.Args), nil return FixFile(env.File, env.Pkg, env.Args), nil
@ -449,7 +473,7 @@ func cmdFix(env CmdEnvironment) (*build.File, error) {
// CommandInfo provides a command function and info on incoming arguments. // CommandInfo provides a command function and info on incoming arguments.
type CommandInfo struct { type CommandInfo struct {
Fn func(CmdEnvironment) (*build.File, error) Fn func(*Options, CmdEnvironment) (*build.File, error)
MinArg int MinArg int
MaxArg int MaxArg int
Template string Template string
@ -470,6 +494,7 @@ var AllCommands = map[string]CommandInfo{
"remove": {cmdRemove, 1, -1, "<attr> <value(s)>"}, "remove": {cmdRemove, 1, -1, "<attr> <value(s)>"},
"rename": {cmdRename, 2, 2, "<old_attr> <new_attr>"}, "rename": {cmdRename, 2, 2, "<old_attr> <new_attr>"},
"replace": {cmdReplace, 3, 3, "<attr> <old_value> <new_value>"}, "replace": {cmdReplace, 3, 3, "<attr> <old_value> <new_value>"},
"substitute": {cmdSubstitute, 3, 3, "<attr> <old_regexp> <new_template>"},
"set": {cmdSet, 2, -1, "<attr> <value(s)>"}, "set": {cmdSet, 2, -1, "<attr> <value(s)>"},
"set_if_absent": {cmdSetIfAbsent, 2, -1, "<attr> <value(s)>"}, "set_if_absent": {cmdSetIfAbsent, 2, -1, "<attr> <value(s)>"},
"copy": {cmdCopy, 2, 2, "<attr> <from_rule>"}, "copy": {cmdCopy, 2, 2, "<attr> <from_rule>"},
@ -500,13 +525,13 @@ func expandTargets(f *build.File, rule string) ([]*build.Rule, error) {
return nil, fmt.Errorf("rule '%s' not found", rule) return nil, fmt.Errorf("rule '%s' not found", rule)
} }
func filterRules(rules []*build.Rule) (result []*build.Rule) { func filterRules(opts *Options, rules []*build.Rule) (result []*build.Rule) {
if len(Opts.FilterRuleTypes) == 0 { if len(opts.FilterRuleTypes) == 0 {
return rules return rules
} }
for _, rule := range rules { for _, rule := range rules {
acceptableType := false acceptableType := false
for _, filterType := range Opts.FilterRuleTypes { for _, filterType := range opts.FilterRuleTypes {
if rule.Kind() == filterType { if rule.Kind() == filterType {
acceptableType = true acceptableType = true
break break
@ -634,7 +659,7 @@ var buildFileNamesSet = map[string]bool{
// rewrite parses the BUILD file for the given file, transforms the AST, // rewrite parses the BUILD file for the given file, transforms the AST,
// and write the changes back in the file (or on stdout). // and write the changes back in the file (or on stdout).
func rewrite(commandsForFile commandsForFile) *rewriteResult { func rewrite(opts *Options, commandsForFile commandsForFile) *rewriteResult {
name := commandsForFile.file name := commandsForFile.file
var data []byte var data []byte
var err error var err error
@ -676,7 +701,7 @@ func rewrite(commandsForFile commandsForFile) *rewriteResult {
} }
vars := map[string]*build.BinaryExpr{} vars := map[string]*build.BinaryExpr{}
if Opts.EditVariables { if opts.EditVariables {
vars = getGlobalVariables(f.Stmt) vars = getGlobalVariables(f.Stmt)
} }
var errs []error var errs []error
@ -684,7 +709,7 @@ func rewrite(commandsForFile commandsForFile) *rewriteResult {
for _, commands := range commandsForFile.commands { for _, commands := range commandsForFile.commands {
target := commands.target target := commands.target
commands := commands.commands commands := commands.commands
_, absPkg, rule := InterpretLabelForWorkspaceLocation(Opts.RootDir, target) _, absPkg, rule := InterpretLabelForWorkspaceLocation(opts.RootDir, target)
_, pkg, _ := ParseLabel(target) _, pkg, _ := ParseLabel(target)
if pkg == stdinPackageName { // Special-case: This is already absolute if pkg == stdinPackageName { // Special-case: This is already absolute
absPkg = stdinPackageName absPkg = stdinPackageName
@ -694,23 +719,23 @@ func rewrite(commandsForFile commandsForFile) *rewriteResult {
if err != nil { if err != nil {
cerr := commandError(commands, target, err) cerr := commandError(commands, target, err)
errs = append(errs, cerr) errs = append(errs, cerr)
if !Opts.KeepGoing { if !opts.KeepGoing {
return &rewriteResult{file: name, errs: errs, records: records} return &rewriteResult{file: name, errs: errs, records: records}
} }
} }
targets = filterRules(targets) targets = filterRules(opts, targets)
for _, cmd := range commands { for _, cmd := range commands {
for _, r := range targets { for _, r := range targets {
cmdInfo := AllCommands[cmd.tokens[0]] cmdInfo := AllCommands[cmd.tokens[0]]
record := &apipb.Output_Record{} record := &apipb.Output_Record{}
newf, err := cmdInfo.Fn(CmdEnvironment{f, r, vars, absPkg, cmd.tokens[1:], record}) newf, err := cmdInfo.Fn(opts, CmdEnvironment{f, r, vars, absPkg, cmd.tokens[1:], record})
if len(record.Fields) != 0 { if len(record.Fields) != 0 {
records = append(records, record) records = append(records, record)
} }
if err != nil { if err != nil {
cerr := commandError([]command{cmd}, target, err) cerr := commandError([]command{cmd}, target, err)
if Opts.KeepGoing { if opts.KeepGoing {
errs = append(errs, cerr) errs = append(errs, cerr)
} else { } else {
return &rewriteResult{file: name, errs: []error{cerr}, records: records} return &rewriteResult{file: name, errs: []error{cerr}, records: records}
@ -727,12 +752,12 @@ func rewrite(commandsForFile commandsForFile) *rewriteResult {
return &rewriteResult{file: name, errs: errs, records: records} return &rewriteResult{file: name, errs: errs, records: records}
} }
f = RemoveEmptyPackage(f) f = RemoveEmptyPackage(f)
ndata, err := runBuildifier(f) ndata, err := runBuildifier(opts, f)
if err != nil { if err != nil {
return &rewriteResult{file: name, errs: []error{fmt.Errorf("running buildifier: %v", err)}, records: records} return &rewriteResult{file: name, errs: []error{fmt.Errorf("running buildifier: %v", err)}, records: records}
} }
if Opts.Stdout || name == stdinPackageName { if opts.Stdout || name == stdinPackageName {
os.Stdout.Write(ndata) os.Stdout.Write(ndata)
return &rewriteResult{file: name, errs: errs, records: records} return &rewriteResult{file: name, errs: errs, records: records}
} }
@ -760,15 +785,15 @@ var EditFile = func(fi os.FileInfo, name string) error {
} }
// runBuildifier formats the build file f. // runBuildifier formats the build file f.
// Runs Opts.Buildifier if it's non-empty, otherwise uses built-in formatter. // Runs opts.Buildifier if it's non-empty, otherwise uses built-in formatter.
// Opts.Buildifier is useful to force consistency with other tools that call Buildifier. // opts.Buildifier is useful to force consistency with other tools that call Buildifier.
func runBuildifier(f *build.File) ([]byte, error) { func runBuildifier(opts *Options, f *build.File) ([]byte, error) {
if Opts.Buildifier == "" { if opts.Buildifier == "" {
build.Rewrite(f, nil) build.Rewrite(f, nil)
return build.Format(f), nil return build.Format(f), nil
} }
cmd := exec.Command(Opts.Buildifier) cmd := exec.Command(opts.Buildifier)
data := build.Format(f) data := build.Format(f)
cmd.Stdin = bytes.NewBuffer(data) cmd.Stdin = bytes.NewBuffer(data)
stdout := bytes.NewBuffer(nil) stdout := bytes.NewBuffer(nil)
@ -787,9 +812,9 @@ func runBuildifier(f *build.File) ([]byte, error) {
// Given a target, whose package may contain a trailing "/...", returns all // Given a target, whose package may contain a trailing "/...", returns all
// extisting BUILD file paths which match the package. // extisting BUILD file paths which match the package.
func targetExpressionToBuildFiles(target string) []string { func targetExpressionToBuildFiles(opts *Options, target string) []string {
file, _, _ := InterpretLabelForWorkspaceLocation(Opts.RootDir, target) file, _, _ := InterpretLabelForWorkspaceLocation(opts.RootDir, target)
if Opts.RootDir == "" { if opts.RootDir == "" {
var err error var err error
if file, err = filepath.Abs(file); err != nil { if file, err = filepath.Abs(file); err != nil {
fmt.Printf("Cannot make path absolute: %s\n", err.Error()) fmt.Printf("Cannot make path absolute: %s\n", err.Error())
@ -827,7 +852,7 @@ func targetExpressionToBuildFiles(target string) []string {
// appendCommands adds the given commands to be applied to each of the given targets // appendCommands adds the given commands to be applied to each of the given targets
// via the commandMap. // via the commandMap.
func appendCommands(commandMap map[string][]commandsForTarget, args []string) { func appendCommands(opts *Options, commandMap map[string][]commandsForTarget, args []string) {
commands, targets := parseCommands(args) commands, targets := parseCommands(args)
for _, target := range targets { for _, target := range targets {
if strings.HasSuffix(target, "/BUILD") { if strings.HasSuffix(target, "/BUILD") {
@ -838,7 +863,7 @@ func appendCommands(commandMap map[string][]commandsForTarget, args []string) {
if pkg == stdinPackageName { if pkg == stdinPackageName {
buildFiles = []string{stdinPackageName} buildFiles = []string{stdinPackageName}
} else { } else {
buildFiles = targetExpressionToBuildFiles(target) buildFiles = targetExpressionToBuildFiles(opts, target)
} }
for _, file := range buildFiles { for _, file := range buildFiles {
@ -847,12 +872,12 @@ func appendCommands(commandMap map[string][]commandsForTarget, args []string) {
} }
} }
func appendCommandsFromFile(commandsByFile map[string][]commandsForTarget, fileName string) { func appendCommandsFromFile(opts *Options, commandsByFile map[string][]commandsForTarget, fileName string) {
var reader io.Reader var reader io.Reader
if Opts.CommandsFile == stdinPackageName { if opts.CommandsFile == stdinPackageName {
reader = os.Stdin reader = os.Stdin
} else { } else {
rc := file.OpenReadFile(Opts.CommandsFile) rc := file.OpenReadFile(opts.CommandsFile)
reader = rc reader = rc
defer rc.Close() defer rc.Close()
} }
@ -863,7 +888,7 @@ func appendCommandsFromFile(commandsByFile map[string][]commandsForTarget, fileN
continue continue
} }
args := strings.Split(line, "|") args := strings.Split(line, "|")
appendCommands(commandsByFile, args) appendCommands(opts, commandsByFile, args)
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error while reading commands file: %v", scanner.Err()) fmt.Fprintf(os.Stderr, "Error while reading commands file: %v", scanner.Err())
@ -905,28 +930,28 @@ func printRecord(writer io.Writer, record *apipb.Output_Record) {
} }
// Buildozer loops over all arguments on the command line fixing BUILD files. // Buildozer loops over all arguments on the command line fixing BUILD files.
func Buildozer(args []string) int { func Buildozer(opts *Options, args []string) int {
commandsByFile := make(map[string][]commandsForTarget) commandsByFile := make(map[string][]commandsForTarget)
if Opts.CommandsFile != "" { if opts.CommandsFile != "" {
appendCommandsFromFile(commandsByFile, Opts.CommandsFile) appendCommandsFromFile(opts, commandsByFile, opts.CommandsFile)
} else { } else {
if len(args) == 0 { if len(args) == 0 {
Usage() Usage()
} }
appendCommands(commandsByFile, args) appendCommands(opts, commandsByFile, args)
} }
numFiles := len(commandsByFile) numFiles := len(commandsByFile)
if Opts.Parallelism > 0 { if opts.Parallelism > 0 {
runtime.GOMAXPROCS(Opts.Parallelism) runtime.GOMAXPROCS(opts.Parallelism)
} }
results := make(chan *rewriteResult, numFiles) results := make(chan *rewriteResult, numFiles)
data := make(chan commandsForFile) data := make(chan commandsForFile)
for i := 0; i < Opts.NumIO; i++ { for i := 0; i < opts.NumIO; i++ {
go func(results chan *rewriteResult, data chan commandsForFile) { go func(results chan *rewriteResult, data chan commandsForFile) {
for commandsForFile := range data { for commandsForFile := range data {
results <- rewrite(commandsForFile) results <- rewrite(opts, commandsForFile)
} }
}(results, data) }(results, data)
} }
@ -946,7 +971,7 @@ func Buildozer(args []string) int {
for _, err := range fileResults.errs { for _, err := range fileResults.errs {
fmt.Fprintf(os.Stderr, "%s: %s\n", fileResults.file, err) fmt.Fprintf(os.Stderr, "%s: %s\n", fileResults.file, err)
} }
if fileResults.modified && !Opts.Quiet { if fileResults.modified && !opts.Quiet {
fmt.Fprintf(os.Stderr, "fixed %s\n", fileResults.file) fmt.Fprintf(os.Stderr, "fixed %s\n", fileResults.file)
} }
if fileResults.records != nil { if fileResults.records != nil {
@ -954,7 +979,7 @@ func Buildozer(args []string) int {
} }
} }
if Opts.IsPrintingProto { if opts.IsPrintingProto {
data, err := proto.Marshal(&apipb.Output{Records: records}) data, err := proto.Marshal(&apipb.Output{Records: records})
if err != nil { if err != nil {
log.Fatal("marshaling error: ", err) log.Fatal("marshaling error: ", err)
@ -969,7 +994,7 @@ func Buildozer(args []string) int {
if hasErrors { if hasErrors {
return 2 return 2
} }
if !fileModified && !Opts.Stdout { if !fileModified && !opts.Stdout {
return 3 return 3
} }
return 0 return 0

View File

@ -20,11 +20,13 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"github.com/bazelbuild/buildtools/build" "github.com/bazelbuild/buildtools/build"
"github.com/bazelbuild/buildtools/tables"
"github.com/bazelbuild/buildtools/wspace" "github.com/bazelbuild/buildtools/wspace"
) )
@ -55,7 +57,7 @@ func ParseLabel(target string) (string, string, string) {
parts := strings.SplitN(target, ":", 2) parts := strings.SplitN(target, ":", 2)
parts[0] = strings.TrimPrefix(parts[0], "//") parts[0] = strings.TrimPrefix(parts[0], "//")
if len(parts) == 1 { if len(parts) == 1 {
if strings.HasPrefix(target, "//") { if strings.HasPrefix(target, "//") || tables.StripLabelLeadingSlashes {
// "//absolute/pkg" -> "absolute/pkg", "pkg" // "//absolute/pkg" -> "absolute/pkg", "pkg"
return repo, parts[0], path.Base(parts[0]) return repo, parts[0], path.Base(parts[0])
} }
@ -164,7 +166,7 @@ func ExprToRule(expr build.Expr, kind string) (*build.Rule, bool) {
if !ok || k.Token != kind { if !ok || k.Token != kind {
return nil, false return nil, false
} }
return &build.Rule{Call: call}, true return &build.Rule{call, ""}, true
} }
// ExistingPackageDeclaration returns the package declaration, or nil if there is none. // ExistingPackageDeclaration returns the package declaration, or nil if there is none.
@ -200,7 +202,7 @@ func PackageDeclaration(f *build.File) *build.Rule {
all = append(all, call) all = append(all, call)
} }
f.Stmt = all f.Stmt = all
return &build.Rule{Call: call} return &build.Rule{call, ""}
} }
// RemoveEmptyPackage removes empty package declarations from the file, i.e.: // RemoveEmptyPackage removes empty package declarations from the file, i.e.:
@ -274,49 +276,12 @@ func FindRuleByName(f *build.File, name string) *build.Rule {
if name == "__pkg__" { if name == "__pkg__" {
return PackageDeclaration(f) return PackageDeclaration(f)
} }
i := IndexOfRuleByName(f, name) _, rule := IndexOfRuleByName(f, name)
if i != -1 { return rule
return &build.Rule{Call: f.Stmt[i].(*build.CallExpr)}
}
return nil
}
// UseImplicitName returns the rule in the file if it meets these conditions:
// - It is the only unnamed rule in the file.
// - The file path's ending directory name and the passed rule name match.
// In the Pants Build System, by pantsbuild, the use of an implicit name makes
// creating targets easier. This function implements such names.
func UseImplicitName(f *build.File, rule string) *build.Rule {
// We disallow empty names
if f.Path == "BUILD" {
return nil
}
ruleCount := 0
var temp, found *build.Rule
pkg := filepath.Base(filepath.Dir(f.Path))
for _, stmt := range f.Stmt {
call, ok := stmt.(*build.CallExpr)
if !ok {
continue
}
temp = &build.Rule{Call: call}
if temp.Kind() != "" && temp.Name() == "" {
ruleCount++
found = temp
}
}
if ruleCount == 1 {
if rule == pkg {
return found
}
}
return nil
} }
// IndexOfRuleByName returns the index (in f.Stmt) of the CallExpr which defines a rule named `name`, or -1 if it doesn't exist. // IndexOfRuleByName returns the index (in f.Stmt) of the CallExpr which defines a rule named `name`, or -1 if it doesn't exist.
func IndexOfRuleByName(f *build.File, name string) int { func IndexOfRuleByName(f *build.File, name string) (int, *build.Rule) {
linenum := -1 linenum := -1
if strings.HasPrefix(name, "%") { if strings.HasPrefix(name, "%") {
// "%<LINENUM>" will match the rule which begins at LINENUM. // "%<LINENUM>" will match the rule which begins at LINENUM.
@ -331,13 +296,13 @@ func IndexOfRuleByName(f *build.File, name string) int {
if !ok { if !ok {
continue continue
} }
r := &build.Rule{Call: call} r := f.Rule(call)
start, _ := call.X.Span() start, _ := call.X.Span()
if r.Name() == name || start.Line == linenum { if r.Name() == name || start.Line == linenum {
return i return i, r
} }
} }
return -1 return -1, nil
} }
// FindExportedFile returns the first exports_files call which contains the // FindExportedFile returns the first exports_files call which contains the
@ -377,7 +342,7 @@ func DeleteRuleByName(f *build.File, name string) *build.File {
all = append(all, stmt) all = append(all, stmt)
continue continue
} }
r := &build.Rule{Call: call} r := f.Rule(call)
if r.Name() != name { if r.Name() != name {
all = append(all, stmt) all = append(all, stmt)
} }
@ -537,6 +502,42 @@ func ListReplace(e build.Expr, old, value, pkg string) bool {
return replaced return replaced
} }
// ListSubstitute replaces strings matching a regular expression in all lists
// in e and returns a Boolean to indicate whether the replacement was
// successful.
func ListSubstitute(e build.Expr, oldRegexp *regexp.Regexp, newTemplate string) bool {
substituted := false
for _, li := range AllLists(e) {
for k, elem := range li.List {
str, ok := elem.(*build.StringExpr)
if !ok {
continue
}
newValue, ok := stringSubstitute(str.Value, oldRegexp, newTemplate)
if ok {
li.List[k] = &build.StringExpr{Value: newValue, Comments: *elem.Comment()}
substituted = true
}
}
}
return substituted
}
func stringSubstitute(oldValue string, oldRegexp *regexp.Regexp, newTemplate string) (string, bool) {
match := oldRegexp.FindStringSubmatchIndex(oldValue)
if match == nil {
return oldValue, false
}
newValue := string(oldRegexp.ExpandString(nil, newTemplate, oldValue, match))
if match[0] > 0 {
newValue = oldValue[:match[0]] + newValue
}
if match[1] < len(oldValue) {
newValue = newValue + oldValue[match[1]:]
}
return newValue, true
}
// isExprLessThan compares two Expr statements. Currently, only labels are supported. // isExprLessThan compares two Expr statements. Currently, only labels are supported.
func isExprLessThan(x1, x2 build.Expr) bool { func isExprLessThan(x1, x2 build.Expr) bool {
str1, ok1 := x1.(*build.StringExpr) str1, ok1 := x1.(*build.StringExpr)

View File

@ -17,12 +17,22 @@ package edit
import ( import (
buildpb "github.com/bazelbuild/buildtools/build_proto" buildpb "github.com/bazelbuild/buildtools/build_proto"
"github.com/bazelbuild/buildtools/lang" "github.com/bazelbuild/buildtools/lang"
"github.com/bazelbuild/buildtools/tables"
) )
var typeOf = lang.TypeOf var typeOf = lang.TypeOf
// IsList returns true for all attributes whose type is a list. // IsList returns true for all attributes whose type is a list.
func IsList(attr string) bool { func IsList(attr string) bool {
overrideValue, isOverridden := tables.IsListArg[attr]
if isOverridden {
return overrideValue
}
// It stands to reason that a sortable list must be a list.
isSortableList := tables.IsSortableListArg[attr]
if isSortableList {
return true
}
ty := typeOf[attr] ty := typeOf[attr]
return ty == buildpb.Attribute_STRING_LIST || return ty == buildpb.Attribute_STRING_LIST ||
ty == buildpb.Attribute_LABEL_LIST || ty == buildpb.Attribute_LABEL_LIST ||

View File

@ -24,6 +24,7 @@ import (
type Definitions struct { type Definitions struct {
IsLabelArg map[string]bool IsLabelArg map[string]bool
LabelBlacklist map[string]bool LabelBlacklist map[string]bool
IsListArg map[string]bool
IsSortableListArg map[string]bool IsSortableListArg map[string]bool
SortableBlacklist map[string]bool SortableBlacklist map[string]bool
SortableWhitelist map[string]bool SortableWhitelist map[string]bool
@ -54,9 +55,9 @@ func ParseAndUpdateJSONDefinitions(file string, merge bool) error {
} }
if merge { if merge {
MergeTables(definitions.IsLabelArg, definitions.LabelBlacklist, definitions.IsSortableListArg, definitions.SortableBlacklist, definitions.SortableWhitelist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative) MergeTables(definitions.IsLabelArg, definitions.LabelBlacklist, definitions.IsListArg, definitions.IsSortableListArg, definitions.SortableBlacklist, definitions.SortableWhitelist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative)
} else { } else {
OverrideTables(definitions.IsLabelArg, definitions.LabelBlacklist, definitions.IsSortableListArg, definitions.SortableBlacklist, definitions.SortableWhitelist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative) OverrideTables(definitions.IsLabelArg, definitions.LabelBlacklist, definitions.IsListArg, definitions.IsSortableListArg, definitions.SortableBlacklist, definitions.SortableWhitelist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative)
} }
return nil return nil
} }

View File

@ -97,6 +97,11 @@ var LabelBlacklist = map[string]bool{
"package_group.includes": true, "package_group.includes": true,
} }
// By default, edit.types.IsList consults lang.TypeOf to determine if an arg is a list.
// You may override this using IsListArg. Specifying a name here overrides any value
// in lang.TypeOf.
var IsListArg = map[string]bool{}
// IsSortableListArg: a named argument to a rule call is considered to be a sortable list // IsSortableListArg: a named argument to a rule call is considered to be a sortable list
// if the name is one of these names. There is a separate blacklist for // if the name is one of these names. There is a separate blacklist for
// rule-specific exceptions. // rule-specific exceptions.
@ -200,10 +205,13 @@ var StripLabelLeadingSlashes = false
var ShortenAbsoluteLabelsToRelative = false var ShortenAbsoluteLabelsToRelative = false
var FormatBzlFiles = false
// OverrideTables allows a user of the build package to override the special-case rules. The user-provided tables replace the built-in tables. // OverrideTables allows a user of the build package to override the special-case rules. The user-provided tables replace the built-in tables.
func OverrideTables(labelArg, blacklist, sortableListArg, sortBlacklist, sortWhitelist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool) { func OverrideTables(labelArg, blacklist, listArg, sortableListArg, sortBlacklist, sortWhitelist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool) {
IsLabelArg = labelArg IsLabelArg = labelArg
LabelBlacklist = blacklist LabelBlacklist = blacklist
IsListArg = listArg
IsSortableListArg = sortableListArg IsSortableListArg = sortableListArg
SortableBlacklist = sortBlacklist SortableBlacklist = sortBlacklist
SortableWhitelist = sortWhitelist SortableWhitelist = sortWhitelist
@ -213,13 +221,16 @@ func OverrideTables(labelArg, blacklist, sortableListArg, sortBlacklist, sortWhi
} }
// MergeTables allows a user of the build package to override the special-case rules. The user-provided tables are merged into the built-in tables. // MergeTables allows a user of the build package to override the special-case rules. The user-provided tables are merged into the built-in tables.
func MergeTables(labelArg, blacklist, sortableListArg, sortBlacklist, sortWhitelist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool) { func MergeTables(labelArg, blacklist, listArg, sortableListArg, sortBlacklist, sortWhitelist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool) {
for k, v := range labelArg { for k, v := range labelArg {
IsLabelArg[k] = v IsLabelArg[k] = v
} }
for k, v := range blacklist { for k, v := range blacklist {
LabelBlacklist[k] = v LabelBlacklist[k] = v
} }
for k, v := range listArg {
IsListArg[k] = v
}
for k, v := range sortableListArg { for k, v := range sortableListArg {
IsSortableListArg[k] = v IsSortableListArg[k] = v
} }

2
vendor/modules.txt vendored
View File

@ -128,7 +128,7 @@ github.com/bazelbuild/bazel-gazelle/internal/rule
github.com/bazelbuild/bazel-gazelle/internal/version github.com/bazelbuild/bazel-gazelle/internal/version
github.com/bazelbuild/bazel-gazelle/internal/walk github.com/bazelbuild/bazel-gazelle/internal/walk
github.com/bazelbuild/bazel-gazelle/internal/wspace github.com/bazelbuild/bazel-gazelle/internal/wspace
# github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e => github.com/bazelbuild/buildtools v0.0.0-20171220125010-1a9c38e0df93 # github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e => github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e
github.com/bazelbuild/buildtools/api_proto github.com/bazelbuild/buildtools/api_proto
github.com/bazelbuild/buildtools/build github.com/bazelbuild/buildtools/build
github.com/bazelbuild/buildtools/build_proto github.com/bazelbuild/buildtools/build_proto