bpmodify: remove-property

`r` option removes only item(s) in a list, so add `remove-property`
option to remove a property itself.

Bug: 146436251
Test: unittest
Change-Id: I0c838d31e72358f094cfb5fa9468dce07018e061
diff --git a/bpmodify/bpmodify.go b/bpmodify/bpmodify.go
index 3a70689..431eb83 100644
--- a/bpmodify/bpmodify.go
+++ b/bpmodify/bpmodify.go
@@ -30,9 +30,9 @@
 	targetedProperty = new(qualifiedProperty)
 	addIdents        = new(identSet)
 	removeIdents     = new(identSet)
-
-	setString  *string
-	addLiteral *string
+	removeProperty   = flag.Bool("remove-property", false, "remove the property")
+	setString        *string
+	addLiteral       *string
 )
 
 func init() {
@@ -147,7 +147,7 @@
 
 func processModule(module *parser.Module, moduleName string,
 	file *parser.File) (modified bool, errs []error) {
-	prop, err := getRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes())
+	prop, parent, err := getRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes())
 	if err != nil {
 		return false, []error{err}
 	}
@@ -168,25 +168,28 @@
 			// Here should be unreachable, but still handle it for completeness.
 			return false, []error{err}
 		}
+	} else if *removeProperty {
+		// remove-property is used solely, so return here.
+		return parent.RemoveProperty(prop.Name), nil
 	}
 	m, errs := processParameter(prop.Value, targetedProperty.String(), moduleName, file)
 	modified = modified || m
 	return modified, errs
 }
 
-func getRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, err error) {
-	prop, _, err = getOrCreateRecursiveProperty(module, name, prefixes, nil)
-	return prop, err
+func getRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, parent *parser.Map, err error) {
+	prop, parent, _, err = getOrCreateRecursiveProperty(module, name, prefixes, nil)
+	return prop, parent, err
 }
 
 func createRecursiveProperty(module *parser.Module, name string, prefixes []string,
 	empty parser.Expression) (prop *parser.Property, modified bool, err error) {
-
-	return getOrCreateRecursiveProperty(module, name, prefixes, empty)
+	prop, _, modified, err = getOrCreateRecursiveProperty(module, name, prefixes, empty)
+	return prop, modified, err
 }
 
 func getOrCreateRecursiveProperty(module *parser.Module, name string, prefixes []string,
-	empty parser.Expression) (prop *parser.Property, modified bool, err error) {
+	empty parser.Expression) (prop *parser.Property, parent *parser.Map, modified bool, err error) {
 	m := &module.Map
 	for i, prefix := range prefixes {
 		if prop, found := m.GetProperty(prefix); found {
@@ -195,7 +198,7 @@
 			} else {
 				// We've found a property in the AST and such property is not of type
 				// *parser.Map, which must mean we didn't modify the AST.
-				return nil, false, fmt.Errorf("Expected property %q to be a map, found %s",
+				return nil, nil, false, fmt.Errorf("Expected property %q to be a map, found %s",
 					strings.Join(prefixes[:i+1], "."), prop.Value.Type())
 			}
 		} else if empty != nil {
@@ -206,18 +209,18 @@
 			// check after this for loop must fail, because the node we inserted is an
 			// empty parser.Map, thus this function will return |modified| is true.
 		} else {
-			return nil, false, nil
+			return nil, nil, false, nil
 		}
 	}
 	if prop, found := m.GetProperty(name); found {
 		// We've found a property in the AST, which must mean we didn't modify the AST.
-		return prop, false, nil
+		return prop, m, false, nil
 	} else if empty != nil {
 		prop = &parser.Property{Name: name, Value: empty}
 		m.Properties = append(m.Properties, prop)
-		return prop, true, nil
+		return prop, m, true, nil
 	} else {
-		return nil, false, nil
+		return nil, nil, false, nil
 	}
 }
 
@@ -341,8 +344,13 @@
 		return
 	}
 
-	if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil && addLiteral == nil {
-		report(fmt.Errorf("-a, -add-literal, -r or -str parameter is required"))
+	if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil && addLiteral == nil && !*removeProperty {
+		report(fmt.Errorf("-a, -add-literal, -r, -remove-property or -str parameter is required"))
+		return
+	}
+
+	if *removeProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil) {
+		report(fmt.Errorf("-remove-property cannot be used with other parameter(s)"))
 		return
 	}
 
diff --git a/bpmodify/bpmodify_test.go b/bpmodify/bpmodify_test.go
index 9740e63..4340edb 100644
--- a/bpmodify/bpmodify_test.go
+++ b/bpmodify/bpmodify_test.go
@@ -23,14 +23,15 @@
 )
 
 var testCases = []struct {
-	name       string
-	input      string
-	output     string
-	property   string
-	addSet     string
-	removeSet  string
-	addLiteral *string
-	setString  *string
+	name           string
+	input          string
+	output         string
+	property       string
+	addSet         string
+	removeSet      string
+	addLiteral     *string
+	setString      *string
+	removeProperty bool
 }{
 	{
 		name: "add",
@@ -304,6 +305,56 @@
 		property:  "foo",
 		setString: proptools.StringPtr("bar"),
 	},
+	{
+		name: "remove existing property",
+		input: `
+			cc_foo {
+				name: "foo",
+				foo: "baz",
+			}
+		`,
+		output: `
+			cc_foo {
+				name: "foo",
+			}
+		`,
+		property:       "foo",
+		removeProperty: true,
+	}, {
+		name: "remove nested property",
+		input: `
+		cc_foo {
+			name: "foo",
+			foo: {
+				bar: "baz",
+			},
+		}
+	`,
+		output: `
+		cc_foo {
+			name: "foo",
+			foo: {},
+		}
+	`,
+		property:       "foo.bar",
+		removeProperty: true,
+	}, {
+		name: "remove non-existing property",
+		input: `
+			cc_foo {
+				name: "foo",
+				foo: "baz",
+			}
+		`,
+		output: `
+			cc_foo {
+				name: "foo",
+				foo: "baz",
+			}
+		`,
+		property:       "bar",
+		removeProperty: true,
+	},
 }
 
 func simplifyModuleDefinition(def string) string {
@@ -320,6 +371,7 @@
 			targetedProperty.Set(testCase.property)
 			addIdents.Set(testCase.addSet)
 			removeIdents.Set(testCase.removeSet)
+			removeProperty = &testCase.removeProperty
 			setString = testCase.setString
 			addLiteral = testCase.addLiteral