github.com/aretext/aretext@v1.3.0/syntax/languages/makefile_test.go (about)

     1  package languages
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/assert"
     7  
     8  	"github.com/aretext/aretext/syntax/parser"
     9  )
    10  
    11  func TestMakefileParseFunc(t *testing.T) {
    12  	testCases := []struct {
    13  		name     string
    14  		text     string
    15  		expected []TokenWithText
    16  	}{
    17  		{
    18  			name: "comment",
    19  			text: `# comment`,
    20  			expected: []TokenWithText{
    21  				{Text: `# comment`, Role: parser.TokenRoleComment},
    22  			},
    23  		},
    24  		{
    25  			name: "comment with colon",
    26  			text: `# notatarget: xyz`,
    27  			expected: []TokenWithText{
    28  				{Text: `# notatarget: xyz`, Role: parser.TokenRoleComment},
    29  			},
    30  		},
    31  		{
    32  			name: "rule",
    33  			text: `
    34  foo: bar.c
    35  	cc bar.c -o foo
    36  
    37  bar.c:
    38  	echo "test" > bar.c
    39  `,
    40  			expected: []TokenWithText{},
    41  		},
    42  		{
    43  			name: "variable assignment",
    44  			text: `
    45  CC := gcc
    46  x ?= maybe
    47  y = foobar
    48  out != echo "hello"
    49  `,
    50  			expected: []TokenWithText{
    51  				{Text: `:=`, Role: parser.TokenRoleOperator},
    52  				{Text: `?=`, Role: parser.TokenRoleOperator},
    53  				{Text: `=`, Role: parser.TokenRoleOperator},
    54  				{Text: `!=`, Role: parser.TokenRoleOperator},
    55  			},
    56  		},
    57  		{
    58  			name: "variable assignment with %",
    59  			text: "BUILD_DATE   := `date +%FT%T%z`",
    60  			expected: []TokenWithText{
    61  				{Text: `:=`, Role: parser.TokenRoleOperator},
    62  			},
    63  		},
    64  		{
    65  			name: "variable assignment with backslash line continuation",
    66  			text: `
    67  VAR = abc \
    68  % \
    69  xyz
    70  
    71  %.o: %.c
    72  	$(CC) -o $@
    73  `,
    74  			expected: []TokenWithText{
    75  				{Text: "=", Role: parser.TokenRoleOperator},
    76  				{Text: "%", Role: makefileTokenRolePattern},
    77  				{Text: "%", Role: makefileTokenRolePattern},
    78  				{Text: "$(CC)", Role: makefileTokenRoleVariable},
    79  				{Text: "$@", Role: makefileTokenRoleVariable},
    80  			},
    81  		},
    82  		{
    83  			name: "command with colon",
    84  			text: `
    85  test:
    86  	echo "foo:bar"
    87  `,
    88  			expected: []TokenWithText{},
    89  		},
    90  		{
    91  			name: "variable assignment followed by colon",
    92  			text: `
    93  ARETEXT_URL ?= https://aretext.org
    94  `,
    95  			expected: []TokenWithText{
    96  				{Text: `?=`, Role: parser.TokenRoleOperator},
    97  			},
    98  		},
    99  		{
   100  			name: "variable assignment with expansion",
   101  			text: `COMMIT       := $(shell git rev-parse --short HEAD)`,
   102  			expected: []TokenWithText{
   103  				{Text: `:=`, Role: parser.TokenRoleOperator},
   104  				{Text: `$(shell git rev-parse --short HEAD)`, Role: makefileTokenRoleVariable},
   105  			},
   106  		},
   107  		{
   108  			name: "variable with nested expansions",
   109  			text: `$(${$(VAR)} abc)`,
   110  			expected: []TokenWithText{
   111  				{Text: `$(${$(VAR)} abc)`, Role: makefileTokenRoleVariable},
   112  			},
   113  		},
   114  		{
   115  			name: "rule with variables",
   116  			text: `
   117  objects = program.o foo.o utils.o
   118  program : $(objects)
   119          cc -o program $(objects)
   120  
   121  $(objects) : defs.h
   122  `,
   123  			expected: []TokenWithText{
   124  				{Text: "=", Role: parser.TokenRoleOperator},
   125  				{Text: "$(objects)", Role: makefileTokenRoleVariable},
   126  				{Text: "$(objects)", Role: makefileTokenRoleVariable},
   127  				{Text: "$(objects)", Role: makefileTokenRoleVariable},
   128  			},
   129  		},
   130  		{
   131  			name: "rule with automatic variables",
   132  			text: `
   133  %.o: %.c
   134          $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
   135  `,
   136  			expected: []TokenWithText{
   137  				{Text: "%", Role: makefileTokenRolePattern},
   138  				{Text: "%", Role: makefileTokenRolePattern},
   139  				{Text: "$(CC)", Role: makefileTokenRoleVariable},
   140  				{Text: "$(CFLAGS)", Role: makefileTokenRoleVariable},
   141  				{Text: "$(CPPFLAGS)", Role: makefileTokenRoleVariable},
   142  				{Text: "$<", Role: makefileTokenRoleVariable},
   143  				{Text: "$@", Role: makefileTokenRoleVariable},
   144  			},
   145  		},
   146  		{
   147  			name: "escaped dollar sign",
   148  			text: `
   149  foo: foo.c
   150  	echo "$${SHELL}"
   151  `,
   152  			expected: []TokenWithText{},
   153  		},
   154  		{
   155  			name: "@ suppress output in command",
   156  			text: `
   157  foo: foo.c
   158  	@ echo "test"
   159  	@echo "test2"
   160  `,
   161  			expected: []TokenWithText{
   162  				{Text: "@", Role: parser.TokenRoleOperator},
   163  				{Text: "@", Role: parser.TokenRoleOperator},
   164  			},
   165  		},
   166  		{
   167  			name: "conditional at top-level",
   168  			text: `
   169  ifeq ($(CC),gcc)
   170    libs=$(libs_for_gcc)
   171  else
   172    libs=$(normal_libs)
   173  endif
   174  `,
   175  			expected: []TokenWithText{
   176  				{Text: "ifeq", Role: parser.TokenRoleKeyword},
   177  				{Text: "$(CC)", Role: makefileTokenRoleVariable},
   178  				{Text: "=", Role: parser.TokenRoleOperator},
   179  				{Text: "$(libs_for_gcc)", Role: makefileTokenRoleVariable},
   180  				{Text: "else", Role: parser.TokenRoleKeyword},
   181  				{Text: "=", Role: parser.TokenRoleOperator},
   182  				{Text: "$(normal_libs)", Role: makefileTokenRoleVariable},
   183  				{Text: "endif", Role: parser.TokenRoleKeyword},
   184  			},
   185  		},
   186  		{
   187  			name: "conditional in recipe",
   188  			text: `
   189  foo.o: foo.c
   190  ifndef ENV_VAR
   191  	$(error ENV_VAR not set)
   192  endif
   193  	@echo "$$ENV_VAR"
   194  `,
   195  			expected: []TokenWithText{
   196  				{Text: "ifndef", Role: parser.TokenRoleKeyword},
   197  				{Text: "$(error ENV_VAR not set)", Role: makefileTokenRoleVariable},
   198  				{Text: "endif", Role: parser.TokenRoleKeyword},
   199  				{Text: "@", Role: parser.TokenRoleOperator},
   200  			},
   201  		},
   202  		{
   203  			name: "one-line recipe with semicolon",
   204  			text: `all : ; @echo 'hello'`,
   205  			expected: []TokenWithText{
   206  				{Text: "@", Role: parser.TokenRoleOperator},
   207  			},
   208  		},
   209  		{
   210  			name: "define",
   211  			text: `
   212  define run-yacc =
   213  yacc $(firstword $^)
   214  mv y.tab.c $@
   215  endef
   216  `,
   217  			expected: []TokenWithText{
   218  				{Text: "define", Role: parser.TokenRoleKeyword},
   219  				{Text: "=", Role: parser.TokenRoleOperator},
   220  				{Text: "$(firstword $^)", Role: makefileTokenRoleVariable},
   221  				{Text: "$@", Role: makefileTokenRoleVariable},
   222  				{Text: "endef", Role: parser.TokenRoleKeyword},
   223  			},
   224  		},
   225  		{
   226  			name: "include",
   227  			text: `include ../Makefile.defs`,
   228  			expected: []TokenWithText{
   229  				{Text: "include", Role: parser.TokenRoleKeyword},
   230  			},
   231  		},
   232  		{
   233  			name: "include with error suppression",
   234  			text: `-include $(DEPS)`,
   235  			expected: []TokenWithText{
   236  				{Text: "-include", Role: parser.TokenRoleKeyword},
   237  				{Text: "$(DEPS)", Role: makefileTokenRoleVariable},
   238  			},
   239  		},
   240  		{
   241  			name: "comment at end of rule",
   242  			text: `
   243  %.o: %.c # this is a comment
   244  	$(CC) X=123
   245  `,
   246  			expected: []TokenWithText{
   247  				{Text: "%", Role: makefileTokenRolePattern},
   248  				{Text: "%", Role: makefileTokenRolePattern},
   249  				{Text: "# this is a comment", Role: parser.TokenRoleComment},
   250  				{Text: "$(CC)", Role: makefileTokenRoleVariable},
   251  				// The "=" should NOT be parsed as an operator, since it's in a recipe command.
   252  			},
   253  		},
   254  		{
   255  			name: "comment at end of rule with variables",
   256  			text: `
   257  $(BUILD_DIR)/$(TARGET_EXEC): $(OBJS) # asdf
   258  	X=Y
   259  	foo % bar
   260  `,
   261  			expected: []TokenWithText{
   262  				{Text: "$(BUILD_DIR)", Role: makefileTokenRoleVariable},
   263  				{Text: "$(TARGET_EXEC)", Role: makefileTokenRoleVariable},
   264  				{Text: "$(OBJS)", Role: makefileTokenRoleVariable},
   265  				{Text: "# asdf", Role: parser.TokenRoleComment},
   266  				// The "=" and "%" should NOT be tokenized, since they're in a recipe command.
   267  			},
   268  		},
   269  	}
   270  
   271  	for _, tc := range testCases {
   272  		t.Run(tc.name, func(t *testing.T) {
   273  			tokens := ParseTokensWithText(MakefileParseFunc(), tc.text)
   274  			assert.Equal(t, tc.expected, tokens)
   275  		})
   276  	}
   277  }
   278  
   279  func FuzzMakefileParseFunc(f *testing.F) {
   280  	seeds := LoadFuzzTestSeeds(f, "./testdata/makefile/*")
   281  	FuzzParser(f, MakefileParseFunc(), seeds)
   282  }