Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for ASP.NET Razor #3064

Merged
merged 5 commits into from Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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