Skip to content

Commit

Permalink
Added support for ASP.NET Razor (#3064)
Browse files Browse the repository at this point in the history
  • Loading branch information
RunDevelopment committed Sep 15, 2021
1 parent 6a356d2 commit 4433ccf
Show file tree
Hide file tree
Showing 13 changed files with 1,844 additions and 3 deletions.
2 changes: 1 addition & 1 deletion components.js

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions components.json
Expand Up @@ -1086,6 +1086,18 @@
"alias": "rkt",
"owner": "RunDevelopment"
},
"cshtml": {
"title": "Razor C#",
"alias": "razor",
"require": ["markup", "csharp"],
"optional":[
"css",
"css-extras",
"javascript",
"js-extras"
],
"owner": "RunDevelopment"
},
"jsx": {
"title": "React JSX",
"require": ["markup", "javascript"],
Expand Down
183 changes: 183 additions & 0 deletions components/prism-cshtml.js
@@ -0,0 +1,183 @@
// Docs:
// https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-5.0&tabs=visual-studio
// https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-5.0

(function (Prism) {

var commentLike = /\/(?![/*])|\/\/.*[\r\n]|\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\//.source;
var stringLike =
/@(?!")|"(?:[^\r\n\\"]|\\.)*"|@"(?:[^\\"]|""|\\[\s\S])*"(?!")/.source +
'|' +
/'(?:(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'|(?=[^\\](?!')))/.source;

/**
* Creates a nested pattern where all occurrences of the string `<<self>>` are replaced with the pattern itself.
*
* @param {string} pattern
* @param {number} depthLog2
* @returns {string}
*/
function nested(pattern, depthLog2) {
for (var i = 0; i < depthLog2; i++) {
pattern = pattern.replace(/<self>/g, function () { return '(?:' + pattern + ')'; });
}
return pattern
.replace(/<self>/g, '[^\\s\\S]')
.replace(/<str>/g, '(?:' + stringLike + ')')
.replace(/<comment>/g, '(?:' + commentLike + ')');
}

var round = nested(/\((?:[^()'"@/]|<str>|<comment>|<self>)*\)/.source, 2);
var square = nested(/\[(?:[^\[\]'"@/]|<str>|<comment>|<self>)*\]/.source, 2);
var curly = nested(/\{(?:[^{}'"@/]|<str>|<comment>|<self>)*\}/.source, 2);
var angle = nested(/<(?:[^<>'"@/]|<str>|<comment>|<self>)*>/.source, 2);

// 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
// comments) is parsed differently. This is a huge problem because HTML might contain brackets and quotes which
// messes up the bracket and string counting implemented by the above patterns.
//
// This problem is not fixable because 1) HTML expression are highly context sensitive and very difficult to detect
// and 2) they require one capturing group at every nested level. See the `tagRegion` pattern to admire the
// complexity of an HTML expression.
//
// 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: <p>That's it!</p>.

var tagAttrs = /(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?/.source;
var tagContent = /(?!\d)[^\s>\/=$<%]+/.source + tagAttrs + /\s*\/?>/.source;
var tagRegion =
/\B@?/.source +
'(?:' +
/<([a-zA-Z][\w:]*)/.source + tagAttrs + /\s*>/.source +
'(?:' +
(
/[^<]/.source +
'|' +
// all tags that are not the start tag
// eslint-disable-next-line regexp/strict
/<\/?(?!\1\b)/.source + tagContent +
'|' +
// nested start tag
nested(
// eslint-disable-next-line regexp/strict
/<\1/.source + tagAttrs + /\s*>/.source +
'(?:' +
(
/[^<]/.source +
'|' +
// all tags that are not the start tag
// eslint-disable-next-line regexp/strict
/<\/?(?!\1\b)/.source + tagContent +
'|' +
'<self>'
) +
')*' +
// eslint-disable-next-line regexp/strict
/<\/\1\s*>/.source,
2
)
) +
')*' +
// eslint-disable-next-line regexp/strict
/<\/\1\s*>/.source +
'|' +
/</.source + tagContent +
')';

// Now for the actual language definition(s):
//
// Razor as a language has 2 parts:
// 1) CSHTML: A markup-like language that has been extended with inline C# code expressions and blocks.
// 2) C#+HTML: A variant of C# that can contain CSHTML tags as expressions.
//
// In the below code, both CSHTML and C#+HTML will be create as separate language definitions that reference each
// other. However, only CSHTML will be exported via `Prism.languages`.

Prism.languages.cshtml = Prism.languages.extend('markup', {});

var csharpWithHtml = Prism.languages.insertBefore('csharp', 'string', {
'html': {
pattern: RegExp(tagRegion),
greedy: true,
inside: Prism.languages.cshtml
},
}, { csharp: Prism.languages.extend('csharp', {}) });

var cs = {
pattern: /\S[\s\S]*/,
alias: 'language-csharp',
inside: csharpWithHtml
};

Prism.languages.insertBefore('cshtml', 'prolog', {
'razor-comment': {
pattern: /@\*[\s\S]*?\*@/,
greedy: true,
alias: 'comment'
},

'block': {
pattern: RegExp(
/(^|[^@])@/.source +
'(?:' +
[
// @{ ... }
curly,
// @code{ ... }
/(?:code|functions)\s*/.source + curly,
// @for (...) { ... }
/(?:for|foreach|lock|switch|using|while)\s*/.source + round + /\s*/.source + curly,
// @do { ... } while (...);
/do\s*/.source + curly + /\s*while\s*/.source + round + /(?:\s*;)?/.source,
// @try { ... } catch (...) { ... } finally { ... }
/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 + ')*',
].join('|') +
')'
),
lookbehind: true,
greedy: true,
inside: {
'keyword': /^@\w*/,
'csharp': cs
}
},

'directive': {
pattern: /^([ \t]*)@(?:addTagHelper|attribute|implements|inherits|inject|layout|model|namespace|page|preservewhitespace|removeTagHelper|section|tagHelperPrefix|using)(?=\s).*/m,
lookbehind: true,
greedy: true,
inside: {
'keyword': /^@\w+/,
'csharp': cs
}
},

'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
}
},

'delegate-operator': {
pattern: /(^|[^@])@(?=<)/,
lookbehind: true,
alias: 'operator'
}
});

Prism.languages.razor = Prism.languages.cshtml;

}(Prism));
1 change: 1 addition & 0 deletions components/prism-cshtml.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions examples/prism-cshtml.html
@@ -0,0 +1,36 @@
<h2>Full example</h2>
<pre><code>@* Source: https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-5.0&amp;tabs=visual-studio#the-home-page *@

@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

&lt;h1>Contacts home page&lt;/h1>
&lt;form method="post">
&lt;table class="table">
&lt;thead>
&lt;tr>
&lt;th>ID&lt;/th>
&lt;th>Name&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
@foreach (var contact in Model.Customer)
{
&lt;tr>
&lt;td> @contact.Id &lt;/td>
&lt;td>@contact.Name&lt;/td>
&lt;td>
&lt;a asp-page="./Edit" asp-route-id="@contact.Id">Edit&lt;/a> |
&lt;button type="submit" asp-page-handler="delete"
asp-route-id="@contact.Id">delete
&lt;/button>
&lt;/td>
&lt;/tr>
}
&lt;/tbody>
&lt;/table>
&lt;a asp-page="Create">Create New&lt;/a>
&lt;/form>
</code></pre>
5 changes: 5 additions & 0 deletions plugins/autoloader/prism-autoloader.js
Expand Up @@ -115,6 +115,10 @@
"qml": "javascript",
"qore": "clike",
"racket": "scheme",
"cshtml": [
"markup",
"csharp"
],
"jsx": [
"markup",
"javascript"
Expand Down Expand Up @@ -224,6 +228,7 @@
"py": "python",
"qs": "qsharp",
"rkt": "racket",
"razor": "cshtml",
"rpy": "renpy",
"robot": "robotframework",
"rb": "ruby",
Expand Down

0 comments on commit 4433ccf

Please sign in to comment.