public async Task <TemplateResult> RunTemplateAsync(string content, dynamic templateModel) { var razorEngine = RazorEngine.Create((builder) => { RazorExtensions.Register(builder); }); // Don't care about the RazorProject as we already have the content of the .cshtml file // and don't need to deal with imports. var razorProject = RazorProject.Create(Directory.GetCurrentDirectory()); var razorTemplateEngine = new RazorTemplateEngine(razorEngine, razorProject); var imports = new RazorSourceDocument[] { RazorSourceDocument.Create(@" @using System @using System.Threading.Tasks ", fileName: null) }; var razorDocument = RazorCodeDocument.Create(RazorSourceDocument.Create(content, "Template"), imports); var generatorResults = razorTemplateEngine.GenerateCode(razorDocument); if (generatorResults.Diagnostics.Any()) { var messages = generatorResults.Diagnostics.Select(d => d.GetMessage()); return(new TemplateResult() { GeneratedText = string.Empty, ProcessingException = new TemplateProcessingException(messages, generatorResults.GeneratedCode) }); } var templateResult = _compilationService.Compile(generatorResults.GeneratedCode); if (templateResult.Messages.Any()) { return(new TemplateResult() { GeneratedText = string.Empty, ProcessingException = new TemplateProcessingException(templateResult.Messages, generatorResults.GeneratedCode) }); } var compiledObject = Activator.CreateInstance(templateResult.CompiledType); var razorTemplate = compiledObject as RazorTemplateBase; string result = String.Empty; if (razorTemplate != null) { razorTemplate.Model = templateModel; //ToDo: If there are errors executing the code, they are missed here. result = await razorTemplate.ExecuteTemplate(); } return(new TemplateResult() { GeneratedText = result, ProcessingException = null }); }
/// <summary> /// Generate a complete C# compilation unit containing a partial class /// with the given name, body contents, and the namespace and all /// usings from the existing code document. /// </summary> /// <param name="className">Name of the resultant partial class.</param> /// <param name="contents">Class body contents.</param> /// <param name="razorCodeDocument">Existing code document we're extracting from.</param> /// <returns></returns> private static string GenerateCodeBehindClass(string className, string contents, RazorCodeDocument razorCodeDocument) { var namespaceNode = (NamespaceDeclarationIntermediateNode)razorCodeDocument .GetDocumentIntermediateNode() .FindDescendantNodes <IntermediateNode>() .FirstOrDefault(n => n is NamespaceDeclarationIntermediateNode); var mock = (ClassDeclarationSyntax)CSharpSyntaxFactory.ParseMemberDeclaration($"class Class {contents}"); var @class = CSharpSyntaxFactory .ClassDeclaration(className) .AddModifiers(CSharpSyntaxFactory.Token(CSharpSyntaxKind.PublicKeyword), CSharpSyntaxFactory.Token(CSharpSyntaxKind.PartialKeyword)) .AddMembers(mock.Members.ToArray()); var @namespace = CSharpSyntaxFactory .NamespaceDeclaration(CSharpSyntaxFactory.ParseName(namespaceNode.Content)) .AddMembers(@class); var usings = FindUsings(razorCodeDocument) .Select(u => CSharpSyntaxFactory.UsingDirective(CSharpSyntaxFactory.ParseName(u))) .ToArray(); var compilationUnit = CSharpSyntaxFactory .CompilationUnit() .AddUsings(usings) .AddMembers(@namespace); return(compilationUnit.NormalizeWhitespace().ToFullString()); }
protected void AssertCSharpDocumentMatchesBaseline(RazorCodeDocument codeDocument) { var document = codeDocument.GetCSharpDocument(); // Normalize newlines to match those in the baseline. var actualCode = document.GeneratedCode.Replace("\r", "").Replace("\n", "\r\n"); var baselineFilePath = GetBaselineFilePath(codeDocument, ".codegen.cs"); var baselineDiagnosticsFilePath = GetBaselineFilePath(codeDocument, ".diagnostics.txt"); var baselineMappingsFilePath = GetBaselineFilePath(codeDocument, ".mappings.txt"); var serializedMappings = SourceMappingsSerializer.Serialize(document, codeDocument.Source); if (GenerateBaselines) { var baselineFullPath = Path.Combine(TestProjectRoot, baselineFilePath); Directory.CreateDirectory(Path.GetDirectoryName(baselineFullPath)); WriteBaseline(actualCode, baselineFullPath); var baselineDiagnosticsFullPath = Path.Combine(TestProjectRoot, baselineDiagnosticsFilePath); var lines = document.Diagnostics.Select(RazorDiagnosticSerializer.Serialize).ToArray(); if (lines.Any()) { WriteBaseline(lines, baselineDiagnosticsFullPath); } else if (File.Exists(baselineDiagnosticsFullPath)) { File.Delete(baselineDiagnosticsFullPath); } var baselineMappingsFullPath = Path.Combine(TestProjectRoot, baselineMappingsFilePath); var text = SourceMappingsSerializer.Serialize(document, codeDocument.Source); if (!string.IsNullOrEmpty(text)) { WriteBaseline(text, baselineMappingsFullPath); } else if (File.Exists(baselineMappingsFullPath)) { File.Delete(baselineMappingsFullPath); } return; } var codegenFile = TestFile.Create(baselineFilePath, GetType().Assembly); if (!codegenFile.Exists()) { throw new XunitException($"The resource {baselineFilePath} was not found."); } var baseline = codegenFile.ReadAllText(); Assert.Equal(baseline, actualCode); var baselineDiagnostics = string.Empty; var diagnosticsFile = TestFile.Create(baselineDiagnosticsFilePath, GetType().Assembly); if (diagnosticsFile.Exists()) { baselineDiagnostics = diagnosticsFile.ReadAllText(); } var actualDiagnostics = string.Concat(document.Diagnostics.Select(d => RazorDiagnosticSerializer.Serialize(d) + "\r\n")); Assert.Equal(baselineDiagnostics, actualDiagnostics); var baselineMappings = string.Empty; var mappingsFile = TestFile.Create(baselineMappingsFilePath, GetType().Assembly); if (mappingsFile.Exists()) { baselineMappings = mappingsFile.ReadAllText(); } var actualMappings = SourceMappingsSerializer.Serialize(document, codeDocument.Source); actualMappings = actualMappings.Replace("\r", "").Replace("\n", "\r\n"); Assert.Equal(baselineMappings, actualMappings); AssertLinePragmas(codeDocument); }
/// <inheritdoc /> protected override void OnDocumentStructureCreated( RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method) { if (!codeDocument.TryComputeNamespaceAndClass(out var computedNamespace, out var computedClass)) { // If we can't compute a nice namespace (no relative path) then just generate something // mangled. computedNamespace = FallbackRootNamespace; var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum()); computedClass = $"AspNetCore_{checksum}"; } if (MangleClassNames) { computedClass = ComponentMetadata.MangleClassName(computedClass); } @namespace.Content = computedNamespace; @class.ClassName = computedClass; @class.Modifiers.Clear(); @class.Modifiers.Add("public"); if (FileKinds.IsComponentImport(codeDocument.GetFileKind())) { // We don't want component imports to be considered as real component. // But we still want to generate code for it so we can get diagnostics. @class.BaseType = typeof(object).FullName; method.ReturnType = "void"; method.MethodName = "Execute"; method.Modifiers.Clear(); method.Modifiers.Add("protected"); method.Parameters.Clear(); } else { @class.BaseType = ComponentsApi.ComponentBase.FullTypeName; var documentNode = codeDocument.GetDocumentIntermediateNode(); var typeParamReferences = documentNode.FindDirectiveReferences(ComponentTypeParamDirective.Directive); for (var i = 0; i < typeParamReferences.Count; i++) { var typeParamNode = (DirectiveIntermediateNode)typeParamReferences[i].Node; if (typeParamNode.HasDiagnostics) { continue; } @class.TypeParameters.Add(new TypeParameter() { ParameterName = typeParamNode.Tokens.First().Content, }); } method.ReturnType = "void"; method.MethodName = ComponentsApi.ComponentBase.BuildRenderTree; method.Modifiers.Clear(); method.Modifiers.Add("protected"); method.Modifiers.Add("override"); method.Parameters.Clear(); method.Parameters.Add(new MethodParameter() { ParameterName = "builder", TypeName = ComponentsApi.RenderTreeBuilder.FullTypeName, }); } }
public override HoverModel GetHoverInfo(RazorCodeDocument codeDocument, SourceSpan location) { if (codeDocument is null) { throw new ArgumentNullException(nameof(codeDocument)); } var syntaxTree = codeDocument.GetSyntaxTree(); var change = new SourceChange(location, ""); var owner = syntaxTree.Root.LocateOwner(change); if (owner == null) { Debug.Fail("Owner should never be null."); return(null); } var parent = owner.Parent; var position = new Position(location.LineIndex, location.CharacterIndex); var tagHelperDocumentContext = codeDocument.GetTagHelperContext(); var ancestors = owner.Ancestors(); var(parentTag, parentIsTagHelper) = _tagHelperFactsService.GetNearestAncestorTagInfo(ancestors); if (_htmlFactsService.TryGetElementInfo(parent, out var containingTagNameToken, out var attributes) && containingTagNameToken.Span.IntersectsWith(location.AbsoluteIndex)) { // Hovering over HTML tag name var stringifiedAttributes = _tagHelperFactsService.StringifyAttributes(attributes); var binding = _tagHelperFactsService.GetTagHelperBinding( tagHelperDocumentContext, containingTagNameToken.Content, stringifiedAttributes, parentTag: parentTag, parentIsTagHelper: parentIsTagHelper); if (binding is null) { // No matching tagHelpers, it's just HTML return(null); } else { Debug.Assert(binding.Descriptors.Count() > 0); var range = GetRangeFromSyntaxNode(containingTagNameToken, codeDocument); var result = ElementInfoToHover(binding.Descriptors, range); return(result); } } if (_htmlFactsService.TryGetAttributeInfo(parent, out containingTagNameToken, out var selectedAttributeName, out attributes) && attributes.Span.IntersectsWith(location.AbsoluteIndex)) { // Hovering over HTML attribute name var stringifiedAttributes = _tagHelperFactsService.StringifyAttributes(attributes); var binding = _tagHelperFactsService.GetTagHelperBinding( tagHelperDocumentContext, containingTagNameToken.Content, stringifiedAttributes, parentTag: parentTag, parentIsTagHelper: parentIsTagHelper); if (binding is null) { // No matching TagHelpers, it's just HTML return(null); } else { Debug.Assert(binding.Descriptors.Count() > 0); var tagHelperAttributes = _tagHelperFactsService.GetBoundTagHelperAttributes(tagHelperDocumentContext, selectedAttributeName, binding); var attribute = attributes.Single(a => a.Span.IntersectsWith(location.AbsoluteIndex)); var range = GetRangeFromSyntaxNode(attribute, codeDocument); var attributeHoverModel = AttributeInfoToHover(tagHelperAttributes, range); return(attributeHoverModel); } } return(null); }
protected virtual MemoryStream CreateAndCompileToStream(string templateSource, RazorEngineCompilationOptions options) { templateSource = this.WriteDirectives(templateSource, options); RazorProjectEngine engine = RazorProjectEngine.Create( RazorConfiguration.Default, RazorProjectFileSystem.Create(@"."), (builder) => { builder.SetNamespace(options.TemplateNamespace); }); string fileName = string.IsNullOrWhiteSpace(options.TemplateFilename) ? Path.GetRandomFileName() : options.TemplateFilename; RazorSourceDocument document = RazorSourceDocument.Create(templateSource, fileName); RazorCodeDocument codeDocument = engine.Process( document, null, new List <RazorSourceDocument>(), new List <TagHelperDescriptor>()); RazorCSharpDocument razorCSharpDocument = codeDocument.GetCSharpDocument(); SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(razorCSharpDocument.GeneratedCode); CSharpCompilation compilation = CSharpCompilation.Create( fileName, new[] { syntaxTree }, options.ReferencedAssemblies .Select(ass => { #if NETSTANDARD2_0 return(MetadataReference.CreateFromFile(ass.Location)); #else unsafe { ass.TryGetRawMetadata(out byte *blob, out int length); ModuleMetadata moduleMetadata = ModuleMetadata.CreateFromMetadata((IntPtr)blob, length); AssemblyMetadata assemblyMetadata = AssemblyMetadata.Create(moduleMetadata); PortableExecutableReference metadataReference = assemblyMetadata.GetReference(); return(metadataReference); } #endif }) .Concat(options.MetadataReferences) .ToList(), new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); MemoryStream memoryStream = new MemoryStream(); EmitResult emitResult = compilation.Emit(memoryStream); if (!emitResult.Success) { RazorEngineCompilationException exception = new RazorEngineCompilationException() { Errors = emitResult.Diagnostics.ToList(), GeneratedCode = razorCSharpDocument.GeneratedCode }; throw exception; } memoryStream.Position = 0; return(memoryStream); }
protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { return(FileKinds.IsComponent(codeDocument.GetFileKind())); }
protected override void ExecuteCore( RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { if (!IsComponentDocument(documentNode)) { return; } if (documentNode.Options.DesignTime) { // Nothing to do during design time. return; } var findVisitor = new FindHtmlTreeVisitor(); findVisitor.Visit(documentNode); var trees = findVisitor.Trees; var rewriteVisitor = new RewriteVisitor(trees); while (trees.Count > 0) { // Walk backwards since we did a postorder traversal. var reference = trees[trees.Count - 1]; // Forcibly remove a node to prevent infinite loops. trees.RemoveAt(trees.Count - 1); // We want to fold together siblings where possible. To do this, first we find // the index of the node we're looking at now - then we need to walk backwards // and identify a set of contiguous nodes we can merge. var start = reference.Parent.Children.Count - 1; for (; start >= 0; start--) { if (ReferenceEquals(reference.Node, reference.Parent.Children[start])) { break; } } // This is the current node. Check if the left sibling is always a candidate // for rewriting. Due to the order we processed the nodes, we know that the // left sibling is next in the list to process if it's a candidate. var end = start; while (start - 1 >= 0) { var candidate = reference.Parent.Children[start - 1]; if (trees.Count == 0 || !ReferenceEquals(trees[trees.Count - 1].Node, candidate)) { // This means the we're out of nodes, or the left sibling is not in the list. break; } // This means that the left sibling is valid to merge. start--; // Remove this since we're combining it. trees.RemoveAt(trees.Count - 1); } // As a degenerate case, don't bother rewriting an single HtmlContent node // It doesn't add any value. if (end - start == 0 && reference.Node is HtmlContentIntermediateNode) { continue; } // Now we know the range of nodes to rewrite (end is inclusive) var length = end + 1 - start; while (length > 0) { // Keep using start since we're removing nodes. var node = reference.Parent.Children[start]; reference.Parent.Children.RemoveAt(start); rewriteVisitor.Visit(node); length--; } reference.Parent.Children.Insert(start, new MarkupBlockIntermediateNode() { Content = rewriteVisitor.Builder.ToString(), }); rewriteVisitor.Builder.Clear(); } }
private TagHelperSemanticRangeVisitor(RazorCodeDocument razorCodeDocument, Range?range) { _semanticRanges = new List <SemanticRange>(); _razorCodeDocument = razorCodeDocument; _range = range; }
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument) { var projectEngine = CreateProjectEngine(); return(Lower(codeDocument, projectEngine)); }
public CompiledCSharpCode(Compilation baseCompilation, RazorCodeDocument codeDocument) { BaseCompilation = baseCompilation; CodeDocument = codeDocument; }
static RazorCodeDocument CreateCodeDocument(string rootDir, string sourceFileName) { string detectedLayout = null; string detectedTagName = null; string detectedModel = null; bool detectedPage = false; List <string> detectedPageGroup = null; using (var ms = new MemoryStream()) { using (var sw = new StreamWriter(ms)) { var filesToIncludeInOrder = ViewImportsFromRootToSameDir(rootDir, sourceFileName) .Concat(new[] { sourceFileName }); var sourceLines = filesToIncludeInOrder.SelectMany(file => File.ReadAllLines(file)).ToList(); string lastInheritsLine = null; var usingNamespaces = new List <string>(); foreach (var line in sourceLines) { const string inheritsLinePrefix = "@inherits "; if (line.StartsWith(inheritsLinePrefix)) { lastInheritsLine = line.Substring(inheritsLinePrefix.Length).Replace("<TModel>", string.Empty); continue; } const string layoutLinePrefix = "@layout "; if (line.StartsWith(layoutLinePrefix)) { detectedLayout = line.Substring(layoutLinePrefix.Length); continue; } const string usingLinePrefix = "@using "; if (line.StartsWith(usingLinePrefix)) { usingNamespaces.Add(line.Substring(usingLinePrefix.Length)); continue; } const string modelLinePrefix = "@model "; if (line.StartsWith(modelLinePrefix)) { detectedModel = line.Substring(modelLinePrefix.Length); continue; } // The @page directive is only used to generate the page models. // Currently, blazor assumes that all razor components are pages. const string pageLinePrefix = "@page"; if (line.StartsWith(pageLinePrefix)) { detectedPage = true; var parameters = new Regex("^\\s*{\\s*([^}]+)}\\s*"); var matches = parameters.Matches(line.Substring(pageLinePrefix.Length)); if (matches.Count > 0) { detectedPageGroup = new List <string>(); foreach (Match match in matches) { var collection = match.Groups; detectedPageGroup.Add(collection[0].Value); } } continue; } var tagNameRegex = new Regex("^\\s*\\@TagName\\(\\s*\\\"([^\\\"]+)\\\"\\s*\\)"); var tagNameMatch = tagNameRegex.Match(line); if (tagNameMatch.Success) { detectedTagName = tagNameMatch.Groups[1].Value; continue; } sw.WriteLine(line); } sw.Flush(); ms.Position = 0; var sourceDoc = RazorSourceDocument.ReadFrom(ms, sourceFileName); var codeDoc = RazorCodeDocument.Create(sourceDoc); codeDoc.Items["DetectedLayout"] = detectedLayout; codeDoc.Items["DetectedTagName"] = detectedTagName; codeDoc.Items["DetectedModel"] = detectedModel; codeDoc.Items["DetectedBaseClass"] = lastInheritsLine; codeDoc.Items["UsingNamespaces"] = usingNamespaces; codeDoc.Items["DetectedPage"] = detectedPage; codeDoc.Items["DetectedPageMatches"] = detectedPageGroup; return(codeDoc); } } }
private static void AddIComponentRazorViewFactoryImplementation(ClassDeclarationIRNode classNode, RazorCodeDocument codeDoc) { // The Activator.CreateInstance feature that I added to the DNA runtime is very basic and doesn't // actually invoke the default constructor of the type being created. It just allocates memory for // the instance and returns it, without having run any constructor. This could be confusing if you // put constructor logic (such as field initializers) in your Razor page, given that we instantiate // it using Activator.CreateInstance. // As a workaround (without actually adding constructor support to Activator.CreateInstance, which // would be nontrivial), the Razor views privately implement an interface IComponentRazorViewFactory // that can return new instances of their own type. We can then just call this with normal .NET code. // This means we allocate memory for two instances of the view even though we're only using one, // but it's not going to matter much as the first instance will just be released to GC immediately. if (classNode.Interfaces == null) { classNode.Interfaces = new List <string>(); } classNode.Interfaces.Add(typeof(IRazorComponentFactory).FullName); var methodStatement = new CSharpStatementIRNode { Parent = classNode, Source = null }; classNode.Children.Add(methodStatement); var model = codeDoc.Items["DetectedModel"]; string content = ""; string isPage = ""; string addItems = ""; if (model != null) { content = $@"Model = new {model}();"; } if ((bool)codeDoc.Items["DetectedPage"]) { isPage += "classNode.IsPage = true;"; } if ((codeDoc.Items["DetectedPageMatches"]) != null) { var list = (List <string>)codeDoc.Items["DetectedPageMatches"]; addItems += "List<string> list = new List<string>();"; foreach (var item in list) { addItems += $"\r\n list.Add(\"{item}\");"; } addItems += "\r\n Items = list;"; } var razorToken = new RazorIRToken { Kind = RazorIRToken.TokenKind.CSharp, Parent = classNode, Content = $@" {typeof(RazorComponent).FullName} {typeof(IRazorComponentFactory).FullName}.{nameof(IRazorComponentFactory.Instantiate)}() {{ {content} var classNode = new {classNode.Name}(); {isPage} {addItems} return classNode; }}" }; methodStatement.Children.Add(razorToken); }
protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { return(ShouldMatch); }
private static DocumentIntermediateNode CreateIRDocument(RazorProjectEngine projectEngine, RazorCodeDocument codeDocument) { for (var i = 0; i < projectEngine.Phases.Count; i++) { var phase = projectEngine.Phases[i]; phase.Execute(codeDocument); if (phase is IRazorIntermediateNodeLoweringPhase) { break; } } return(codeDocument.GetDocumentIntermediateNode()); }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { if (documentNode.Options == null || documentNode.Options.SuppressMetadataAttributes) { // Metadata attributes are turned off (or options not populated), nothing to do. return; } // We need to be able to compute the data we need for the [RazorCompiledItem] attribute - that includes // a full type name, and a document kind, and optionally an identifier. // // If we can't use [RazorCompiledItem] then we don't care about the rest of the attributes. var @namespace = documentNode.FindPrimaryNamespace(); if (@namespace == null || string.IsNullOrEmpty(@namespace.Content)) { // No namespace node or it's incomplete. Skip. return; } var @class = documentNode.FindPrimaryClass(); if (@class == null || string.IsNullOrEmpty(@class.ClassName)) { // No class node or it's incomplete. Skip. return; } if (documentNode.DocumentKind == null) { // No document kind. Skip. return; } var identifier = codeDocument.GetIdentifier(); if (identifier == null) { // No identifier. Skip return; } // [RazorCompiledItem] is an [assembly: ... ] attribute, so it needs to be applied at the global scope. documentNode.Children.Insert(0, new RazorCompiledItemAttributeIntermediateNode() { TypeName = @namespace.Content + "." + @class.ClassName, Kind = documentNode.DocumentKind, Identifier = identifier, }); // Now we need to add a [RazorSourceChecksum] for the source and for each import // these are class attributes, so we need to find the insertion point to put them // right before the class. var insert = (int?)null; for (var j = 0; j < @namespace.Children.Count; j++) { if (object.ReferenceEquals(@namespace.Children[j], @class)) { insert = j; break; } } if (insert == null) { // Can't find a place to put the attributes, just bail. return; } // Checksum of the main source AddChecksum(codeDocument.Source.GetChecksum(), codeDocument.Source.GetChecksumAlgorithm(), identifier); // Now process the checksums of the imports // // It's possible that the counts of these won't match, just process as many as we can. var importIdentifiers = codeDocument.GetImportIdentifiers() ?? Array.Empty <string>(); for (var i = 0; i < codeDocument.Imports.Count && i < importIdentifiers.Count; i++) { var import = codeDocument.Imports[i]; AddChecksum(import.GetChecksum(), import.GetChecksumAlgorithm(), importIdentifiers[i]); } void AddChecksum(byte[] checksum, string checksumAlgorithm, string id) { if (checksum == null || checksum.Length == 0 || checksumAlgorithm == null || id == null) { // Don't generate anything unless we have all of the required information. return; } // Checksum of the main source @namespace.Children.Insert((int)insert++, new RazorSourceChecksumAttributeIntermediateNode() { Checksum = checksum, ChecksumAlgorithm = checksumAlgorithm, Identifier = id, }); } }
private RazorCodeDocument CreateDocument(string content) { var source = RazorSourceDocument.Create(content, "test.cshtml"); return(RazorCodeDocument.Create(source)); }
public abstract RazorCSharpDocument WriteDocument(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode);
private static Type CreateGeneratorType(Type _) { var name = typeof(T).FullName; var metadataReferences = GetMetadataReferences(); RazorSourceDocument document; using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{name}.razor")) { if (stream == null) { throw new Exception($"Could not find embedded resource {name}.razor"); } using (var reader = new StreamReader(stream)) { document = RazorSourceDocument.Create(reader.ReadToEnd(), $"{name}.razor"); } } #pragma warning disable CS0618 //Create and Register are marked as obsolete but there aren't alternative available var engine = RazorEngine.Create(b => { FunctionsDirective.Register(b); InheritsDirective.Register(b); #pragma warning restore CS0618 }); RazorCodeDocument codeDocument = RazorCodeDocument.Create(document); engine.Process(codeDocument); string code; using (var srcFileWriter = new StringWriter()) { code = codeDocument.GetCSharpDocument().GeneratedCode; } SourceText sourceText = SourceText.From(code, Encoding.UTF8); SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(sourceText, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest)); CSharpCompilationOptions compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) .WithSpecificDiagnosticOptions(new Dictionary <string, ReportDiagnostic> { // Binding redirects ["CS1701"] = ReportDiagnostic.Suppress, ["CS1702"] = ReportDiagnostic.Suppress, ["CS1705"] = ReportDiagnostic.Suppress, ["CS8019"] = ReportDiagnostic.Suppress }) .WithUsings("System"); CSharpCompilation compilation = CSharpCompilation.Create(typeof(T).FullName, new List <SyntaxTree> { syntaxTree }, metadataReferences, compilationOptions); Assembly assembly; EmitOptions emitOptions = new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb); using (MemoryStream assemblyStream = new MemoryStream()) { using (MemoryStream pdbStream = new MemoryStream()) { var emitResult = compilation.Emit( assemblyStream, pdbStream, options: emitOptions); if (!emitResult.Success) { throw new Exception("Compilation error: " + string.Join("; ", emitResult.Diagnostics.Select(d => d.ToString()))); } assemblyStream.Seek(0, SeekOrigin.Begin); pdbStream.Seek(0, SeekOrigin.Begin); assembly = Assembly.Load(assemblyStream.ToArray(), pdbStream.ToArray()); } } var generatorType = assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(T))).Single(); return(generatorType); }
public DefaultCodeRenderingContext( CodeWriter codeWriter, IntermediateNodeWriter nodeWriter, RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode, RazorCodeGenerationOptions options) { if (codeWriter == null) { throw new ArgumentNullException(nameof(codeWriter)); } if (nodeWriter == null) { throw new ArgumentNullException(nameof(nodeWriter)); } if (codeDocument == null) { throw new ArgumentNullException(nameof(codeDocument)); } if (documentNode == null) { throw new ArgumentNullException(nameof(documentNode)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } CodeWriter = codeWriter; _codeDocument = codeDocument; _documentNode = documentNode; Options = options; _ancestors = new Stack <IntermediateNode>(); Diagnostics = new RazorDiagnosticCollection(); Items = new ItemCollection(); SourceMappings = new List <SourceMapping>(); LinePragmas = new List <LinePragma>(); var diagnostics = _documentNode.GetAllDiagnostics(); for (var i = 0; i < diagnostics.Count; i++) { Diagnostics.Add(diagnostics[i]); } var newLineString = codeDocument.Items[NewLineString]; if (newLineString != null) { // Set new line character to a specific string regardless of platform, for testing purposes. codeWriter.NewLine = (string)newLineString; } Items[NewLineString] = codeDocument.Items[NewLineString]; Items[SuppressUniqueIds] = codeDocument.Items[SuppressUniqueIds]; _scopes = new List <ScopeInternal>(); _scopes.Add(new ScopeInternal(nodeWriter)); }
protected override CodeTarget CreateTarget(RazorCodeDocument codeDocument, RazorCodeGenerationOptions options) { return(new ComponentCodeTarget(options, TargetExtensions)); }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { var visitor = new Visitor(); visitor.Visit(documentNode); }
private async Task <(RazorCodeDocument, VersionStamp, VersionStamp)> GetGeneratedOutputAndVersionCoreAsync(DefaultProjectSnapshot project, DocumentSnapshot document) { // We only need to produce the generated code if any of our inputs is newer than the // previously cached output. // // First find the versions that are the inputs: // - The project + computed state // - The imports // - This document // // All of these things are cached, so no work is wasted if we do need to generate the code. var computedStateVersion = await project.State.GetComputedStateVersionAsync(project).ConfigureAwait(false); var documentCollectionVersion = project.State.DocumentCollectionVersion; var imports = await GetImportsAsync(project, document).ConfigureAwait(false); var documentVersion = await document.GetTextVersionAsync().ConfigureAwait(false); // OK now that have the previous output and all of the versions, we can see if anything // has changed that would require regenerating the code. var inputVersion = documentVersion; if (inputVersion.GetNewerVersion(computedStateVersion) == computedStateVersion) { inputVersion = computedStateVersion; } if (inputVersion.GetNewerVersion(documentCollectionVersion) == documentCollectionVersion) { inputVersion = documentCollectionVersion; } for (var i = 0; i < imports.Count; i++) { var importVersion = imports[i].Version; if (inputVersion.GetNewerVersion(importVersion) == importVersion) { inputVersion = importVersion; } } RazorCodeDocument olderOutput = null; var olderInputVersion = default(VersionStamp); var olderOutputVersion = default(VersionStamp); if (_older?.TaskUnsafe != null) { (olderOutput, olderInputVersion, olderOutputVersion) = await _older.TaskUnsafe.ConfigureAwait(false); if (inputVersion.GetNewerVersion(olderInputVersion) == olderInputVersion) { // Nothing has changed, we can use the cached result. lock (_lock) { TaskUnsafe = _older.TaskUnsafe; _older = null; return(olderOutput, olderInputVersion, olderOutputVersion); } } } // OK we have to generate the code. var tagHelpers = await project.GetTagHelpersAsync().ConfigureAwait(false); var importSources = new List <RazorSourceDocument>(); foreach (var item in imports) { var sourceDocument = await GetRazorSourceDocumentAsync(item.Document).ConfigureAwait(false); importSources.Add(sourceDocument); } var documentSource = await GetRazorSourceDocumentAsync(document).ConfigureAwait(false); var projectEngine = project.GetProjectEngine(); var codeDocument = projectEngine.ProcessDesignTime(documentSource, fileKind: document.FileKind, importSources, tagHelpers); var csharpDocument = codeDocument.GetCSharpDocument(); // OK now we've generated the code. Let's check if the output is actually different. This is // a valuable optimization for our use cases because lots of changes you could make require // us to run code generation, but don't change the result. // // Note that we're talking about the effect on the generated C# code here (not the other artifacts). // This is the reason why we have two versions associated with the output. // // The INPUT version is related the .cshtml files and tag helpers // The OUTPUT version is related to the generated C#. // // Examples: // // A change to a tag helper not used by this document - updates the INPUT version, but not // the OUTPUT version. // // A change in the HTML - updates the INPUT version, but not the OUTPUT version. // // // Razor IDE features should always retrieve the output and party on it regardless. Depending // on the use cases we may or may not need to synchronize the output. var outputVersion = inputVersion; if (olderOutput != null) { if (string.Equals( olderOutput.GetCSharpDocument().GeneratedCode, csharpDocument.GeneratedCode, StringComparison.Ordinal)) { outputVersion = olderOutputVersion; } } if (document is DefaultDocumentSnapshot defaultDocument) { defaultDocument.State.HostDocument.GeneratedCodeContainer.SetOutput( defaultDocument, csharpDocument, inputVersion, outputVersion); } return(codeDocument, inputVersion, outputVersion); }
public static FormattingContext Create( DocumentUri uri, DocumentSnapshot originalSnapshot, RazorCodeDocument codeDocument, FormattingOptions options, Range range = null, bool isFormatOnType = false) { if (uri is null) { throw new ArgumentNullException(nameof(uri)); } if (originalSnapshot is null) { throw new ArgumentNullException(nameof(originalSnapshot)); } if (codeDocument is null) { throw new ArgumentNullException(nameof(codeDocument)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } var text = codeDocument.GetSourceText(); range ??= TextSpan.FromBounds(0, text.Length).AsRange(text); var result = new FormattingContext() { Uri = uri, OriginalSnapshot = originalSnapshot, CodeDocument = codeDocument, Range = range, Options = options, IsFormatOnType = isFormatOnType }; var source = codeDocument.Source; var syntaxTree = codeDocument.GetSyntaxTree(); var formattingSpans = syntaxTree.GetFormattingSpans(); var indentations = new Dictionary <int, IndentationContext>(); var total = 0; var previousIndentationLevel = 0; for (var i = 0; i < source.Lines.Count; i++) { // Get first non-whitespace character position var lineLength = source.Lines.GetLineLength(i); var nonWsChar = 0; for (var j = 0; j < lineLength; j++) { var ch = source[total + j]; if (!char.IsWhiteSpace(ch) && !ParserHelpers.IsNewLine(ch)) { nonWsChar = j; break; } } // position now contains the first non-whitespace character or 0. Get the corresponding FormattingSpan. if (TryGetFormattingSpan(total + nonWsChar, formattingSpans, out var span)) { indentations[i] = new IndentationContext { Line = i, IndentationLevel = span.IndentationLevel, RelativeIndentationLevel = span.IndentationLevel - previousIndentationLevel, ExistingIndentation = nonWsChar, FirstSpan = span, }; previousIndentationLevel = span.IndentationLevel; } else { // Couldn't find a corresponding FormattingSpan. Happens if it is a 0 length line. // Let's create a 0 length span to represent this and default it to HTML. var placeholderSpan = new FormattingSpan( new Language.Syntax.TextSpan(total + nonWsChar, 0), new Language.Syntax.TextSpan(total + nonWsChar, 0), FormattingSpanKind.Markup, FormattingBlockKind.Markup, indentationLevel: 0, isInClassBody: false); indentations[i] = new IndentationContext { Line = i, IndentationLevel = 0, RelativeIndentationLevel = previousIndentationLevel, ExistingIndentation = nonWsChar, FirstSpan = placeholderSpan, }; } total += lineLength; } result.Indentations = indentations; return(result); }
public void Process(RazorCodeDocument document) => _engine.Process(document);
public override Task <TextEdit[]> FormatOnTypeAsync(Uri uri, RazorCodeDocument codeDocument, Position position, string character, FormattingOptions options) { if (uri is null) { throw new ArgumentNullException(nameof(uri)); } if (codeDocument is null) { throw new ArgumentNullException(nameof(codeDocument)); } if (position is null) { throw new ArgumentNullException(nameof(position)); } if (string.IsNullOrEmpty(character)) { throw new ArgumentNullException(nameof(character)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } if (!_optionsMonitor.CurrentValue.AutoClosingTags || character != ">") { // We currently only support auto-closing tags our onType formatter. return(Task.FromResult(EmptyArray)); } bool addCursorPlaceholder; if (options.TryGetValue(LanguageServerConstants.ExpectsCursorPlaceholderKey, out var value) && value.IsBool) { addCursorPlaceholder = value.Bool; } else { // Temporary: // no-op if cursor placeholder isn't supported. This means the request isn't coming from VS. // Can remove this once VSCode starts using this endpoint for auto closing <text> tags. return(Task.FromResult(EmptyArray)); } var formattingContext = CreateFormattingContext(uri, codeDocument, new Range(position, position), options); if (IsAtTextTag(formattingContext, position)) { // This is a text tag. var cursorPlaceholder = addCursorPlaceholder ? LanguageServerConstants.CursorPlaceholderString : string.Empty; var edit = new TextEdit() { NewText = $"{cursorPlaceholder}</{SyntaxConstants.TextTagName}>", Range = new Range(position, position) }; return(Task.FromResult(new[] { edit })); } return(Task.FromResult(EmptyArray)); }
private static FormattingContext CreateFormattingContext(Uri uri, RazorCodeDocument codedocument, Range range, FormattingOptions options) { var result = new FormattingContext() { Uri = uri, CodeDocument = codedocument, Range = range, Options = options }; var source = codedocument.Source; var syntaxTree = codedocument.GetSyntaxTree(); var formattingSpans = syntaxTree.GetFormattingSpans(); var total = 0; var previousIndentationLevel = 0; for (var i = 0; i < source.Lines.Count; i++) { // Get first non-whitespace character position var lineLength = source.Lines.GetLineLength(i); var nonWsChar = 0; for (var j = 0; j < lineLength; j++) { var ch = source[total + j]; if (!char.IsWhiteSpace(ch) && !ParserHelpers.IsNewLine(ch)) { nonWsChar = j; break; } } // position now contains the first non-whitespace character or 0. Get the corresponding FormattingSpan. if (TryGetFormattingSpan(total + nonWsChar, formattingSpans, out var span)) { result.Indentations[i] = new IndentationContext { Line = i, IndentationLevel = span.IndentationLevel, RelativeIndentationLevel = span.IndentationLevel - previousIndentationLevel, ExistingIndentation = nonWsChar, FirstSpan = span, }; previousIndentationLevel = span.IndentationLevel; } else { // Couldn't find a corresponding FormattingSpan. result.Indentations[i] = new IndentationContext { Line = i, IndentationLevel = -1, RelativeIndentationLevel = previousIndentationLevel, ExistingIndentation = nonWsChar, }; } total += lineLength; } return(result); }
public abstract bool TryGetFromDocument(TextDocument document, out RazorCodeDocument codeDocument);
protected override bool IsMatch(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { return(PageDirective.TryGetPageDirective(documentNode, out var pageDirective)); }
public abstract bool TryGetGeneratedOutput(out RazorCodeDocument result);