From 31a38d0ca02e6399637a6c693f65f1b007c71c63 Mon Sep 17 00:00:00 2001 From: Michael Schmidt Date: Sun, 13 Mar 2022 11:01:11 +0100 Subject: [PATCH] CSHTML: Added support for `@helper` and inline C# inside attribute values (#3355) --- components/prism-cshtml.js | 52 ++++-- components/prism-cshtml.min.js | 2 +- tests/languages/cshtml/block_feature.test | 22 +++ tests/languages/cshtml/issue3354.test | 194 ++++++++++++++++++++++ 4 files changed, 251 insertions(+), 19 deletions(-) create mode 100644 tests/languages/cshtml/issue3354.test diff --git a/components/prism-cshtml.js b/components/prism-cshtml.js index a917626aa0..4643710d3d 100644 --- a/components/prism-cshtml.js +++ b/components/prism-cshtml.js @@ -28,9 +28,15 @@ } var round = nested(/\((?:[^()'"@/]|||)*\)/.source, 2); - var square = nested(/\[(?:[^\[\]'"@/]|||)*\]/.source, 2); + var square = nested(/\[(?:[^\[\]'"@/]|||)*\]/.source, 1); var curly = nested(/\{(?:[^{}'"@/]|||)*\}/.source, 2); - var angle = nested(/<(?:[^<>'"@/]|||)*>/.source, 2); + var angle = nested(/<(?:[^<>'"@/]||)*>/.source, 1); + + var inlineCs = /@/.source + + /(?:await\b\s*)?/.source + + '(?:' + /(?!await\b)\w+\b/.source + '|' + round + ')' + + '(?:' + /[?!]?\.\w+\b/.source + '|' + '(?:' + angle + ')?' + round + '|' + square + ')*' + + /(?![?!\.(\[]|<(?!\/))/.source; // Note about the above bracket patterns: // They all ignore HTML expressions that might be in the C# code. This is a problem because HTML (like strings and @@ -44,7 +50,14 @@ // To somewhat alleviate the problem a bit, the patterns for characters (e.g. 'a') is very permissive, it also // allows invalid characters to support HTML expressions like this:

That's it!

