github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/parser/pageparser/pageparser_shortcode_test.go (about)

     1  // Copyright 2018 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package pageparser
    15  
    16  import (
    17  	"testing"
    18  )
    19  
    20  var (
    21  	tstEOF            = nti(tEOF, "")
    22  	tstLeftNoMD       = nti(tLeftDelimScNoMarkup, "{{<")
    23  	tstRightNoMD      = nti(tRightDelimScNoMarkup, ">}}")
    24  	tstLeftMD         = nti(tLeftDelimScWithMarkup, "{{%")
    25  	tstRightMD        = nti(tRightDelimScWithMarkup, "%}}")
    26  	tstSCClose        = nti(tScClose, "/")
    27  	tstSC1            = nti(tScName, "sc1")
    28  	tstSC1Inline      = nti(tScNameInline, "sc1.inline")
    29  	tstSC2Inline      = nti(tScNameInline, "sc2.inline")
    30  	tstSC2            = nti(tScName, "sc2")
    31  	tstSC3            = nti(tScName, "sc3")
    32  	tstSCSlash        = nti(tScName, "sc/sub")
    33  	tstParam1         = nti(tScParam, "param1")
    34  	tstParam2         = nti(tScParam, "param2")
    35  	tstParamBoolTrue  = nti(tScParam, "true")
    36  	tstParamBoolFalse = nti(tScParam, "false")
    37  	tstParamInt       = nti(tScParam, "32")
    38  	tstParamFloat     = nti(tScParam, "3.14")
    39  	tstVal            = nti(tScParamVal, "Hello World")
    40  	tstText           = nti(tText, "Hello World")
    41  )
    42  
    43  var shortCodeLexerTests = []lexerTest{
    44  	{"empty", "", []Item{tstEOF}},
    45  	{"spaces", " \t\n", []Item{nti(tText, " \t\n"), tstEOF}},
    46  	{"text", `to be or not`, []Item{nti(tText, "to be or not"), tstEOF}},
    47  	{"no markup", `{{< sc1 >}}`, []Item{tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF}},
    48  	{"with EOL", "{{< sc1 \n >}}", []Item{tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF}},
    49  
    50  	{"forward slash inside name", `{{< sc/sub >}}`, []Item{tstLeftNoMD, tstSCSlash, tstRightNoMD, tstEOF}},
    51  
    52  	{"simple with markup", `{{% sc1 %}}`, []Item{tstLeftMD, tstSC1, tstRightMD, tstEOF}},
    53  	{"with spaces", `{{<     sc1     >}}`, []Item{tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF}},
    54  	{"mismatched rightDelim", `{{< sc1 %}}`, []Item{
    55  		tstLeftNoMD, tstSC1,
    56  		nti(tError, "unrecognized character in shortcode action: U+0025 '%'. Note: Parameters with non-alphanumeric args must be quoted"),
    57  	}},
    58  	{"inner, markup", `{{% sc1 %}} inner {{% /sc1 %}}`, []Item{
    59  		tstLeftMD,
    60  		tstSC1,
    61  		tstRightMD,
    62  		nti(tText, " inner "),
    63  		tstLeftMD,
    64  		tstSCClose,
    65  		tstSC1,
    66  		tstRightMD,
    67  		tstEOF,
    68  	}},
    69  	{"close, but no open", `{{< /sc1 >}}`, []Item{
    70  		tstLeftNoMD, nti(tError, "got closing shortcode, but none is open"),
    71  	}},
    72  	{"close wrong", `{{< sc1 >}}{{< /another >}}`, []Item{
    73  		tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose,
    74  		nti(tError, "closing tag for shortcode 'another' does not match start tag"),
    75  	}},
    76  	{"close, but no open, more", `{{< sc1 >}}{{< /sc1 >}}{{< /another >}}`, []Item{
    77  		tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose,
    78  		nti(tError, "closing tag for shortcode 'another' does not match start tag"),
    79  	}},
    80  	{"close with extra keyword", `{{< sc1 >}}{{< /sc1 keyword>}}`, []Item{
    81  		tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, tstSC1,
    82  		nti(tError, "unclosed shortcode"),
    83  	}},
    84  	{"float param, positional", `{{< sc1 3.14 >}}`, []Item{
    85  		tstLeftNoMD, tstSC1, nti(tScParam, "3.14"), tstRightNoMD, tstEOF,
    86  	}},
    87  	{"float param, named", `{{< sc1 param1=3.14 >}}`, []Item{
    88  		tstLeftNoMD, tstSC1, tstParam1, nti(tScParamVal, "3.14"), tstRightNoMD, tstEOF,
    89  	}},
    90  	{"named param, raw string", `{{< sc1 param1=` + "`" + "Hello World" + "`" + " >}}", []Item{
    91  		tstLeftNoMD, tstSC1, tstParam1, nti(tScParamVal, "Hello World"), tstRightNoMD, tstEOF,
    92  	}},
    93  	{"float param, named, space before", `{{< sc1 param1= 3.14 >}}`, []Item{
    94  		tstLeftNoMD, tstSC1, tstParam1, nti(tScParamVal, "3.14"), tstRightNoMD, tstEOF,
    95  	}},
    96  	{"Youtube id", `{{< sc1 -ziL-Q_456igdO-4 >}}`, []Item{
    97  		tstLeftNoMD, tstSC1, nti(tScParam, "-ziL-Q_456igdO-4"), tstRightNoMD, tstEOF,
    98  	}},
    99  	{"non-alphanumerics param quoted", `{{< sc1 "-ziL-.%QigdO-4" >}}`, []Item{
   100  		tstLeftNoMD, tstSC1, nti(tScParam, "-ziL-.%QigdO-4"), tstRightNoMD, tstEOF,
   101  	}},
   102  	{"raw string", `{{< sc1` + "`" + "Hello World" + "`" + ` >}}`, []Item{
   103  		tstLeftNoMD, tstSC1, nti(tScParam, "Hello World"), tstRightNoMD, tstEOF,
   104  	}},
   105  	{"raw string with newline", `{{< sc1` + "`" + `Hello 
   106  	World` + "`" + ` >}}`, []Item{
   107  		tstLeftNoMD, tstSC1, nti(tScParam, `Hello 
   108  	World`), tstRightNoMD, tstEOF,
   109  	}},
   110  	{"raw string with escape character", `{{< sc1` + "`" + `Hello \b World` + "`" + ` >}}`, []Item{
   111  		tstLeftNoMD, tstSC1, nti(tScParam, `Hello \b World`), tstRightNoMD, tstEOF,
   112  	}},
   113  	{"two params", `{{< sc1 param1   param2 >}}`, []Item{
   114  		tstLeftNoMD, tstSC1, tstParam1, tstParam2, tstRightNoMD, tstEOF,
   115  	}},
   116  	// issue #934
   117  	{"self-closing", `{{< sc1 />}}`, []Item{
   118  		tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD, tstEOF,
   119  	}},
   120  	// Issue 2498
   121  	{"multiple self-closing", `{{< sc1 />}}{{< sc1 />}}`, []Item{
   122  		tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD,
   123  		tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD, tstEOF,
   124  	}},
   125  	{"self-closing with param", `{{< sc1 param1 />}}`, []Item{
   126  		tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, tstEOF,
   127  	}},
   128  	{"multiple self-closing with param", `{{< sc1 param1 />}}{{< sc1 param1 />}}`, []Item{
   129  		tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD,
   130  		tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, tstEOF,
   131  	}},
   132  	{"multiple different self-closing with param", `{{< sc1 param1 />}}{{< sc2 param1 />}}`, []Item{
   133  		tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD,
   134  		tstLeftNoMD, tstSC2, tstParam1, tstSCClose, tstRightNoMD, tstEOF,
   135  	}},
   136  	{"nested simple", `{{< sc1 >}}{{< sc2 >}}{{< /sc1 >}}`, []Item{
   137  		tstLeftNoMD, tstSC1, tstRightNoMD,
   138  		tstLeftNoMD, tstSC2, tstRightNoMD,
   139  		tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, tstEOF,
   140  	}},
   141  	{"nested complex", `{{< sc1 >}}ab{{% sc2 param1 %}}cd{{< sc3 >}}ef{{< /sc3 >}}gh{{% /sc2 %}}ij{{< /sc1 >}}kl`, []Item{
   142  		tstLeftNoMD, tstSC1, tstRightNoMD,
   143  		nti(tText, "ab"),
   144  		tstLeftMD, tstSC2, tstParam1, tstRightMD,
   145  		nti(tText, "cd"),
   146  		tstLeftNoMD, tstSC3, tstRightNoMD,
   147  		nti(tText, "ef"),
   148  		tstLeftNoMD, tstSCClose, tstSC3, tstRightNoMD,
   149  		nti(tText, "gh"),
   150  		tstLeftMD, tstSCClose, tstSC2, tstRightMD,
   151  		nti(tText, "ij"),
   152  		tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD,
   153  		nti(tText, "kl"), tstEOF,
   154  	}},
   155  
   156  	{"two quoted params", `{{< sc1 "param nr. 1" "param nr. 2" >}}`, []Item{
   157  		tstLeftNoMD, tstSC1, nti(tScParam, "param nr. 1"), nti(tScParam, "param nr. 2"), tstRightNoMD, tstEOF,
   158  	}},
   159  	{"two named params", `{{< sc1 param1="Hello World" param2="p2Val">}}`, []Item{
   160  		tstLeftNoMD, tstSC1, tstParam1, tstVal, tstParam2, nti(tScParamVal, "p2Val"), tstRightNoMD, tstEOF,
   161  	}},
   162  	{"escaped quotes", `{{< sc1 param1=\"Hello World\"  >}}`, []Item{
   163  		tstLeftNoMD, tstSC1, tstParam1, tstVal, tstRightNoMD, tstEOF,
   164  	}},
   165  	{"escaped quotes, positional param", `{{< sc1 \"param1\"  >}}`, []Item{
   166  		tstLeftNoMD, tstSC1, tstParam1, tstRightNoMD, tstEOF,
   167  	}},
   168  	{"escaped quotes inside escaped quotes", `{{< sc1 param1=\"Hello \"escaped\" World\"  >}}`, []Item{
   169  		tstLeftNoMD, tstSC1, tstParam1,
   170  		nti(tScParamVal, `Hello `), nti(tError, `got positional parameter 'escaped'. Cannot mix named and positional parameters`),
   171  	}},
   172  	{
   173  		"escaped quotes inside nonescaped quotes",
   174  		`{{< sc1 param1="Hello \"escaped\" World"  >}}`,
   175  		[]Item{
   176  			tstLeftNoMD, tstSC1, tstParam1, nti(tScParamVal, `Hello "escaped" World`), tstRightNoMD, tstEOF,
   177  		},
   178  	},
   179  	{
   180  		"escaped quotes inside nonescaped quotes in positional param",
   181  		`{{< sc1 "Hello \"escaped\" World"  >}}`,
   182  		[]Item{
   183  			tstLeftNoMD, tstSC1, nti(tScParam, `Hello "escaped" World`), tstRightNoMD, tstEOF,
   184  		},
   185  	},
   186  	{"escaped raw string, named param", `{{< sc1 param1=` + `\` + "`" + "Hello World" + `\` + "`" + ` >}}`, []Item{
   187  		tstLeftNoMD, tstSC1, tstParam1, nti(tError, "unrecognized escape character"),
   188  	}},
   189  	{"escaped raw string, positional param", `{{< sc1 param1 ` + `\` + "`" + "Hello World" + `\` + "`" + ` >}}`, []Item{
   190  		tstLeftNoMD, tstSC1, tstParam1, nti(tError, "unrecognized escape character"),
   191  	}},
   192  	{"two raw string params", `{{< sc1` + "`" + "Hello World" + "`" + "`" + "Second Param" + "`" + ` >}}`, []Item{
   193  		tstLeftNoMD, tstSC1, nti(tScParam, "Hello World"), nti(tScParam, "Second Param"), tstRightNoMD, tstEOF,
   194  	}},
   195  	{"unterminated quote", `{{< sc1 param2="Hello World>}}`, []Item{
   196  		tstLeftNoMD, tstSC1, tstParam2, nti(tError, "unterminated quoted string in shortcode parameter-argument: 'Hello World>}}'"),
   197  	}},
   198  	{"unterminated raw string", `{{< sc1` + "`" + "Hello World" + ` >}}`, []Item{
   199  		tstLeftNoMD, tstSC1, nti(tError, "unterminated raw string in shortcode parameter-argument: 'Hello World >}}'"),
   200  	}},
   201  	{"unterminated raw string in second argument", `{{< sc1` + "`" + "Hello World" + "`" + "`" + "Second Param" + ` >}}`, []Item{
   202  		tstLeftNoMD, tstSC1, nti(tScParam, "Hello World"), nti(tError, "unterminated raw string in shortcode parameter-argument: 'Second Param >}}'"),
   203  	}},
   204  	{"one named param, one not", `{{< sc1 param1="Hello World" p2 >}}`, []Item{
   205  		tstLeftNoMD, tstSC1, tstParam1, tstVal,
   206  		nti(tError, "got positional parameter 'p2'. Cannot mix named and positional parameters"),
   207  	}},
   208  	{"one named param, one quoted positional param, both raw strings", `{{< sc1 param1=` + "`" + "Hello World" + "`" + "`" + "Second Param" + "`" + ` >}}`, []Item{
   209  		tstLeftNoMD, tstSC1, tstParam1, tstVal,
   210  		nti(tError, "got quoted positional parameter. Cannot mix named and positional parameters"),
   211  	}},
   212  	{"one named param, one quoted positional param", `{{< sc1 param1="Hello World" "And Universe" >}}`, []Item{
   213  		tstLeftNoMD, tstSC1, tstParam1, tstVal,
   214  		nti(tError, "got quoted positional parameter. Cannot mix named and positional parameters"),
   215  	}},
   216  	{"one quoted positional param, one named param", `{{< sc1 "param1" param2="And Universe" >}}`, []Item{
   217  		tstLeftNoMD, tstSC1, tstParam1,
   218  		nti(tError, "got named parameter 'param2'. Cannot mix named and positional parameters"),
   219  	}},
   220  	{"ono positional param, one not", `{{< sc1 param1 param2="Hello World">}}`, []Item{
   221  		tstLeftNoMD, tstSC1, tstParam1,
   222  		nti(tError, "got named parameter 'param2'. Cannot mix named and positional parameters"),
   223  	}},
   224  	{"commented out", `{{</* sc1 */>}}`, []Item{
   225  		nti(tText, "{{<"), nti(tText, " sc1 "), nti(tText, ">}}"), tstEOF,
   226  	}},
   227  	{"commented out, with asterisk inside", `{{</* sc1 "**/*.pdf" */>}}`, []Item{
   228  		nti(tText, "{{<"), nti(tText, " sc1 \"**/*.pdf\" "), nti(tText, ">}}"), tstEOF,
   229  	}},
   230  	{"commented out, missing close", `{{</* sc1 >}}`, []Item{
   231  		nti(tError, "comment must be closed"),
   232  	}},
   233  	{"commented out, misplaced close", `{{</* sc1 >}}*/`, []Item{
   234  		nti(tError, "comment must be closed"),
   235  	}},
   236  	// Inline shortcodes
   237  	{"basic inline", `{{< sc1.inline >}}Hello World{{< /sc1.inline >}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstEOF}},
   238  	{"basic inline with space", `{{< sc1.inline >}}Hello World{{< / sc1.inline >}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstEOF}},
   239  	{"inline self closing", `{{< sc1.inline >}}Hello World{{< /sc1.inline >}}Hello World{{< sc1.inline />}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSC1Inline, tstSCClose, tstRightNoMD, tstEOF}},
   240  	{"inline self closing, then a new inline", `{{< sc1.inline >}}Hello World{{< /sc1.inline >}}Hello World{{< sc1.inline />}}{{< sc2.inline >}}Hello World{{< /sc2.inline >}}`, []Item{
   241  		tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSC1Inline, tstSCClose, tstRightNoMD,
   242  		tstLeftNoMD, tstSC2Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC2Inline, tstRightNoMD, tstEOF,
   243  	}},
   244  	{"inline with template syntax", `{{< sc1.inline >}}{{ .Get 0 }}{{ .Get 1 }}{{< /sc1.inline >}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, nti(tText, "{{ .Get 0 }}"), nti(tText, "{{ .Get 1 }}"), tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstEOF}},
   245  	{"inline with nested shortcode (not supported)", `{{< sc1.inline >}}Hello World{{< sc1 >}}{{< /sc1.inline >}}`, []Item{tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, nti(tError, "inline shortcodes do not support nesting")}},
   246  	{"inline case mismatch", `{{< sc1.Inline >}}Hello World{{< /sc1.Inline >}}`, []Item{tstLeftNoMD, nti(tError, "period in shortcode name only allowed for inline identifiers")}},
   247  }
   248  
   249  func TestShortcodeLexer(t *testing.T) {
   250  	t.Parallel()
   251  	for i, test := range shortCodeLexerTests {
   252  		t.Run(test.name, func(t *testing.T) {
   253  			items := collect([]byte(test.input), true, lexMainSection)
   254  			if !equal(items, test.items) {
   255  				t.Errorf("[%d] %s: got\n\t%v\nexpected\n\t%v", i, test.name, items, test.items)
   256  			}
   257  		})
   258  	}
   259  }
   260  
   261  func BenchmarkShortcodeLexer(b *testing.B) {
   262  	testInputs := make([][]byte, len(shortCodeLexerTests))
   263  	for i, input := range shortCodeLexerTests {
   264  		testInputs[i] = []byte(input.input)
   265  	}
   266  	var cfg Config
   267  	b.ResetTimer()
   268  	for i := 0; i < b.N; i++ {
   269  		for _, input := range testInputs {
   270  			items := collectWithConfig(input, true, lexMainSection, cfg)
   271  			if len(items) == 0 {
   272  			}
   273  
   274  		}
   275  	}
   276  }