pulumi/pkg/codegen/hcl2/syntax/tokens.go

1323 lines
31 KiB
Go
Raw Normal View History

// Copyright 2020-2024, 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.
package syntax
import (
"bytes"
"fmt"
"math/big"
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
"strconv"
"unicode"
"unicode/utf8"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
)
var tokenStrings = map[hclsyntax.TokenType]string{
hclsyntax.TokenOBrace: "{",
hclsyntax.TokenCBrace: "}",
hclsyntax.TokenOBrack: "[",
hclsyntax.TokenCBrack: "]",
hclsyntax.TokenOParen: "(",
hclsyntax.TokenCParen: ")",
hclsyntax.TokenOQuote: `"`,
hclsyntax.TokenCQuote: `"`,
hclsyntax.TokenStar: "*",
hclsyntax.TokenSlash: "/",
hclsyntax.TokenPlus: "+",
hclsyntax.TokenMinus: "-",
hclsyntax.TokenPercent: "%",
hclsyntax.TokenEqual: "=",
hclsyntax.TokenEqualOp: "==",
hclsyntax.TokenNotEqual: "!=",
hclsyntax.TokenLessThan: "<",
hclsyntax.TokenLessThanEq: "<=",
hclsyntax.TokenGreaterThan: ">",
hclsyntax.TokenGreaterThanEq: ">=",
hclsyntax.TokenAnd: "&&",
hclsyntax.TokenOr: "||",
hclsyntax.TokenBang: "!",
hclsyntax.TokenDot: ".",
hclsyntax.TokenComma: ",",
hclsyntax.TokenEllipsis: "...",
hclsyntax.TokenFatArrow: "=>",
hclsyntax.TokenQuestion: "?",
hclsyntax.TokenColon: ":",
hclsyntax.TokenTemplateInterp: "${",
hclsyntax.TokenTemplateControl: "%{",
hclsyntax.TokenTemplateSeqEnd: "}",
hclsyntax.TokenNewline: "\n",
}
// Trivia represents bytes in a source file that are not syntactically meaningful. This includes whitespace and
// comments.
type Trivia interface {
// Range returns the range of the trivia in the source file.
Range() hcl.Range
// Bytes returns the raw bytes that comprise the trivia.
Bytes() []byte
isTrivia()
}
// TriviaList is a list of trivia.
type TriviaList []Trivia
func (trivia TriviaList) LeadingWhitespace() TriviaList {
end := 0
for i, t := range trivia {
if _, ok := t.(Whitespace); !ok {
break
}
end = i
}
if end == 0 {
return nil
}
return append(TriviaList(nil), trivia[0:end]...)
}
func (trivia TriviaList) TrailingWhitespace() TriviaList {
start := len(trivia)
for i := len(trivia) - 1; i >= 0; i-- {
if _, ok := trivia[i].(Whitespace); !ok {
break
}
start = i
}
if start == len(trivia) {
return nil
}
return append(TriviaList(nil), trivia[start:]...)
}
func (trivia TriviaList) CollapseWhitespace() TriviaList {
result := make(TriviaList, 0, len(trivia))
for _, t := range trivia {
if ws, ok := t.(Whitespace); ok {
if len(result) != 0 {
ws.bytes = []byte{' '}
result = append(result, ws)
}
} else {
result = append(result, t)
}
}
return result
}
func (trivia TriviaList) EndsOnNewLine() bool {
for _, trivia := range trivia {
b := trivia.Bytes()
for len(b) > 0 {
r, sz := utf8.DecodeLastRune(b)
if r == '\n' {
return true
}
if !unicode.IsSpace(r) {
return false
}
b = b[:len(b)-sz]
}
}
return false
}
func (trivia TriviaList) Index(sep string) (Trivia, int) {
s := []byte(sep)
for _, trivia := range trivia {
if i := bytes.Index(trivia.Bytes(), s); i != -1 {
return trivia, i
}
}
return nil, -1
}
func (trivia TriviaList) Format(f fmt.State, c rune) {
for _, trivia := range trivia {
_, err := f.Write(trivia.Bytes())
if err != nil {
panic(err)
}
}
}
// Comment is a piece of trivia that represents a line or block comment in a source file.
type Comment struct {
// Lines contains the lines of the comment without leading comment characters or trailing newlines.
Lines []string
rng hcl.Range
bytes []byte
}
// Range returns the range of the comment in the source file.
func (c Comment) Range() hcl.Range {
return c.rng
}
// Bytes returns the raw bytes that comprise the comment.
func (c Comment) Bytes() []byte {
return c.bytes
}
func (Comment) isTrivia() {}
// Whitespace is a piece of trivia that represents a sequence of whitespace characters in a source file.
type Whitespace struct {
rng hcl.Range
bytes []byte
}
// NewWhitespace returns a new piece of whitespace trivia with the given contents.
func NewWhitespace(bytes ...byte) Whitespace {
return Whitespace{bytes: bytes}
}
// Range returns the range of the whitespace in the source file.
func (w Whitespace) Range() hcl.Range {
return w.rng
}
// Bytes returns the raw bytes that comprise the whitespace.
func (w Whitespace) Bytes() []byte {
return w.bytes
}
func (Whitespace) isTrivia() {}
// TemplateDelimiter is a piece of trivia that represents a token that demarcates an interpolation or control sequence
// inside of a template.
type TemplateDelimiter struct {
// Type is the type of the delimiter (e.g. hclsyntax.TokenTemplateInterp)
Type hclsyntax.TokenType
rng hcl.Range
bytes []byte
}
// NewTemplateDelimiter creates a new TemplateDelimiter value with the given delimiter type. If the token type is not a
// template delimiter, this function will panic.
func NewTemplateDelimiter(typ hclsyntax.TokenType) TemplateDelimiter {
var s string
turn on the golangci-lint exhaustive linter (#15028) Turn on the golangci-lint exhaustive linter. This is the first step towards catching more missing cases during development rather than in tests, or in production. This might be best reviewed commit-by-commit, as the first commit turns on the linter with the `default-signifies-exhaustive: true` option set, which requires a lot less changes in the current codebase. I think it's probably worth doing the second commit as well, as that will get us the real benefits, even though we end up with a little bit more churn. However it means all the `switch` statements are covered, which isn't the case after the first commit, since we do have a lot of `default` statements that just call `assert.Fail`. Fixes #14601 ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2024-01-17 16:50:41 +00:00
//nolint:exhaustive // Only some tokens are template delimiters.
switch typ {
case hclsyntax.TokenTemplateInterp:
s = "${"
case hclsyntax.TokenTemplateControl:
s = "%{"
case hclsyntax.TokenTemplateSeqEnd:
s = "}"
default:
panic(fmt.Errorf("%v is not a template delimiter", typ))
}
return TemplateDelimiter{
Type: typ,
bytes: []byte(s),
}
}
// Range returns the range of the delimiter in the source file.
func (t TemplateDelimiter) Range() hcl.Range {
return t.rng
}
// Bytes returns the raw bytes that comprise the delimiter.
func (t TemplateDelimiter) Bytes() []byte {
return t.bytes
}
func (TemplateDelimiter) isTrivia() {}
// Token represents an HCL2 syntax token with attached leading trivia.
type Token struct {
Raw hclsyntax.Token
LeadingTrivia TriviaList
TrailingTrivia TriviaList
}
func (t Token) Format(f fmt.State, c rune) {
if t.LeadingTrivia != nil {
t.LeadingTrivia.Format(f, c)
} else if f.Flag(' ') {
if _, err := f.Write([]byte{' '}); err != nil {
panic(err)
}
}
bytes := t.Raw.Bytes
if str, ok := tokenStrings[t.Raw.Type]; ok {
bytes = []byte(str)
}
if _, err := f.Write(bytes); err != nil {
panic(err)
}
t.TrailingTrivia.Format(f, c)
}
func (t Token) AllTrivia() TriviaList {
result := make(TriviaList, len(t.LeadingTrivia)+len(t.TrailingTrivia))
copy(result, t.LeadingTrivia)
copy(result[len(t.LeadingTrivia):], t.TrailingTrivia)
return result
}
// Range returns the total range covered by this token and any leading trivia.
func (t Token) Range() hcl.Range {
start := t.Raw.Range.Start
if len(t.LeadingTrivia) > 0 {
start = t.LeadingTrivia[0].Range().Start
}
end := t.Raw.Range.End
if len(t.TrailingTrivia) > 0 {
end = t.TrailingTrivia[len(t.TrailingTrivia)-1].Range().End
}
return hcl.Range{Filename: t.Raw.Range.Filename, Start: start, End: end}
}
func (t Token) withIdent(s string) Token {
if string(t.Raw.Bytes) == s {
return t
}
t.Raw.Bytes = []byte(s)
return t
}
func (t Token) withOperation(operation *hclsyntax.Operation) Token {
typ := OperationTokenType(operation)
if t.Raw.Type == typ {
return t
}
t.Raw.Type = typ
return t
}
func OperationTokenType(operation *hclsyntax.Operation) hclsyntax.TokenType {
switch operation {
case hclsyntax.OpAdd:
return hclsyntax.TokenPlus
case hclsyntax.OpDivide:
return hclsyntax.TokenSlash
case hclsyntax.OpEqual:
return hclsyntax.TokenEqualOp
case hclsyntax.OpGreaterThan:
return hclsyntax.TokenGreaterThan
case hclsyntax.OpGreaterThanOrEqual:
return hclsyntax.TokenGreaterThanEq
case hclsyntax.OpLessThan:
return hclsyntax.TokenLessThan
case hclsyntax.OpLessThanOrEqual:
return hclsyntax.TokenLessThanEq
case hclsyntax.OpLogicalAnd:
return hclsyntax.TokenAnd
case hclsyntax.OpLogicalNot:
return hclsyntax.TokenBang
case hclsyntax.OpLogicalOr:
return hclsyntax.TokenOr
case hclsyntax.OpModulo:
return hclsyntax.TokenPercent
case hclsyntax.OpMultiply:
return hclsyntax.TokenStar
case hclsyntax.OpNegate:
return hclsyntax.TokenMinus
case hclsyntax.OpNotEqual:
return hclsyntax.TokenNotEqual
case hclsyntax.OpSubtract:
return hclsyntax.TokenMinus
}
return hclsyntax.TokenInvalid
}
func newRawToken(typ hclsyntax.TokenType, text ...string) hclsyntax.Token {
bytes := []byte(tokenStrings[typ])
if len(text) != 0 {
bytes = []byte(text[0])
}
return hclsyntax.Token{
Type: typ,
Bytes: bytes,
}
}
// NodeTokens is a closed interface that is used to represent arbitrary *Tokens types in this package.
type NodeTokens interface {
isNodeTokens()
}
// Parentheses records enclosing parenthesis tokens for expressions.
type Parentheses struct {
Open []Token
Close []Token
}
func (parens Parentheses) Any() bool {
return len(parens.Open) > 0
}
func (parens Parentheses) GetLeadingTrivia() TriviaList {
if !parens.Any() {
return nil
}
return parens.Open[0].LeadingTrivia
}
func (parens Parentheses) SetLeadingTrivia(trivia TriviaList) {
if parens.Any() {
parens.Open[0].LeadingTrivia = trivia
}
}
func (parens Parentheses) GetTrailingTrivia() TriviaList {
if !parens.Any() {
return nil
}
return parens.Close[0].TrailingTrivia
}
func (parens Parentheses) SetTrailingTrivia(trivia TriviaList) {
if parens.Any() {
parens.Close[0].TrailingTrivia = trivia
}
}
func (parens Parentheses) Format(f fmt.State, c rune) {
switch c {
case '(':
for i := len(parens.Open) - 1; i >= 0; i-- {
if _, err := fmt.Fprintf(f, "%v", parens.Open[i]); err != nil {
panic(err)
}
}
case ')':
for _, p := range parens.Close {
if _, err := fmt.Fprintf(f, "%v", p); err != nil {
panic(err)
}
}
default:
if _, err := fmt.Fprintf(f, "%v%v", parens.Open, parens.Close); err != nil {
panic(err)
}
}
}
func exprRange(filename string, parens Parentheses, start, end hcl.Pos) hcl.Range {
if parens.Any() {
start = parens.Open[len(parens.Open)-1].Range().Start
end = parens.Close[len(parens.Close)-1].Range().End
}
return hcl.Range{
Filename: filename,
Start: start,
End: end,
}
}
// AttributeTokens records the tokens associated with an *hclsyntax.Attribute.
type AttributeTokens struct {
Name Token
Equals Token
}
func NewAttributeTokens(name string) *AttributeTokens {
var t *AttributeTokens
return &AttributeTokens{
Name: t.GetName(name),
Equals: t.GetEquals(),
}
}
func (t *AttributeTokens) GetName(name string) Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenIdent, name)}
}
return t.Name.withIdent(name)
}
func (t *AttributeTokens) GetEquals() Token {
if t == nil {
return Token{
Raw: newRawToken(hclsyntax.TokenEqual),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
}
}
return t.Equals
}
func (*AttributeTokens) isNodeTokens() {}
// BinaryOpTokens records the tokens associated with an *hclsyntax.BinaryOpExpr.
type BinaryOpTokens struct {
Parentheses Parentheses
Operator Token
}
func NewBinaryOpTokens(operation *hclsyntax.Operation) *BinaryOpTokens {
var t *BinaryOpTokens
return &BinaryOpTokens{Operator: t.GetOperator(operation)}
}
func (t *BinaryOpTokens) GetParentheses() Parentheses {
if t == nil {
return Parentheses{}
}
return t.Parentheses
}
func (t *BinaryOpTokens) GetOperator(operation *hclsyntax.Operation) Token {
if t == nil {
return Token{
Raw: newRawToken(OperationTokenType(operation)),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
}
}
return t.Operator.withOperation(operation)
}
func (*BinaryOpTokens) isNodeTokens() {}
// BlockTokens records the tokens associated with an *hclsyntax.Block.
type BlockTokens struct {
Type Token
Labels []Token
OpenBrace Token
CloseBrace Token
}
func NewBlockTokens(typ string, labels ...string) *BlockTokens {
var t *BlockTokens
return &BlockTokens{
Type: t.GetType(typ),
Labels: t.GetLabels(labels),
OpenBrace: t.GetOpenBrace(),
CloseBrace: t.GetCloseBrace(),
}
}
func (t *BlockTokens) GetType(typ string) Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenIdent, typ)}
}
return t.Type.withIdent(typ)
}
func (t *BlockTokens) GetLabels(labels []string) []Token {
if t == nil {
labelTokens := make([]Token, len(labels))
for i, l := range labels {
var raw hclsyntax.Token
if hclsyntax.ValidIdentifier(l) {
raw = newRawToken(hclsyntax.TokenIdent, l)
} else {
raw = newRawToken(hclsyntax.TokenQuotedLit, fmt.Sprintf("%q", l))
}
labelTokens[i] = Token{
Raw: raw,
LeadingTrivia: TriviaList{NewWhitespace(' ')},
}
}
return labelTokens
}
return t.Labels
}
func (t *BlockTokens) GetOpenBrace() Token {
if t == nil {
return Token{
Raw: newRawToken(hclsyntax.TokenOBrace),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
TrailingTrivia: TriviaList{NewWhitespace('\n')},
}
}
return t.OpenBrace
}
func (t *BlockTokens) GetCloseBrace() Token {
if t == nil {
return Token{
Raw: newRawToken(hclsyntax.TokenCBrace),
LeadingTrivia: TriviaList{NewWhitespace('\n')},
TrailingTrivia: TriviaList{NewWhitespace('\n')},
}
}
return t.CloseBrace
}
func (*BlockTokens) isNodeTokens() {}
// BodyTokens records the tokens associated with an *hclsyntax.Body.
type BodyTokens struct {
EndOfFile *Token
}
func (t *BodyTokens) GetEndOfFile() *Token {
if t == nil {
return nil
}
return t.EndOfFile
}
func (*BodyTokens) isNodeTokens() {}
// ConditionalTokens records the tokens associated with an *hclsyntax.ConditionalExpr of the form "a ? t : f".
type ConditionalTokens struct {
Parentheses Parentheses
QuestionMark Token
Colon Token
}
func NewConditionalTokens() *ConditionalTokens {
return &ConditionalTokens{
QuestionMark: Token{
Raw: newRawToken(hclsyntax.TokenQuestion),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
},
Colon: Token{
Raw: newRawToken(hclsyntax.TokenColon),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
},
}
}
func (*ConditionalTokens) isNodeTokens() {}
// TemplateConditionalTokens records the tokens associated with an *hclsyntax.ConditionalExpr inside a template
// expression.
type TemplateConditionalTokens struct {
OpenIf Token
If Token
CloseIf Token
OpenElse *Token
Else *Token
CloseElse *Token
OpenEndif Token
Endif Token
CloseEndif Token
}
func NewTemplateConditionalTokens(hasElse bool) *TemplateConditionalTokens {
var openElseT, elseT, closeElseT *Token
if hasElse {
openElseT = &Token{Raw: newRawToken(hclsyntax.TokenTemplateControl)}
elseT = &Token{Raw: newRawToken(hclsyntax.TokenIdent, "else")}
closeElseT = &Token{Raw: newRawToken(hclsyntax.TokenTemplateSeqEnd)}
}
return &TemplateConditionalTokens{
OpenIf: Token{Raw: newRawToken(hclsyntax.TokenTemplateControl)},
If: Token{Raw: newRawToken(hclsyntax.TokenIdent, "if")},
CloseIf: Token{Raw: newRawToken(hclsyntax.TokenTemplateSeqEnd)},
OpenElse: openElseT,
Else: elseT,
CloseElse: closeElseT,
OpenEndif: Token{Raw: newRawToken(hclsyntax.TokenTemplateControl)},
Endif: Token{Raw: newRawToken(hclsyntax.TokenIdent, "endif")},
CloseEndif: Token{Raw: newRawToken(hclsyntax.TokenTemplateSeqEnd)},
}
}
func (*TemplateConditionalTokens) isNodeTokens() {}
// ForTokens records the tokens associated with an *hclsyntax.ForExpr.
type ForTokens struct {
Parentheses Parentheses
Open Token
For Token
Key *Token
Comma *Token
Value Token
In Token
Colon Token
Arrow *Token
Group *Token
If *Token
Close Token
}
func NewForTokens(keyVariable, valueVariable string, mapFor, group, conditional bool) *ForTokens {
var keyT, commaT, arrowT, groupT, ifT *Token
if keyVariable != "" {
keyT = &Token{
Raw: newRawToken(hclsyntax.TokenIdent, keyVariable),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
}
commaT = &Token{Raw: newRawToken(hclsyntax.TokenComma)}
}
if mapFor {
arrowT = &Token{
Raw: newRawToken(hclsyntax.TokenFatArrow),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
}
}
if group {
groupT = &Token{Raw: newRawToken(hclsyntax.TokenEllipsis)}
}
if conditional {
ifT = &Token{
Raw: newRawToken(hclsyntax.TokenIdent, "if"),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
}
}
return &ForTokens{
Open: Token{Raw: newRawToken(hclsyntax.TokenOBrack)},
For: Token{Raw: newRawToken(hclsyntax.TokenIdent, "for")},
Key: keyT,
Comma: commaT,
Value: Token{
Raw: newRawToken(hclsyntax.TokenIdent, valueVariable),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
},
In: Token{
Raw: newRawToken(hclsyntax.TokenIdent, "in"),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
},
Colon: Token{Raw: newRawToken(hclsyntax.TokenColon)},
Arrow: arrowT,
Group: groupT,
If: ifT,
Close: Token{Raw: newRawToken(hclsyntax.TokenCBrack)},
}
}
func (*ForTokens) isNodeTokens() {}
// TemplateForTokens records the tokens associated with an *hclsyntax.ForExpr inside a template.
type TemplateForTokens struct {
OpenFor Token
For Token
Key *Token
Comma *Token
Value Token
In Token
CloseFor Token
OpenEndfor Token
Endfor Token
CloseEndfor Token
}
func NewTemplateForTokens(keyVariable, valueVariable string) *TemplateForTokens {
var keyT, commaT *Token
if keyVariable != "" {
keyT = &Token{
Raw: newRawToken(hclsyntax.TokenIdent, keyVariable),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
}
commaT = &Token{Raw: newRawToken(hclsyntax.TokenComma)}
}
return &TemplateForTokens{
OpenFor: Token{Raw: newRawToken(hclsyntax.TokenTemplateControl)},
For: Token{Raw: newRawToken(hclsyntax.TokenIdent, "for")},
Key: keyT,
Comma: commaT,
Value: Token{
Raw: newRawToken(hclsyntax.TokenIdent, valueVariable),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
},
In: Token{
Raw: newRawToken(hclsyntax.TokenIdent, "in"),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
},
CloseFor: Token{Raw: newRawToken(hclsyntax.TokenTemplateSeqEnd)},
OpenEndfor: Token{Raw: newRawToken(hclsyntax.TokenTemplateControl)},
Endfor: Token{Raw: newRawToken(hclsyntax.TokenIdent, "endfor")},
CloseEndfor: Token{Raw: newRawToken(hclsyntax.TokenTemplateSeqEnd)},
}
}
func (*TemplateForTokens) isNodeTokens() {}
// FunctionCallTokens records the tokens associated with an *hclsyntax.FunctionCallExpr.
type FunctionCallTokens struct {
Parentheses Parentheses
Name Token
OpenParen Token
Commas []Token
CloseParen Token
}
func NewFunctionCallTokens(name string, argCount int) *FunctionCallTokens {
var t *FunctionCallTokens
return &FunctionCallTokens{
Name: t.GetName(name),
OpenParen: t.GetOpenParen(),
Commas: t.GetCommas(argCount),
CloseParen: t.GetCloseParen(),
}
}
func (t *FunctionCallTokens) GetParentheses() Parentheses {
if t == nil {
return Parentheses{}
}
return t.Parentheses
}
func (t *FunctionCallTokens) GetName(name string) Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenIdent, name)}
}
return t.Name.withIdent(name)
}
func (t *FunctionCallTokens) GetOpenParen() Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenOParen)}
}
return t.OpenParen
}
func (t *FunctionCallTokens) GetCommas(argCount int) []Token {
if t == nil {
if argCount == 0 {
return nil
}
commas := make([]Token, argCount-1)
for i := 0; i < len(commas); i++ {
commas[i] = Token{Raw: newRawToken(hclsyntax.TokenComma)}
}
return commas
}
return t.Commas
}
func (t *FunctionCallTokens) GetCloseParen() Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenCParen)}
}
return t.CloseParen
}
func (*FunctionCallTokens) isNodeTokens() {}
// IndexTokens records the tokens associated with an *hclsyntax.IndexExpr.
type IndexTokens struct {
Parentheses Parentheses
OpenBracket Token
CloseBracket Token
}
func NewIndexTokens() *IndexTokens {
var t *IndexTokens
return &IndexTokens{
OpenBracket: t.GetOpenBracket(),
CloseBracket: t.GetCloseBracket(),
}
}
func (t *IndexTokens) GetParentheses() Parentheses {
if t == nil {
return Parentheses{}
}
return t.Parentheses
}
func (t *IndexTokens) GetOpenBracket() Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenOBrack)}
}
return t.OpenBracket
}
func (t *IndexTokens) GetCloseBracket() Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenCBrack)}
}
return t.CloseBracket
}
func (*IndexTokens) isNodeTokens() {}
// LiteralValueTokens records the tokens associated with an *hclsyntax.LiteralValueExpr.
type LiteralValueTokens struct {
Parentheses Parentheses
Value []Token
}
func rawLiteralValueToken(value cty.Value) hclsyntax.Token {
rawType, rawText := hclsyntax.TokenIdent, ""
switch value.Type() {
case cty.Bool:
rawText = "false"
if value.True() {
rawText = "true"
}
case cty.Number:
rawType = hclsyntax.TokenNumberLit
bf := value.AsBigFloat()
i, acc := bf.Int64()
if acc == big.Exact {
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
rawText = strconv.FormatInt(i, 10)
} else {
d, _ := bf.Float64()
rawText = fmt.Sprintf("%g", d)
}
case cty.String:
rawText = value.AsString()
}
return newRawToken(rawType, rawText)
}
func NewLiteralValueTokens(value cty.Value) *LiteralValueTokens {
var t *LiteralValueTokens
return &LiteralValueTokens{
Value: t.GetValue(value),
}
}
func (t *LiteralValueTokens) GetParentheses() Parentheses {
if t == nil {
return Parentheses{}
}
return t.Parentheses
}
func (t *LiteralValueTokens) GetValue(value cty.Value) []Token {
if t == nil {
return []Token{{Raw: rawLiteralValueToken(value)}}
}
return t.Value
}
func (*LiteralValueTokens) isNodeTokens() {}
// ObjectConsItemTokens records the tokens associated with an hclsyntax.ObjectConsItem.
type ObjectConsItemTokens struct {
Equals Token
Comma *Token
}
func NewObjectConsItemTokens(last bool) ObjectConsItemTokens {
var comma *Token
if !last {
comma = &Token{
Raw: newRawToken(hclsyntax.TokenComma),
TrailingTrivia: TriviaList{NewWhitespace('\n')},
}
}
return ObjectConsItemTokens{
Equals: Token{
Raw: newRawToken(hclsyntax.TokenEqual),
LeadingTrivia: TriviaList{NewWhitespace(' ')},
},
Comma: comma,
}
}
// ObjectConsTokens records the tokens associated with an *hclsyntax.ObjectConsExpr.
type ObjectConsTokens struct {
Parentheses Parentheses
OpenBrace Token
Items []ObjectConsItemTokens
CloseBrace Token
}
func NewObjectConsTokens(itemCount int) *ObjectConsTokens {
var t *ObjectConsTokens
return &ObjectConsTokens{
OpenBrace: t.GetOpenBrace(itemCount),
Items: t.GetItems(itemCount),
CloseBrace: t.GetCloseBrace(),
}
}
func (t *ObjectConsTokens) GetParentheses() Parentheses {
if t == nil {
return Parentheses{}
}
return t.Parentheses
}
func (t *ObjectConsTokens) GetOpenBrace(itemCount int) Token {
if t == nil {
var openBraceTrailingTrivia TriviaList
if itemCount > 0 {
openBraceTrailingTrivia = TriviaList{NewWhitespace('\n')}
}
return Token{
Raw: newRawToken(hclsyntax.TokenOBrace),
TrailingTrivia: openBraceTrailingTrivia,
}
}
return t.OpenBrace
}
func (t *ObjectConsTokens) GetItems(itemCount int) []ObjectConsItemTokens {
if t == nil {
items := make([]ObjectConsItemTokens, itemCount)
for i := 0; i < len(items); i++ {
items[i] = NewObjectConsItemTokens(i == len(items)-1)
}
return items
}
return t.Items
}
func (t *ObjectConsTokens) GetCloseBrace() Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenCBrace)}
}
return t.CloseBrace
}
func (*ObjectConsTokens) isNodeTokens() {}
// TraverserTokens is a closed interface implemented by DotTraverserTokens and BracketTraverserTokens
type TraverserTokens interface {
Range() hcl.Range
isTraverserTokens()
}
func NewTraverserTokens(traverser hcl.Traverser) TraverserTokens {
switch traverser := traverser.(type) {
case hcl.TraverseAttr:
return NewDotTraverserTokens(traverser.Name)
case hcl.TraverseIndex:
return NewBracketTraverserTokens(string(rawLiteralValueToken(traverser.Key).Bytes))
default:
return nil
}
}
// DotTraverserTokens records the tokens associated with dotted traverser (i.e. '.' <attr>).
type DotTraverserTokens struct {
Parentheses Parentheses
Dot Token
Index Token
}
func NewDotTraverserTokens(index string) *DotTraverserTokens {
indexType := hclsyntax.TokenIdent
_, err := cty.ParseNumberVal(index)
if err == nil {
indexType = hclsyntax.TokenNumberLit
}
return &DotTraverserTokens{
Dot: Token{Raw: newRawToken(hclsyntax.TokenDot)},
Index: Token{Raw: newRawToken(indexType, index)},
}
}
func (t *DotTraverserTokens) Range() hcl.Range {
filename := t.Dot.Range().Filename
return exprRange(filename, t.Parentheses, t.Dot.Range().Start, t.Index.Range().End)
}
func (*DotTraverserTokens) isTraverserTokens() {}
// BracketTraverserTokens records the tokens associated with a bracketed traverser (i.e. '[' <index> ']').
type BracketTraverserTokens struct {
Parentheses Parentheses
OpenBracket Token
Index Token
CloseBracket Token
}
func NewBracketTraverserTokens(index string) *BracketTraverserTokens {
indexType := hclsyntax.TokenIdent
_, err := cty.ParseNumberVal(index)
if err == nil {
indexType = hclsyntax.TokenNumberLit
}
return &BracketTraverserTokens{
OpenBracket: Token{Raw: newRawToken(hclsyntax.TokenOBrack)},
Index: Token{Raw: newRawToken(indexType, index)},
CloseBracket: Token{Raw: newRawToken(hclsyntax.TokenCBrack)},
}
}
func (t *BracketTraverserTokens) Range() hcl.Range {
filename := t.OpenBracket.Range().Filename
return exprRange(filename, t.Parentheses, t.OpenBracket.Range().Start, t.CloseBracket.Range().End)
}
func (*BracketTraverserTokens) isTraverserTokens() {}
func newRelativeTraversalTokens(traversal hcl.Traversal) []TraverserTokens {
result := make([]TraverserTokens, len(traversal))
for i, t := range traversal {
result[i] = NewTraverserTokens(t)
}
return result
}
// RelativeTraversalTokens records the tokens associated with an *hclsyntax.RelativeTraversalExpr.
type RelativeTraversalTokens struct {
Parentheses Parentheses
Traversal []TraverserTokens
}
func NewRelativeTraversalTokens(traversal hcl.Traversal) *RelativeTraversalTokens {
return &RelativeTraversalTokens{
Traversal: newRelativeTraversalTokens(traversal),
}
}
func (t *RelativeTraversalTokens) GetParentheses() Parentheses {
if t == nil {
return Parentheses{}
}
return t.Parentheses
}
func (t *RelativeTraversalTokens) GetTraversal(traversal hcl.Traversal) []TraverserTokens {
if t == nil {
return newRelativeTraversalTokens(traversal)
}
return t.Traversal
}
func (*RelativeTraversalTokens) isNodeTokens() {}
// ScopeTraversalTokens records the tokens associated with an *hclsyntax.ScopeTraversalExpr.
type ScopeTraversalTokens struct {
Parentheses Parentheses
Root Token
Traversal []TraverserTokens
}
func NewScopeTraversalTokens(traversal hcl.Traversal) *ScopeTraversalTokens {
var t *ScopeTraversalTokens
return &ScopeTraversalTokens{
Root: t.GetRoot(traversal),
Traversal: t.GetTraversal(traversal),
}
}
func (t *ScopeTraversalTokens) GetParentheses() Parentheses {
if t == nil {
return Parentheses{}
}
return t.Parentheses
}
func (t *ScopeTraversalTokens) GetRoot(traversal hcl.Traversal) Token {
if t == nil {
rootName := traversal[0].(hcl.TraverseRoot).Name
return Token{Raw: newRawToken(hclsyntax.TokenIdent, rootName)}
}
return t.Root
}
func (t *ScopeTraversalTokens) GetTraversal(traversal hcl.Traversal) []TraverserTokens {
if t == nil {
return newRelativeTraversalTokens(traversal[1:])
}
return t.Traversal
}
func (*ScopeTraversalTokens) isNodeTokens() {}
// SplatTokens records the tokens associated with an *hclsyntax.SplatExpr.
type SplatTokens struct {
Parentheses Parentheses
Open Token
Star Token
Close *Token
}
func NewSplatTokens(dotted bool) *SplatTokens {
openType := hclsyntax.TokenDot
var closeT *Token
if !dotted {
openType = hclsyntax.TokenOBrack
closeT = &Token{Raw: newRawToken(hclsyntax.TokenCBrack)}
}
return &SplatTokens{
Open: Token{Raw: newRawToken(openType)},
Star: Token{Raw: newRawToken(hclsyntax.TokenStar)},
Close: closeT,
}
}
func (t *SplatTokens) GetParentheses() Parentheses {
if t == nil {
return Parentheses{}
}
return t.Parentheses
}
func (t *SplatTokens) GetOpen() Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenOBrack)}
}
return t.Open
}
func (t *SplatTokens) GetStar() Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenStar)}
}
return t.Star
}
func (t *SplatTokens) GetClose() *Token {
if t == nil {
return &Token{Raw: newRawToken(hclsyntax.TokenCBrack)}
}
return t.Close
}
func (*SplatTokens) isNodeTokens() {}
// TemplateTokens records the tokens associated with an *hclsyntax.TemplateExpr.
type TemplateTokens struct {
Parentheses Parentheses
Open Token
Close Token
}
func NewTemplateTokens() *TemplateTokens {
var t *TemplateTokens
return &TemplateTokens{
Open: t.GetOpen(),
Close: t.GetClose(),
}
}
func (t *TemplateTokens) GetParentheses() Parentheses {
if t == nil {
return Parentheses{}
}
return t.Parentheses
}
func (t *TemplateTokens) GetOpen() Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenOQuote)}
}
return t.Open
}
func (t *TemplateTokens) GetClose() Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenCQuote)}
}
return t.Close
}
func (*TemplateTokens) isNodeTokens() {}
// TupleConsTokens records the tokens associated with an *hclsyntax.TupleConsExpr.
type TupleConsTokens struct {
Parentheses Parentheses
OpenBracket Token
Commas []Token
CloseBracket Token
}
func NewTupleConsTokens(elementCount int) *TupleConsTokens {
var t *TupleConsTokens
return &TupleConsTokens{
OpenBracket: t.GetOpenBracket(),
Commas: t.GetCommas(elementCount),
CloseBracket: t.GetCloseBracket(),
}
}
func (t *TupleConsTokens) GetParentheses() Parentheses {
if t == nil {
return Parentheses{}
}
return t.Parentheses
}
func (t *TupleConsTokens) GetOpenBracket() Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenOBrack)}
}
return t.OpenBracket
}
func (t *TupleConsTokens) GetCommas(elementCount int) []Token {
if t == nil {
if elementCount == 0 {
return nil
}
commas := make([]Token, elementCount-1)
for i := 0; i < len(commas); i++ {
commas[i] = Token{Raw: newRawToken(hclsyntax.TokenComma)}
}
return commas
}
return t.Commas
}
func (t *TupleConsTokens) GetCloseBracket() Token {
if t == nil {
return Token{Raw: newRawToken(hclsyntax.TokenCBrack)}
}
return t.CloseBracket
}
func (*TupleConsTokens) isNodeTokens() {}
// UnaryOpTokens records the tokens associated with an *hclsyntax.UnaryOpExpr.
type UnaryOpTokens struct {
Parentheses Parentheses
Operator Token
}
func NewUnaryOpTokens(operation *hclsyntax.Operation) *UnaryOpTokens {
var t *UnaryOpTokens
return &UnaryOpTokens{
Operator: t.GetOperator(operation),
}
}
func (t *UnaryOpTokens) GetParentheses() Parentheses {
if t == nil {
return Parentheses{}
}
return t.Parentheses
}
func (t *UnaryOpTokens) GetOperator(operation *hclsyntax.Operation) Token {
if t == nil {
return Token{Raw: newRawToken(OperationTokenType(operation))}
}
return t.Operator
}
func (*UnaryOpTokens) isNodeTokens() {}