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 }