. - var tagAttrs = /(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?/.source; + var tagAttrInlineCs = /@(?![\w()])/.source + '|' + inlineCs; + var tagAttrValue = '(?:' + + /"[^"@]*"|'[^'@]*'|[^\s'"@>=]+(?=[\s>])/.source + + '|' + + '["\'][^"\'@]*(?:(?:' + tagAttrInlineCs + ')[^"\'@]*)+["\']' + + ')'; + + var tagAttrs = /(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*|(?=[\s/>])))+)?/.source.replace(//, tagAttrValue); var tagContent = /(?!\d)[^\s>\/=$<%]+/.source + tagAttrs + /\s*\/?>/.source; var tagRegion = /\B@?/.source + @@ -110,6 +123,21 @@ inside: csharpWithHtml }; + var inlineValue = { + pattern: RegExp(/(^|[^@])/.source + inlineCs), + lookbehind: true, + greedy: true, + alias: 'variable', + inside: { + 'keyword': /^@/, + 'csharp': cs + } + }; + + Prism.languages.cshtml.tag.pattern = RegExp(/<\/?/.source + tagContent); + Prism.languages.cshtml.tag.inside['attr-value'].pattern = RegExp(/=\s*/.source + tagAttrValue); + Prism.languages.insertBefore('inside', 'punctuation', { 'value': inlineValue }, Prism.languages.cshtml.tag.inside['attr-value']); + Prism.languages.insertBefore('cshtml', 'prolog', { 'razor-comment': { pattern: /@\*[\s\S]*?\*@/, @@ -134,6 +162,8 @@ /try\s*/.source + curly + /\s*catch\s*/.source + round + /\s*/.source + curly + /\s*finally\s*/.source + curly, // @if (...) {...} else if (...) {...} else {...} /if\s*/.source + round + /\s*/.source + curly + '(?:' + /\s*else/.source + '(?:' + /\s+if\s*/.source + round + ')?' + /\s*/.source + curly + ')*', + // @helper Ident(params) { ... } + /helper\s+\w+\s*/.source + round + /\s*/.source + curly, ].join('|') + ')' ), @@ -155,21 +185,7 @@ } }, - 'value': { - pattern: RegExp( - /(^|[^@])@/.source + - /(?:await\b\s*)?/.source + - '(?:' + /\w+\b/.source + '|' + round + ')' + - '(?:' + /[?!]?\.\w+\b/.source + '|' + round + '|' + square + '|' + angle + round + ')*' - ), - lookbehind: true, - greedy: true, - alias: 'variable', - inside: { - 'keyword': /^@/, - 'csharp': cs - } - }, + 'value': inlineValue, 'delegate-operator': { pattern: /(^|[^@])@(?=<)/, diff --git a/components/prism-cshtml.min.js b/components/prism-cshtml.min.js index 537152a87f..c5c96841e6 100644 --- a/components/prism-cshtml.min.js +++ b/components/prism-cshtml.min.js @@ -1 +1 @@ -!function(e){function s(e,s){for(var a=0;a/g,function(){return"(?:"+e+")"});return e.replace(//g,"[^\\s\\S]").replace(//g,'(?:@(?!")|"(?:[^\r\n\\\\"]|\\\\.)*"|@"(?:[^\\\\"]|""|\\\\[^])*"(?!")|'+"'(?:(?:[^\r\n'\\\\]|\\\\.|\\\\[Uux][\\da-fA-F]{1,8})'|(?=[^\\\\](?!'))))").replace(//g,"(?:/(?![/*])|//.*[\r\n]|/\\*[^*]*(?:\\*(?!/)[^*]*)*\\*/)")}var a=s("\\((?:[^()'\"@/]|||)*\\)",2),r=s("\\[(?:[^\\[\\]'\"@/]|||)*\\]",2),t=s("\\{(?:[^{}'\"@/]|||)*\\}",2),n=s("<(?:[^<>'\"@/]|||)*>",2),l="(?:\\s(?:\\s*[^\\s>/=]+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))|(?=[\\s/>])))+)?",i="(?!\\d)[^\\s>/=$<%]+"+l+"\\s*/?>",o="\\B@?(?:<([a-zA-Z][\\w:]*)"+l+"\\s*>(?:[^<]|(?:[^<]|)*",2)+")*|<"+i+")";e.languages.cshtml=e.languages.extend("markup",{});var g={pattern:/\S[\s\S]*/,alias:"language-csharp",inside:e.languages.insertBefore("csharp","string",{html:{pattern:RegExp(o),greedy:!0,inside:e.languages.cshtml}},{csharp:e.languages.extend("csharp",{})})};e.languages.insertBefore("cshtml","prolog",{"razor-comment":{pattern:/@\*[\s\S]*?\*@/,greedy:!0,alias:"comment"},block:{pattern:RegExp("(^|[^@])@(?:"+[t,"(?:code|functions)\\s*"+t,"(?:for|foreach|lock|switch|using|while)\\s*"+a+"\\s*"+t,"do\\s*"+t+"\\s*while\\s*"+a+"(?:\\s*;)?","try\\s*"+t+"\\s*catch\\s*"+a+"\\s*"+t+"\\s*finally\\s*"+t,"if\\s*"+a+"\\s*"+t+"(?:\\s*else(?:\\s+if\\s*"+a+")?\\s*"+t+")*"].join("|")+")"),lookbehind:!0,greedy:!0,inside:{keyword:/^@\w*/,csharp:g}},directive:{pattern:/^([ \t]*)@(?:addTagHelper|attribute|implements|inherits|inject|layout|model|namespace|page|preservewhitespace|removeTagHelper|section|tagHelperPrefix|using)(?=\s).*/m,lookbehind:!0,greedy:!0,inside:{keyword:/^@\w+/,csharp:g}},value:{pattern:RegExp("(^|[^@])@(?:await\\b\\s*)?(?:\\w+\\b|"+a+")(?:[?!]?\\.\\w+\\b|"+a+"|"+r+"|"+n+a+")*"),lookbehind:!0,greedy:!0,alias:"variable",inside:{keyword:/^@/,csharp:g}},"delegate-operator":{pattern:/(^|[^@])@(?=<)/,lookbehind:!0,alias:"operator"}}),e.languages.razor=e.languages.cshtml}(Prism); \ No newline at end of file +!function(e){function s(e,s){for(var a=0;a/g,function(){return"(?:"+e+")"});return e.replace(//g,"[^\\s\\S]").replace(//g,'(?:@(?!")|"(?:[^\r\n\\\\"]|\\\\.)*"|@"(?:[^\\\\"]|""|\\\\[^])*"(?!")|'+"'(?:(?:[^\r\n'\\\\]|\\\\.|\\\\[Uux][\\da-fA-F]{1,8})'|(?=[^\\\\](?!'))))").replace(//g,"(?:/(?![/*])|//.*[\r\n]|/\\*[^*]*(?:\\*(?!/)[^*]*)*\\*/)")}var a=s("\\((?:[^()'\"@/]|||)*\\)",2),t=s("\\[(?:[^\\[\\]'\"@/]|||)*\\]",1),r=s("\\{(?:[^{}'\"@/]|||)*\\}",2),n="@(?:await\\b\\s*)?(?:(?!await\\b)\\w+\\b|"+a+")(?:[?!]?\\.\\w+\\b|(?:"+s("<(?:[^<>'\"@/]||)*>",1)+")?"+a+"|"+t+")*(?![?!\\.(\\[]|<(?!/))",l="(?:\"[^\"@]*\"|'[^'@]*'|[^\\s'\"@>=]+(?=[\\s>])|[\"'][^\"'@]*(?:(?:"+("@(?![\\w()])|"+n)+")[^\"'@]*)+[\"'])",i="(?:\\s(?:\\s*[^\\s>/=]+(?:\\s*=\\s*|(?=[\\s/>])))+)?".replace(//,l),g="(?!\\d)[^\\s>/=$<%]+"+i+"\\s*/?>",o="\\B@?(?:<([a-zA-Z][\\w:]*)"+i+"\\s*>(?:[^<]|(?:[^<]|)*",2)+")*|<"+g+")";e.languages.cshtml=e.languages.extend("markup",{});var c={pattern:/\S[\s\S]*/,alias:"language-csharp",inside:e.languages.insertBefore("csharp","string",{html:{pattern:RegExp(o),greedy:!0,inside:e.languages.cshtml}},{csharp:e.languages.extend("csharp",{})})},p={pattern:RegExp("(^|[^@])"+n),lookbehind:!0,greedy:!0,alias:"variable",inside:{keyword:/^@/,csharp:c}};e.languages.cshtml.tag.pattern=RegExp(" + +

+ @Localize.GetLabelHtml("TITLE") +

+ +@{ + var man = "Federico"; + var text = string.Concat("Nice to meet you", " ", man); +} + +@helper TrialHelper(string name) { + var text = string.Concat("Hello", " ", name); +

+ @(text + ", how's going?") +

+

+ Hello World! +

+} + +---------------------------------------------------- + +[ + ["tag", [ + ["tag", [ + ["punctuation", "<"], + "input" + ]], + ["attr-name", ["type"]], + ["attr-value", [ + ["punctuation", "="], + ["punctuation", "\""], + "text", + ["punctuation", "\""] + ]], + ["attr-name", ["placeholder"]], + ["attr-value", [ + ["punctuation", "="], + ["punctuation", "\""], + ["value", [ + ["keyword", "@"], + ["csharp", [ + "Localize", + ["punctuation", "."], + ["function", "GetLabelHtml"], + ["punctuation", "("], + ["string", "\"PLACEHOLDER\""], + ["punctuation", ")"] + ]] + ]], + ["punctuation", "\""] + ]], + ["punctuation", "/>"] + ]], + + ["tag", [ + ["tag", [ + ["punctuation", "<"], + "h1" + ]], + ["punctuation", ">"] + ]], + ["value", [ + ["keyword", "@"], + ["csharp", [ + "Localize", + ["punctuation", "."], + ["function", "GetLabelHtml"], + ["punctuation", "("], + ["string", "\"TITLE\""], + ["punctuation", ")"] + ]] + ]], + ["tag", [ + ["tag", [ + ["punctuation", ""] + ]], + + ["block", [ + ["keyword", "@"], + ["csharp", [ + ["punctuation", "{"], + + ["class-name", [ + ["keyword", "var"] + ]], + " man ", + ["operator", "="], + ["string", "\"Federico\""], + ["punctuation", ";"], + + ["class-name", [ + ["keyword", "var"] + ]], + " text ", + ["operator", "="], + ["keyword", "string"], + ["punctuation", "."], + ["function", "Concat"], + ["punctuation", "("], + ["string", "\"Nice to meet you\""], + ["punctuation", ","], + ["string", "\" \""], + ["punctuation", ","], + " man", + ["punctuation", ")"], + ["punctuation", ";"], + + ["punctuation", "}"] + ]] + ]], + + ["block", [ + ["keyword", "@helper"], + ["csharp", [ + ["function", "TrialHelper"], + ["punctuation", "("], + ["class-name", [ + ["keyword", "string"] + ]], + " name", + ["punctuation", ")"], + ["punctuation", "{"], + + ["class-name", [ + ["keyword", "var"] + ]], + " text ", + ["operator", "="], + ["keyword", "string"], + ["punctuation", "."], + ["function", "Concat"], + ["punctuation", "("], + ["string", "\"Hello\""], + ["punctuation", ","], + ["string", "\" \""], + ["punctuation", ","], + " name", + ["punctuation", ")"], + ["punctuation", ";"], + + ["html", [ + ["tag", [ + ["tag", [ + ["punctuation", "<"], + "h1" + ]], + ["punctuation", ">"] + ]], + ["value", [ + ["keyword", "@"], + ["csharp", [ + ["punctuation", "("], + "text ", + ["operator", "+"], + ["string", "\", how's going?\""], + ["punctuation", ")"] + ]] + ]], + ["tag", [ + ["tag", [ + ["punctuation", ""] + ]] + ]], + + ["html", [ + ["tag", [ + ["tag", [ + ["punctuation", "<"], + "p" + ]], + ["punctuation", ">"] + ]], + "\r\n Hello World!\r\n ", + ["tag", [ + ["tag", [ + ["punctuation", ""] + ]] + ]], + + ["punctuation", "}"] + ]] + ]] +]