pulumi/sdk/go/common/tokens/names.go

146 lines
4.4 KiB
Go
Raw Permalink Normal View History

2018-05-22 19:43:36 +00:00
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Begin overhauling semantic phases This change further merges the new AST and MuPack/MuIL formats and abstractions into the core of the compiler. A good amount of the old code is gone now; I decided against ripping it all out in one fell swoop so that I can methodically check that we are preserving all relevant decisions and/or functionality we had in the old model. The changes are too numerous to outline in this commit message, however, here are the noteworthy ones: * Split up the notion of symbols and tokens, resulting in: - pkg/symbols for true compiler symbols (bound nodes) - pkg/tokens for name-based tokens, identifiers, constants * Several packages move underneath pkg/compiler: - pkg/ast becomes pkg/compiler/ast - pkg/errors becomes pkg/compiler/errors - pkg/symbols becomes pkg/compiler/symbols * pkg/ast/... becomes pkg/compiler/legacy/ast/... * pkg/pack/ast becomes pkg/compiler/ast. * pkg/options goes away, merged back into pkg/compiler. * All binding functionality moves underneath a dedicated package, pkg/compiler/binder. The legacy.go file contains cruft that will eventually go away, while the other files represent a halfway point between new and old, but are expected to stay roughly in the current shape. * All parsing functionality is moved underneath a new pkg/compiler/metadata namespace, and we adopt new terminology "metadata reading" since real parsing happens in the MetaMu compilers. Hence, Parser has become metadata.Reader. * In general phases of the compiler no longer share access to the actual compiler.Compiler object. Instead, shared state is moved to the core.Context object underneath pkg/compiler/core. * Dependency resolution during binding has been rewritten to the new model, including stashing bound package symbols in the context object, and detecting import cycles. * Compiler construction does not take a workspace object. Instead, creation of a workspace is entirely hidden inside of the compiler's constructor logic. * There are three Compile* functions on the Compiler interface, to support different styles of invoking compilation: Compile() auto- detects a Mu package, based on the workspace; CompilePath(string) loads the target as a Mu package and compiles it, regardless of the workspace settings; and, CompilePackage(*pack.Package) will compile a pre-loaded package AST, again regardless of workspace. * Delete the _fe, _sema, and parsetree phases. They are no longer relevant and the functionality is largely subsumed by the above. ...and so very much more. I'm surprised I ever got this to compile again!
2017-01-18 20:18:37 +00:00
package tokens
import (
"regexp"
"strings"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)
// Name is an identifier. It conforms to NameRegexpPattern.
type Name string
func (nm Name) String() string { return string(nm) }
// Q turns a Name into a qualified name; this is legal, since Name's is a proper subset of QName's grammar.
func (nm Name) Q() QName { return QName(nm) }
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
var (
NameRegexp = regexp.MustCompile(NameRegexpPattern)
nameFirstCharRegexp = regexp.MustCompile("^" + nameFirstCharRegexpPattern + "$")
nameRestCharRegexp = regexp.MustCompile("^" + nameRestCharRegexpPattern + "$")
)
var NameRegexpPattern = nameFirstCharRegexpPattern + nameRestCharRegexpPattern
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
const (
nameFirstCharRegexpPattern = "[A-Za-z0-9_.-]"
nameRestCharRegexpPattern = "[A-Za-z0-9_.-]*"
)
// IsName checks whether a string is a legal Name.
func IsName(s string) bool {
Revive some compiler tests This change revives some compiler tests that are still lingering around from the old architecture, before our latest round of ship burning. It also fixes up some bugs uncovered during this: * Don't claim that a symbol's kind is incorrect in the binder error message when it wasn't found. Instead, say that it was missing. * Do not attempt to compile if an error was issued during workspace resolution and/or loading of the Mufile. This leads to trying to load an empty path and badness quickly ensues (crash). * Issue an error if the Mufile wasn't found (this got lost apparently). * Rename the ErrorMissingPackageName message to ErrorInvalidPackageName, since missing names are now caught by our new fancy decoder that understands required versus optional fields. We still need to guard against illegal characters in the name, including the empty string "". * During decoding, reject !src.IsValid elements. This represents the zero value and should be treated equivalently to a missing field. * Do not permit empty strings "" as Names or QNames. The old logic accidentally permitted them because regexp.FindString("") == "", no matter the regex! * Move the TestDiagSink abstraction to a new pkg/util/testutil package, allowing us to share this common code across multiple package tests. * Fix up a few messages that needed tidying or to use Infof vs. Info. The binder tests -- deleted in this -- are about to come back, however, I am splitting up the changes, since this represents a passing fixed point.
2017-01-26 23:30:08 +00:00
return s != "" && NameRegexp.FindString(s) == s
}
// QName is a qualified identifier. The "/" character optionally delimits different pieces of the name. Each element
// conforms to NameRegexpPattern. For example, "pulumi/pulumi/stack".
type QName string
func (nm QName) String() string { return string(nm) }
// QNameDelimiter is what delimits Namespace and Name parts.
const QNameDelimiter = "/"
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
var (
QNameRegexp = regexp.MustCompile(QNameRegexpPattern)
QNameRegexpPattern = "(" + NameRegexpPattern + "\\" + QNameDelimiter + ")*" + NameRegexpPattern
)
// IsQName checks whether a string is a legal QName.
func IsQName(s string) bool {
Revive some compiler tests This change revives some compiler tests that are still lingering around from the old architecture, before our latest round of ship burning. It also fixes up some bugs uncovered during this: * Don't claim that a symbol's kind is incorrect in the binder error message when it wasn't found. Instead, say that it was missing. * Do not attempt to compile if an error was issued during workspace resolution and/or loading of the Mufile. This leads to trying to load an empty path and badness quickly ensues (crash). * Issue an error if the Mufile wasn't found (this got lost apparently). * Rename the ErrorMissingPackageName message to ErrorInvalidPackageName, since missing names are now caught by our new fancy decoder that understands required versus optional fields. We still need to guard against illegal characters in the name, including the empty string "". * During decoding, reject !src.IsValid elements. This represents the zero value and should be treated equivalently to a missing field. * Do not permit empty strings "" as Names or QNames. The old logic accidentally permitted them because regexp.FindString("") == "", no matter the regex! * Move the TestDiagSink abstraction to a new pkg/util/testutil package, allowing us to share this common code across multiple package tests. * Fix up a few messages that needed tidying or to use Infof vs. Info. The binder tests -- deleted in this -- are about to come back, however, I am splitting up the changes, since this represents a passing fixed point.
2017-01-26 23:30:08 +00:00
return s != "" && QNameRegexp.FindString(s) == s
}
// IntoQName converts an arbitrary string into a QName, converting the string to a valid QName if
// necessary. The conversion is deterministic, but also lossy.
func IntoQName(s string) QName {
output := []string{}
for _, s := range strings.Split(s, QNameDelimiter) {
if s == "" {
continue
}
segment := []byte(s)
if !nameFirstCharRegexp.Match([]byte{segment[0]}) {
segment[0] = '_'
}
for i := 1; i < len(s); i++ {
if !nameRestCharRegexp.Match([]byte{segment[i]}) {
segment[i] = '_'
}
}
output = append(output, string(segment))
}
result := strings.Join(output, QNameDelimiter)
if result == "" {
result = "_"
}
return QName(result)
}
// Name extracts the Name portion of a QName (dropping any namespace).
func (nm QName) Name() Name {
ix := strings.LastIndex(string(nm), QNameDelimiter)
var nmn string
if ix == -1 {
nmn = string(nm)
} else {
nmn = string(nm[ix+1:])
}
contract.Assertf(IsName(nmn), "QName %q has invalid name %q", nm, nmn)
return Name(nmn)
}
// Namespace extracts the namespace portion of a QName (dropping the name); this may be empty.
func (nm QName) Namespace() QName {
ix := strings.LastIndex(string(nm), QNameDelimiter)
var qn string
if ix == -1 {
qn = ""
} else {
qn = string(nm[:ix])
}
contract.Assertf(IsQName(qn), "QName %q has invalid namespace %q", nm, qn)
return QName(qn)
}
// PackageName is a qualified name referring to an imported package.
type PackageName QName
func (nm PackageName) String() string { return string(nm) }
// ModuleName is a qualified name referring to an imported module from a package.
type ModuleName QName
func (nm ModuleName) String() string { return string(nm) }
// ModuleMemberName is a simple name representing the module member's identifier.
type ModuleMemberName Name
func (nm ModuleMemberName) String() string { return string(nm) }
// ClassMemberName is a simple name representing the class member's identifier.
type ClassMemberName Name
func (nm ClassMemberName) Name() Name { return Name(nm) }
func (nm ClassMemberName) String() string { return string(nm) }
// TypeName is a simple name representing the type's name, without any package/module qualifiers.
type TypeName Name
func (nm TypeName) String() string { return string(nm) }