cmd/go is not subject to all the same restrictions as most of cmd.
In particular it need not be buildable with the bootstrap toolchain.
So it is better to keep as little code shared between cmd/go and
cmd/compile, cmd/link, cmd/cgo as possible.
cmd/internal/str started as cmd/go/internal/str but was moved
to cmd/internal in order to make use of the quoted string code.
Move that code to cmd/internal/quoted and then move the rest of
cmd/internal/str back to cmd/go/internal/str.
Change-Id: I3a98f754d545cc3af7e9a32c2b77a5a035ea7b9a
Reviewed-on: https://go-review.googlesource.com/c/go/+/355010
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
"unicode"
"unicode/utf8"
- "cmd/internal/str"
+ "cmd/internal/quoted"
)
var debugDefine = flag.Bool("debug-define", false, "print relevant #defines")
if value == "" {
value = defaultCC(goos, goarch)
}
- args, err := str.SplitQuotedFields(value)
+ args, err := quoted.Split(value)
if err != nil {
return nil, err
}
import (
cmddwarf "cmd/internal/dwarf"
- "cmd/internal/str"
+ "cmd/internal/quoted"
"debug/dwarf"
"debug/elf"
"debug/macho"
if extld == "" {
extld = "gcc"
}
- extldArgs, err := str.SplitQuotedFields(extld)
+ extldArgs, err := quoted.Split(extld)
if err != nil {
t.Fatal(err)
}
"cmd/internal/obj/...",
"cmd/internal/objabi",
"cmd/internal/pkgpath",
+ "cmd/internal/quoted",
"cmd/internal/src",
- "cmd/internal/str",
"cmd/internal/sys",
"cmd/link",
"cmd/link/internal/...",
"sync"
"cmd/go/internal/cfg"
- "cmd/internal/str"
+ "cmd/go/internal/str"
)
// A Command is an implementation of a go command
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
- "cmd/internal/str"
+ "cmd/internal/quoted"
)
// A StringsFlag is a command-line flag that interprets its argument
func (v *StringsFlag) Set(s string) error {
var err error
- *v, err = str.SplitQuotedFields(s)
+ *v, err = quoted.Split(s)
if *v == nil {
*v = []string{}
}
"cmd/go/internal/load"
"cmd/go/internal/modload"
"cmd/go/internal/work"
- "cmd/internal/str"
+ "cmd/internal/quoted"
)
var CmdEnv = &base.Command{
if val == "" {
break
}
- args, err := str.SplitQuotedFields(val)
+ args, err := quoted.Split(val)
if err != nil {
return fmt.Errorf("invalid %s: %v", key, err)
}
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/modload"
- "cmd/internal/str"
+ "cmd/go/internal/str"
"context"
"fmt"
"os"
"cmd/go/internal/load"
"cmd/go/internal/modload"
"cmd/go/internal/work"
- "cmd/internal/str"
+ "cmd/go/internal/str"
)
var CmdGenerate = &base.Command{
"cmd/go/internal/vcs"
"cmd/go/internal/web"
"cmd/go/internal/work"
- "cmd/internal/str"
+ "cmd/go/internal/str"
"golang.org/x/mod/module"
)
"cmd/go/internal/modinfo"
"cmd/go/internal/modload"
"cmd/go/internal/work"
- "cmd/internal/str"
+ "cmd/go/internal/str"
)
var CmdList = &base.Command{
import (
"cmd/go/internal/base"
- "cmd/internal/str"
+ "cmd/internal/quoted"
"fmt"
"strings"
)
match = MatchPackage(pattern, cwd)
v = v[i+1:]
}
- flags, err := str.SplitQuotedFields(v)
+ flags, err := quoted.Split(v)
if err != nil {
return err
}
"cmd/go/internal/modload"
"cmd/go/internal/par"
"cmd/go/internal/search"
+ "cmd/go/internal/str"
"cmd/go/internal/trace"
"cmd/go/internal/vcs"
- "cmd/internal/str"
"cmd/internal/sys"
"golang.org/x/mod/modfile"
"cmd/go/internal/fsys"
"cmd/go/internal/trace"
- "cmd/internal/str"
+ "cmd/go/internal/str"
)
var TestMainDeps = []string{
"cmd/go/internal/imports"
"cmd/go/internal/load"
"cmd/go/internal/modload"
- "cmd/internal/str"
+ "cmd/go/internal/str"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
"cmd/go/internal/cfg"
"cmd/go/internal/lockedfile"
- "cmd/internal/str"
+ "cmd/go/internal/str"
)
// Downloaded size limits.
"cmd/go/internal/lockedfile"
"cmd/go/internal/par"
- "cmd/internal/str"
+ "cmd/go/internal/str"
)
// A VCSError indicates an error using a version control system.
"cmd/go/internal/base"
"cmd/go/internal/modload"
"cmd/go/internal/search"
- "cmd/internal/str"
+ "cmd/go/internal/str"
"golang.org/x/mod/module"
)
"cmd/go/internal/mvs"
"cmd/go/internal/par"
"cmd/go/internal/search"
- "cmd/internal/str"
+ "cmd/go/internal/str"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
"cmd/go/internal/modfetch"
"cmd/go/internal/search"
"cmd/go/internal/trace"
- "cmd/internal/str"
+ "cmd/go/internal/str"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
"cmd/go/internal/load"
"cmd/go/internal/modload"
"cmd/go/internal/work"
- "cmd/internal/str"
+ "cmd/go/internal/str"
)
var CmdRun = &base.Command{
--- /dev/null
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package str provides string manipulation utilities.
+package str
+
+import (
+ "bytes"
+ "fmt"
+ "unicode"
+ "unicode/utf8"
+)
+
+// StringList flattens its arguments into a single []string.
+// Each argument in args must have type string or []string.
+func StringList(args ...interface{}) []string {
+ var x []string
+ for _, arg := range args {
+ switch arg := arg.(type) {
+ case []string:
+ x = append(x, arg...)
+ case string:
+ x = append(x, arg)
+ default:
+ panic("stringList: invalid argument of type " + fmt.Sprintf("%T", arg))
+ }
+ }
+ return x
+}
+
+// ToFold returns a string with the property that
+// strings.EqualFold(s, t) iff ToFold(s) == ToFold(t)
+// This lets us test a large set of strings for fold-equivalent
+// duplicates without making a quadratic number of calls
+// to EqualFold. Note that strings.ToUpper and strings.ToLower
+// do not have the desired property in some corner cases.
+func ToFold(s string) string {
+ // Fast path: all ASCII, no upper case.
+ // Most paths look like this already.
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ if c >= utf8.RuneSelf || 'A' <= c && c <= 'Z' {
+ goto Slow
+ }
+ }
+ return s
+
+Slow:
+ var buf bytes.Buffer
+ for _, r := range s {
+ // SimpleFold(x) cycles to the next equivalent rune > x
+ // or wraps around to smaller values. Iterate until it wraps,
+ // and we've found the minimum value.
+ for {
+ r0 := r
+ r = unicode.SimpleFold(r0)
+ if r <= r0 {
+ break
+ }
+ }
+ // Exception to allow fast path above: A-Z => a-z
+ if 'A' <= r && r <= 'Z' {
+ r += 'a' - 'A'
+ }
+ buf.WriteRune(r)
+ }
+ return buf.String()
+}
+
+// FoldDup reports a pair of strings from the list that are
+// equal according to strings.EqualFold.
+// It returns "", "" if there are no such strings.
+func FoldDup(list []string) (string, string) {
+ clash := map[string]string{}
+ for _, s := range list {
+ fold := ToFold(s)
+ if t := clash[fold]; t != "" {
+ if s > t {
+ s, t = t, s
+ }
+ return s, t
+ }
+ clash[fold] = s
+ }
+ return "", ""
+}
+
+// Contains reports whether x contains s.
+func Contains(x []string, s string) bool {
+ for _, t := range x {
+ if t == s {
+ return true
+ }
+ }
+ return false
+}
+
+// Uniq removes consecutive duplicate strings from ss.
+func Uniq(ss *[]string) {
+ if len(*ss) <= 1 {
+ return
+ }
+ uniq := (*ss)[:1]
+ for _, s := range *ss {
+ if s != uniq[len(uniq)-1] {
+ uniq = append(uniq, s)
+ }
+ }
+ *ss = uniq
+}
--- /dev/null
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package str
+
+import (
+ "testing"
+)
+
+var foldDupTests = []struct {
+ list []string
+ f1, f2 string
+}{
+ {StringList("math/rand", "math/big"), "", ""},
+ {StringList("math", "strings"), "", ""},
+ {StringList("strings"), "", ""},
+ {StringList("strings", "strings"), "strings", "strings"},
+ {StringList("Rand", "rand", "math", "math/rand", "math/Rand"), "Rand", "rand"},
+}
+
+func TestFoldDup(t *testing.T) {
+ for _, tt := range foldDupTests {
+ f1, f2 := FoldDup(tt.list)
+ if f1 != tt.f1 || f2 != tt.f2 {
+ t.Errorf("foldDup(%q) = %q, %q, want %q, %q", tt.list, f1, f2, tt.f1, tt.f2)
+ }
+ }
+}
"cmd/go/internal/search"
"cmd/go/internal/trace"
"cmd/go/internal/work"
- "cmd/internal/str"
+ "cmd/go/internal/str"
"cmd/internal/test2json"
)
"cmd/go/internal/cfg"
"cmd/go/internal/search"
"cmd/go/internal/web"
- "cmd/internal/str"
+ "cmd/go/internal/str"
"golang.org/x/mod/module"
)
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/internal/buildid"
- "cmd/internal/str"
+ "cmd/go/internal/str"
)
// Build IDs
"cmd/go/internal/fsys"
"cmd/go/internal/load"
"cmd/go/internal/modload"
+ "cmd/go/internal/str"
"cmd/go/internal/trace"
- "cmd/internal/str"
+ "cmd/internal/quoted"
"cmd/internal/sys"
)
if v == "" {
v = def
}
- args, err := str.SplitQuotedFields(v)
+ args, err := quoted.Split(v)
if err != nil {
panic(fmt.Sprintf("could not parse environment variable %s with value %q: %v", key, v, err))
}
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/go/internal/load"
+ "cmd/go/internal/str"
"cmd/internal/objabi"
- "cmd/internal/str"
+ "cmd/internal/quoted"
"cmd/internal/sys"
"crypto/sha1"
)
return ldflags, nil
}
}
- joined, err := str.JoinAndQuoteFields(compiler)
+ joined, err := quoted.Join(compiler)
if err != nil {
return nil, err
}
"cmd/go/internal/fsys"
"cmd/go/internal/load"
"cmd/internal/pkgpath"
- "cmd/internal/str"
+ "cmd/go/internal/str"
)
// The Gccgo toolchain.
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/go/internal/modload"
- "cmd/internal/str"
+ "cmd/internal/quoted"
"cmd/internal/sys"
"fmt"
"os"
// Make sure CC, CXX, and FC are absolute paths.
for _, key := range []string{"CC", "CXX", "FC"} {
value := cfg.Getenv(key)
- args, err := str.SplitQuotedFields(value)
+ args, err := quoted.Split(value)
if err != nil {
base.Fatalf("go: %s environment variable could not be parsed: %v", key, err)
}
--- /dev/null
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package quoted provides string manipulation utilities.
+package quoted
+
+import (
+ "flag"
+ "fmt"
+ "strings"
+ "unicode"
+)
+
+func isSpaceByte(c byte) bool {
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r'
+}
+
+// Split splits s into a list of fields,
+// allowing single or double quotes around elements.
+// There is no unescaping or other processing within
+// quoted fields.
+func Split(s string) ([]string, error) {
+ // Split fields allowing '' or "" around elements.
+ // Quotes further inside the string do not count.
+ var f []string
+ for len(s) > 0 {
+ for len(s) > 0 && isSpaceByte(s[0]) {
+ s = s[1:]
+ }
+ if len(s) == 0 {
+ break
+ }
+ // Accepted quoted string. No unescaping inside.
+ if s[0] == '"' || s[0] == '\'' {
+ quote := s[0]
+ s = s[1:]
+ i := 0
+ for i < len(s) && s[i] != quote {
+ i++
+ }
+ if i >= len(s) {
+ return nil, fmt.Errorf("unterminated %c string", quote)
+ }
+ f = append(f, s[:i])
+ s = s[i+1:]
+ continue
+ }
+ i := 0
+ for i < len(s) && !isSpaceByte(s[i]) {
+ i++
+ }
+ f = append(f, s[:i])
+ s = s[i:]
+ }
+ return f, nil
+}
+
+// Join joins a list of arguments into a string that can be parsed
+// with Split. Arguments are quoted only if necessary; arguments
+// without spaces or quotes are kept as-is. No argument may contain both
+// single and double quotes.
+func Join(args []string) (string, error) {
+ var buf []byte
+ for i, arg := range args {
+ if i > 0 {
+ buf = append(buf, ' ')
+ }
+ var sawSpace, sawSingleQuote, sawDoubleQuote bool
+ for _, c := range arg {
+ switch {
+ case c > unicode.MaxASCII:
+ continue
+ case isSpaceByte(byte(c)):
+ sawSpace = true
+ case c == '\'':
+ sawSingleQuote = true
+ case c == '"':
+ sawDoubleQuote = true
+ }
+ }
+ switch {
+ case !sawSpace && !sawSingleQuote && !sawDoubleQuote:
+ buf = append(buf, []byte(arg)...)
+
+ case !sawSingleQuote:
+ buf = append(buf, '\'')
+ buf = append(buf, []byte(arg)...)
+ buf = append(buf, '\'')
+
+ case !sawDoubleQuote:
+ buf = append(buf, '"')
+ buf = append(buf, []byte(arg)...)
+ buf = append(buf, '"')
+
+ default:
+ return "", fmt.Errorf("argument %q contains both single and double quotes and cannot be quoted", arg)
+ }
+ }
+ return string(buf), nil
+}
+
+// A Flag parses a list of string arguments encoded with Join.
+// It is useful for flags like cmd/link's -extldflags.
+type Flag []string
+
+var _ flag.Value = (*Flag)(nil)
+
+func (f *Flag) Set(v string) error {
+ fs, err := Split(v)
+ if err != nil {
+ return err
+ }
+ *f = fs[:len(fs):len(fs)]
+ return nil
+}
+
+func (f *Flag) String() string {
+ if f == nil {
+ return ""
+ }
+ s, err := Join(*f)
+ if err != nil {
+ return strings.Join(*f, " ")
+ }
+ return s
+}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package str
+package quoted
import (
"reflect"
"testing"
)
-var foldDupTests = []struct {
- list []string
- f1, f2 string
-}{
- {StringList("math/rand", "math/big"), "", ""},
- {StringList("math", "strings"), "", ""},
- {StringList("strings"), "", ""},
- {StringList("strings", "strings"), "strings", "strings"},
- {StringList("Rand", "rand", "math", "math/rand", "math/Rand"), "Rand", "rand"},
-}
-
-func TestFoldDup(t *testing.T) {
- for _, tt := range foldDupTests {
- f1, f2 := FoldDup(tt.list)
- if f1 != tt.f1 || f2 != tt.f2 {
- t.Errorf("foldDup(%q) = %q, %q, want %q, %q", tt.list, f1, f2, tt.f1, tt.f2)
- }
- }
-}
-
-func TestSplitQuotedFields(t *testing.T) {
+func TestSplit(t *testing.T) {
for _, test := range []struct {
name string
value string
{name: "quote_unclosed", value: `'a`, wantErr: "unterminated ' string"},
} {
t.Run(test.name, func(t *testing.T) {
- got, err := SplitQuotedFields(test.value)
+ got, err := Split(test.value)
if err != nil {
if test.wantErr == "" {
t.Fatalf("unexpected error: %v", err)
}
}
-func TestJoinAndQuoteFields(t *testing.T) {
+func TestJoin(t *testing.T) {
for _, test := range []struct {
name string
args []string
{name: "unquoteable", args: []string{`'"`}, wantErr: "contains both single and double quotes and cannot be quoted"},
} {
t.Run(test.name, func(t *testing.T) {
- got, err := JoinAndQuoteFields(test.args)
+ got, err := Join(test.args)
if err != nil {
if test.wantErr == "" {
t.Fatalf("unexpected error: %v", err)
+++ /dev/null
-// Copyright 2017 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package str provides string manipulation utilities.
-package str
-
-import (
- "bytes"
- "flag"
- "fmt"
- "strings"
- "unicode"
- "unicode/utf8"
-)
-
-// StringList flattens its arguments into a single []string.
-// Each argument in args must have type string or []string.
-func StringList(args ...interface{}) []string {
- var x []string
- for _, arg := range args {
- switch arg := arg.(type) {
- case []string:
- x = append(x, arg...)
- case string:
- x = append(x, arg)
- default:
- panic("stringList: invalid argument of type " + fmt.Sprintf("%T", arg))
- }
- }
- return x
-}
-
-// ToFold returns a string with the property that
-// strings.EqualFold(s, t) iff ToFold(s) == ToFold(t)
-// This lets us test a large set of strings for fold-equivalent
-// duplicates without making a quadratic number of calls
-// to EqualFold. Note that strings.ToUpper and strings.ToLower
-// do not have the desired property in some corner cases.
-func ToFold(s string) string {
- // Fast path: all ASCII, no upper case.
- // Most paths look like this already.
- for i := 0; i < len(s); i++ {
- c := s[i]
- if c >= utf8.RuneSelf || 'A' <= c && c <= 'Z' {
- goto Slow
- }
- }
- return s
-
-Slow:
- var buf bytes.Buffer
- for _, r := range s {
- // SimpleFold(x) cycles to the next equivalent rune > x
- // or wraps around to smaller values. Iterate until it wraps,
- // and we've found the minimum value.
- for {
- r0 := r
- r = unicode.SimpleFold(r0)
- if r <= r0 {
- break
- }
- }
- // Exception to allow fast path above: A-Z => a-z
- if 'A' <= r && r <= 'Z' {
- r += 'a' - 'A'
- }
- buf.WriteRune(r)
- }
- return buf.String()
-}
-
-// FoldDup reports a pair of strings from the list that are
-// equal according to strings.EqualFold.
-// It returns "", "" if there are no such strings.
-func FoldDup(list []string) (string, string) {
- clash := map[string]string{}
- for _, s := range list {
- fold := ToFold(s)
- if t := clash[fold]; t != "" {
- if s > t {
- s, t = t, s
- }
- return s, t
- }
- clash[fold] = s
- }
- return "", ""
-}
-
-// Contains reports whether x contains s.
-func Contains(x []string, s string) bool {
- for _, t := range x {
- if t == s {
- return true
- }
- }
- return false
-}
-
-// Uniq removes consecutive duplicate strings from ss.
-func Uniq(ss *[]string) {
- if len(*ss) <= 1 {
- return
- }
- uniq := (*ss)[:1]
- for _, s := range *ss {
- if s != uniq[len(uniq)-1] {
- uniq = append(uniq, s)
- }
- }
- *ss = uniq
-}
-
-func isSpaceByte(c byte) bool {
- return c == ' ' || c == '\t' || c == '\n' || c == '\r'
-}
-
-// SplitQuotedFields splits s into a list of fields,
-// allowing single or double quotes around elements.
-// There is no unescaping or other processing within
-// quoted fields.
-func SplitQuotedFields(s string) ([]string, error) {
- // Split fields allowing '' or "" around elements.
- // Quotes further inside the string do not count.
- var f []string
- for len(s) > 0 {
- for len(s) > 0 && isSpaceByte(s[0]) {
- s = s[1:]
- }
- if len(s) == 0 {
- break
- }
- // Accepted quoted string. No unescaping inside.
- if s[0] == '"' || s[0] == '\'' {
- quote := s[0]
- s = s[1:]
- i := 0
- for i < len(s) && s[i] != quote {
- i++
- }
- if i >= len(s) {
- return nil, fmt.Errorf("unterminated %c string", quote)
- }
- f = append(f, s[:i])
- s = s[i+1:]
- continue
- }
- i := 0
- for i < len(s) && !isSpaceByte(s[i]) {
- i++
- }
- f = append(f, s[:i])
- s = s[i:]
- }
- return f, nil
-}
-
-// JoinAndQuoteFields joins a list of arguments into a string that can be parsed
-// with SplitQuotedFields. Arguments are quoted only if necessary; arguments
-// without spaces or quotes are kept as-is. No argument may contain both
-// single and double quotes.
-func JoinAndQuoteFields(args []string) (string, error) {
- var buf []byte
- for i, arg := range args {
- if i > 0 {
- buf = append(buf, ' ')
- }
- var sawSpace, sawSingleQuote, sawDoubleQuote bool
- for _, c := range arg {
- switch {
- case c > unicode.MaxASCII:
- continue
- case isSpaceByte(byte(c)):
- sawSpace = true
- case c == '\'':
- sawSingleQuote = true
- case c == '"':
- sawDoubleQuote = true
- }
- }
- switch {
- case !sawSpace && !sawSingleQuote && !sawDoubleQuote:
- buf = append(buf, []byte(arg)...)
-
- case !sawSingleQuote:
- buf = append(buf, '\'')
- buf = append(buf, []byte(arg)...)
- buf = append(buf, '\'')
-
- case !sawDoubleQuote:
- buf = append(buf, '"')
- buf = append(buf, []byte(arg)...)
- buf = append(buf, '"')
-
- default:
- return "", fmt.Errorf("argument %q contains both single and double quotes and cannot be quoted", arg)
- }
- }
- return string(buf), nil
-}
-
-// A QuotedStringListFlag parses a list of string arguments encoded with
-// JoinAndQuoteFields. It is useful for flags like cmd/link's -extldflags.
-type QuotedStringListFlag []string
-
-var _ flag.Value = (*QuotedStringListFlag)(nil)
-
-func (f *QuotedStringListFlag) Set(v string) error {
- fs, err := SplitQuotedFields(v)
- if err != nil {
- return err
- }
- *f = fs[:len(fs):len(fs)]
- return nil
-}
-
-func (f *QuotedStringListFlag) String() string {
- if f == nil {
- return ""
- }
- s, err := JoinAndQuoteFields(*f)
- if err != nil {
- return strings.Join(*f, " ")
- }
- return s
-}
"bytes"
cmddwarf "cmd/internal/dwarf"
"cmd/internal/objfile"
- "cmd/internal/str"
+ "cmd/internal/quoted"
"debug/dwarf"
"internal/testenv"
"os"
if extld == "" {
extld = "gcc"
}
- extldArgs, err := str.SplitQuotedFields(extld)
+ extldArgs, err := quoted.Split(extld)
if err != nil {
t.Fatal(err)
}
"bufio"
"cmd/internal/goobj"
"cmd/internal/objabi"
- "cmd/internal/str"
+ "cmd/internal/quoted"
"cmd/internal/sys"
"cmd/link/internal/benchmark"
"flag"
flagLibGCC = flag.String("libgcc", "", "compiler support lib for internal linking; use \"none\" to disable")
flagTmpdir = flag.String("tmpdir", "", "use `directory` for temporary files")
- flagExtld str.QuotedStringListFlag
- flagExtldflags str.QuotedStringListFlag
+ flagExtld quoted.Flag
+ flagExtldflags quoted.Flag
flagExtar = flag.String("extar", "", "archive program for buildmode=c-archive")
flagA = flag.Bool("a", false, "no-op (deprecated)")