UNPKG

highlightjs-cshtml-razor

Version:

highlight.js syntax definition for ASP.NET Razor CSHTML language

409 lines (403 loc) 9.58 kB
/* eslint-disable no-useless-escape */ /* * Language: cshtml-razor * Requires: xml.js, csharp.js, css.js, javascript.js * Author: Roman Resh <romanresh@live.com> */ module.exports = function(hljs) { const SPECIAL_SYMBOL_CLASSNAME = "built_in"; const CONTENT_REPLACER = {}; const closedBrace = { begin: "}", className: SPECIAL_SYMBOL_CLASSNAME, endsParent: true }; const braces = { begin: "{", end: "}", contains: [hljs.QUOTE_STRING_MODE, 'self'] }; const csbraces = { // allows to find exactly last closing brace in code blocks (to process codeblock content with "csharp" sub language) begin: "{", end: "}", contains: ['self'], skip: true }; const quotes = { // allows to skip razor symbols/tags inside CS strings variants: [ { begin: /"/, end: /"/, skip: true }, { begin: /'/, end: /'/, skip: true } ], skip: true }; const razorComment = hljs.COMMENT( '@\\*', '\\*@', { relevance: 10 } ); const razorInlineExpression = { begin: '@[A-Za-z0-9\\._:-]+', returnBegin: true, end: "(\\r|\\n|<|\\s|\"|')", subLanguage: 'csharp', contains: [ { begin: '@', className: SPECIAL_SYMBOL_CLASSNAME }, { begin: '\\[', end: '\\]', skip: true }, { begin: '\\(', end: '\\)', skip: true } ], returnEnd: true }; const razorTextBlock = { begin: "[@]{0,1}<text>", returnBegin: true, end: "</text>", returnEnd: true, subLanguage: "cshtml-razor", contains: [ { begin: "[@]{0,1}<text>", className: SPECIAL_SYMBOL_CLASSNAME }, { begin: "</text>", className: SPECIAL_SYMBOL_CLASSNAME, endsParent: true } ] }; const razorEscapeAt = { variants: [ { begin: "@@" }, { begin: "[a-zA-Z]+@" } ], skip: true }; const razorParenthesesBlock = { begin: "@\\(", end: "\\)", returnBegin: true, returnEnd: true, subLanguage: 'csharp', contains: [ { begin: "@\\(", className: SPECIAL_SYMBOL_CLASSNAME }, { begin: "\\(", end: "\\)", subLanguage: 'csharp', contains: [hljs.QUOTE_STRING_MODE, 'self', razorTextBlock] }, razorTextBlock, { begin: "\\)", className: SPECIAL_SYMBOL_CLASSNAME, endsParent: true } ] }; const xmlBlocks = getXmlBlocks(hljs, [razorInlineExpression, razorParenthesesBlock]); const razorDirectivesPrefix = "^\\s*@(page|model|using|inherits|inject|layout)"; const razorDirectives = { begin: razorDirectivesPrefix + "[^\\r\\n{\\(]*$", end: "$", returnBegin: true, returnEnd: true, contains: [ { begin: razorDirectivesPrefix, className: SPECIAL_SYMBOL_CLASSNAME }, { variants: [ { begin: "\\r|\\n", endsParent: true }, { begin: "\\s[^\\r\\n]+", end: "$" }, { begin: "$" } ], className: "type", endsParent: true } ] }; const csCodeBlockVariants = [ { begin: "@\\{", end: "}" }, { begin: "@code\\s*\\{", end: "}" } ]; const razorBlock = { variants: csCodeBlockVariants, returnBegin: true, returnEnd: true, subLanguage: 'csharp', contains: [ { begin: "@(code\\s*)?\\{", className: SPECIAL_SYMBOL_CLASSNAME }, CONTENT_REPLACER, csbraces, quotes, closedBrace ] }; const razorHelperBlock = { begin: "^\\s*@helper\\s+[^{\\s]+(?:\\s+[^{\\s]+)*\\s*{", returnBegin: true, returnEnd: true, end: "}", subLanguage: "cshtml-razor", contains: [ { begin: "@helper", className: SPECIAL_SYMBOL_CLASSNAME }, { begin: "{", className: SPECIAL_SYMBOL_CLASSNAME }, closedBrace ] }; const razorCodeBlockVariants = ['for', 'if', 'switch', 'while', 'using', 'lock', 'foreach'] .map(keyword => ({ begin: `@${keyword}(?![\\w\\d])[^{]*\\{`, end: "}" })); const elseVariants = [ { begin: "\\}\\s*else\\s*(if[^\\{]+|)\\{" } ]; const razorCodeBlock = { variants: razorCodeBlockVariants, returnBegin: true, returnEnd: true, subLanguage: 'csharp', contains: [ { variants: razorCodeBlockVariants.map(function(v) { return { begin: v.begin }; }), returnBegin: true, contains: [ { begin: "@", className: SPECIAL_SYMBOL_CLASSNAME }, { variants: razorCodeBlockVariants.map(function(v) { return { begin: `${v.begin}`.substring(1, v.begin.length - 2) }; }), subLanguage: 'csharp' }, { begin: "{", className: SPECIAL_SYMBOL_CLASSNAME } ] }, CONTENT_REPLACER, { variants: elseVariants, returnBegin: true, contains: [ { begin: "}", className: SPECIAL_SYMBOL_CLASSNAME }, { begin: elseVariants[0].begin.substring(2, elseVariants[0].begin.length - 2), subLanguage: 'csharp' }, { begin: "{", className: SPECIAL_SYMBOL_CLASSNAME } ] }, braces, closedBrace ] }; const razorTryBlock = { begin: "@try\\s*{", end: "}", returnBegin: true, returnEnd: true, subLanguage: 'csharp', contains: [ { begin: "@", className: SPECIAL_SYMBOL_CLASSNAME }, { begin: "try\\s*{", subLanguage: 'csharp' }, { variants: [ { begin: "}\\s*catch\\s*\\([^\\)]+\\)\\s*{" }, { begin: "}\\s*finally\\s*{" } ], returnBegin: true, contains: [ { begin: "}", className: SPECIAL_SYMBOL_CLASSNAME }, { variants: [ { begin: "\\s*catch\\s*\\([^\\)]+\\)\\s*" }, { begin: "\\s*finally\\s*" } ], subLanguage: 'csharp' }, { begin: "{", className: SPECIAL_SYMBOL_CLASSNAME } ] }, CONTENT_REPLACER, braces, closedBrace ] }; const sectionBegin = "@section\\s+[a-zA-Z0-9]+\\s*{"; const razorSectionBlock = { begin: sectionBegin, returnBegin: true, returnEnd: true, end: "}", subLanguage: 'cshtml-razor', contains: [ { begin: sectionBegin, className: SPECIAL_SYMBOL_CLASSNAME }, braces, closedBrace ] }; const razorAwait = { begin: "@await ", returnBegin: true, subLanguage: 'csharp', end: "(\\r|\\n|<|\\s)", contains: [ { begin: "@await ", className: SPECIAL_SYMBOL_CLASSNAME }, { begin: "[<\\r\\n]", endsParent: true } ] }; const contains = [ razorDirectives, razorHelperBlock, razorBlock, razorCodeBlock, razorSectionBlock, razorAwait, razorTryBlock, razorEscapeAt, razorTextBlock, razorComment, razorParenthesesBlock, { className: 'meta', begin: '<!DOCTYPE', end: '>', relevance: 10, contains: [{ begin: '\\[', end: '\\]' }] }, { begin: '<\\!\\[CDATA\\[', end: '\\]\\]>', relevance: 10 } ].concat(xmlBlocks); [razorBlock, razorCodeBlock, razorTryBlock] .forEach(function(mode) { const razorModes = contains.filter(function(c) { return c !== mode; }); const replacerIndex = mode.contains.indexOf(CONTENT_REPLACER); mode.contains.splice.apply(mode.contains, [replacerIndex, 1].concat(razorModes)); }); return { aliases: ['cshtml', 'razor', 'razor-cshtml', 'cshtml-razor'], contains }; }; function getXmlBlocks(hljs, additionalBlocks) { const xmlComment = hljs.COMMENT( '<!--', '-->', { relevance: 10 } ); const string = { className: 'string', variants: [ { begin: /"/, end: /"/, contains: additionalBlocks }, { begin: /'/, end: /'/, contains: additionalBlocks }, { begin: /[^\s"'=<>`]+/ } ] }; const xmlTagInternal = { endsWithParent: true, illegal: /</, relevance: 0, contains: [ { className: 'attr', begin: '[A-Za-z0-9\\._:-]+', relevance: 0 }, { begin: /=\s*/, relevance: 0, contains: [string] } ] }; return [ { className: 'meta', begin: '<!DOCTYPE', end: '>', relevance: 10, contains: [{ begin: '\\[', end: '\\]' }] }, xmlComment, { begin: '<\\!\\[CDATA\\[', end: '\\]\\]>', relevance: 10 }, { className: 'meta', begin: /<\?xml/, end: /\?>/, relevance: 10 }, { className: 'tag', begin: '<style(?=\\s|>|$)', end: '>', keywords: { name: 'style' }, contains: [xmlTagInternal], starts: { end: '</style>', returnEnd: true, subLanguage: ['css', 'xml'] } }, { className: 'tag', begin: '<script(?=\\s|>|$)', end: '>', keywords: { name: 'script' }, contains: [xmlTagInternal], starts: { end: '</script>', returnEnd: true, subLanguage: ['actionscript', 'javascript', 'handlebars', 'xml'] } }, { className: 'tag', begin: '</?', end: '/?>', contains: [ { className: 'name', begin: /[^/><\s]+/, relevance: 0 }, xmlTagInternal ] } ].concat(additionalBlocks); }