Source file src/go/ast/directive_test.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ast
     6  
     7  import (
     8  	"go/token"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  )
    13  
    14  func TestParseDirectiveMatchesIsDirective(t *testing.T) {
    15  	for _, tt := range isDirectiveTests {
    16  		want := tt.ok
    17  		if strings.HasPrefix(tt.in, "extern ") || strings.HasPrefix(tt.in, "export ") {
    18  			// ParseDirective does NOT support extern or export, unlike
    19  			// isDirective.
    20  			want = false
    21  		}
    22  
    23  		if _, ok := ParseDirective(0, "//"+tt.in); ok != want {
    24  			t.Errorf("ParseDirective(0, %q) = %v, want %v", "// "+tt.in, ok, want)
    25  		}
    26  	}
    27  }
    28  
    29  func TestParseDirective(t *testing.T) {
    30  	for _, test := range []struct {
    31  		name   string
    32  		in     string
    33  		pos    token.Pos
    34  		want   Directive
    35  		wantOK bool
    36  	}{
    37  		{
    38  			name: "valid",
    39  			in:   "//go:generate stringer -type Op -trimprefix Op",
    40  			pos:  10,
    41  			want: Directive{
    42  				Tool:    "go",
    43  				Name:    "generate",
    44  				Args:    "stringer -type Op -trimprefix Op",
    45  				Slash:   10,
    46  				ArgsPos: token.Pos(10 + len("//go:generate ")),
    47  			},
    48  			wantOK: true,
    49  		},
    50  		{
    51  			name: "no args",
    52  			in:   "//go:build ignore",
    53  			pos:  20,
    54  			want: Directive{
    55  				Tool:    "go",
    56  				Name:    "build",
    57  				Args:    "ignore",
    58  				Slash:   20,
    59  				ArgsPos: token.Pos(20 + len("//go:build ")),
    60  			},
    61  			wantOK: true,
    62  		},
    63  		{
    64  			name:   "not a directive",
    65  			in:     "// not a directive",
    66  			pos:    30,
    67  			wantOK: false,
    68  		},
    69  		{
    70  			name:   "not a comment",
    71  			in:     "go:generate",
    72  			pos:    40,
    73  			wantOK: false,
    74  		},
    75  		{
    76  			name:   "empty",
    77  			in:     "",
    78  			pos:    50,
    79  			wantOK: false,
    80  		},
    81  		{
    82  			name:   "just slashes",
    83  			in:     "//",
    84  			pos:    60,
    85  			wantOK: false,
    86  		},
    87  		{
    88  			name:   "no name",
    89  			in:     "//go:",
    90  			pos:    70,
    91  			wantOK: false,
    92  		},
    93  		{
    94  			name:   "no tool",
    95  			in:     "//:generate",
    96  			pos:    80,
    97  			wantOK: false,
    98  		},
    99  		{
   100  			name: "multiple spaces",
   101  			in:   "//go:build  foo bar",
   102  			pos:  90,
   103  			want: Directive{
   104  				Tool:    "go",
   105  				Name:    "build",
   106  				Args:    "foo bar",
   107  				Slash:   90,
   108  				ArgsPos: token.Pos(90 + len("//go:build  ")),
   109  			},
   110  			wantOK: true,
   111  		},
   112  		{
   113  			name: "trailing space",
   114  			in:   "//go:build foo ",
   115  			pos:  100,
   116  			want: Directive{
   117  				Tool:    "go",
   118  				Name:    "build",
   119  				Args:    "foo",
   120  				Slash:   100,
   121  				ArgsPos: token.Pos(100 + len("//go:build ")),
   122  			},
   123  			wantOK: true,
   124  		},
   125  	} {
   126  		t.Run(test.name, func(t *testing.T) {
   127  			got, gotOK := ParseDirective(test.pos, test.in)
   128  			if gotOK != test.wantOK {
   129  				t.Fatalf("ParseDirective(%q) ok = %v, want %v", test.in, gotOK, test.wantOK)
   130  			}
   131  			if !reflect.DeepEqual(got, test.want) {
   132  				t.Errorf("ParseDirective(%q) = %+v, want %+v", test.in, got, test.want)
   133  			}
   134  		})
   135  	}
   136  }
   137  
   138  func TestParseArgs(t *testing.T) {
   139  	for _, test := range []struct {
   140  		name    string
   141  		in      Directive
   142  		want    []DirectiveArg
   143  		wantErr bool
   144  	}{
   145  		{
   146  			name: "simple",
   147  			in: Directive{
   148  				Tool:    "go",
   149  				Name:    "generate",
   150  				Args:    "stringer -type Op",
   151  				ArgsPos: 10,
   152  			},
   153  			want: []DirectiveArg{
   154  				{"stringer", 10},
   155  				{"-type", token.Pos(10 + len("stringer "))},
   156  				{"Op", token.Pos(10 + len("stringer -type "))},
   157  			},
   158  		},
   159  		{
   160  			name: "quoted",
   161  			in: Directive{
   162  				Tool:    "go",
   163  				Name:    "generate",
   164  				Args:    "\"foo bar\" baz",
   165  				ArgsPos: 10,
   166  			},
   167  			want: []DirectiveArg{
   168  				{"foo bar", 10},
   169  				{"baz", token.Pos(10 + len("\"foo bar\" "))},
   170  			},
   171  		},
   172  		{
   173  			name: "raw quoted",
   174  			in: Directive{
   175  				Tool:    "go",
   176  				Name:    "generate",
   177  				Args:    "`foo bar` baz",
   178  				ArgsPos: 10,
   179  			},
   180  			want: []DirectiveArg{
   181  				{"foo bar", 10},
   182  				{"baz", token.Pos(10 + len("`foo bar` "))},
   183  			},
   184  		},
   185  		{
   186  			name: "escapes",
   187  			in: Directive{
   188  				Tool:    "go",
   189  				Name:    "generate",
   190  				Args:    "\"foo\\U0001F60Abar\" `a\\tb`",
   191  				ArgsPos: 10,
   192  			},
   193  			want: []DirectiveArg{
   194  				{"foo😊bar", 10},
   195  				{"a\\tb", token.Pos(10 + len("\"foo\\U0001F60Abar\" "))},
   196  			},
   197  		},
   198  		{
   199  			name: "empty args",
   200  			in: Directive{
   201  				Tool:    "go",
   202  				Name:    "build",
   203  				Args:    "",
   204  				ArgsPos: 10,
   205  			},
   206  			want: []DirectiveArg{},
   207  		},
   208  		{
   209  			name: "spaces",
   210  			in: Directive{
   211  				Tool:    "go",
   212  				Name:    "build",
   213  				Args:    "  foo   bar  ",
   214  				ArgsPos: 10,
   215  			},
   216  			want: []DirectiveArg{
   217  				{"foo", token.Pos(10 + len("  "))},
   218  				{"bar", token.Pos(10 + len("  foo   "))},
   219  			},
   220  		},
   221  		{
   222  			name: "unterminated quote",
   223  			in: Directive{
   224  				Tool: "go",
   225  				Name: "generate",
   226  				Args: "`foo",
   227  			},
   228  			wantErr: true,
   229  		},
   230  		{
   231  			name: "no space after quote",
   232  			in: Directive{
   233  				Tool: "go",
   234  				Name: "generate",
   235  				Args: `"foo"bar`,
   236  			},
   237  			wantErr: true,
   238  		},
   239  	} {
   240  		t.Run(test.name, func(t *testing.T) {
   241  			got, err := test.in.ParseArgs()
   242  			if err != nil && !test.wantErr {
   243  				t.Errorf("got ParseArgs(%+v) = error %s; want %+v", test.in, err, test.want)
   244  			} else if err == nil && test.wantErr {
   245  				t.Errorf("got ParseArgs(%+v) = %+v; want error", test.in, got)
   246  			} else if err == nil && !reflect.DeepEqual(got, test.want) {
   247  				t.Errorf("got ParseArgs(%+v) = %+v; want %+v", test.in, got, test.want)
   248  			}
   249  		})
   250  	}
   251  }
   252  

View as plain text