/// <summary> /// Parses the template specified by <paramref name="codeDocument"/>. /// </summary> /// <param name="codeDocument">The <see cref="RazorProjectItem"/>.</param> /// <returns>The <see cref="RazorCSharpDocument"/>.</returns> public virtual RazorCSharpDocument GenerateCode(RazorCodeDocument codeDocument) { if (codeDocument == null) { throw new ArgumentNullException(nameof(codeDocument)); } Engine.Process(codeDocument); return(codeDocument.GetCSharpDocument()); }
private 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 = Path.GetRandomFileName(); 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 => MetadataReference.CreateFromFile(ass.Location)) .ToList(), new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); MemoryStream memoryStream = new MemoryStream(); EmitResult emitResult = compilation.Emit(memoryStream); if (!emitResult.Success) { List <Diagnostic> errors = emitResult.Diagnostics.ToList(); RazorEngineCompilationException exception = new RazorEngineCompilationException($"Unable to compile template: {errors?.FirstOrDefault()}"); exception.Errors = errors; throw exception; } memoryStream.Position = 0; return(memoryStream); }
private TemplateFactoryResult CompilationError(RazorCodeDocument document, CompilationResult result) { var csdocs = document.GetCSharpDocument(); return(TemplateFactoryResult.Error(new StringValues(new string[] { document.Source.FilePath + ".error", string.Join( Environment.NewLine, result.Messages), csdocs.GeneratedCode }).ToString())); }
private TemplateFactoryResult GenerateError(RazorCodeDocument document) { var csdocs = document.GetCSharpDocument(); return(TemplateFactoryResult.Error(new StringValues(new string[] { document.Source.FilePath + ".error", string.Join( Environment.NewLine, csdocs.Diagnostics.Select(d => d.GetMessage())), csdocs.GeneratedCode }).ToString())); }
// Internal for testing only internal static bool TryGetMinimalCSharpRange(RazorCodeDocument codeDocument, Range razorRange, [NotNullWhen(true)] out Range?csharpRange) { SourceSpan?minGeneratedSpan = null; SourceSpan?maxGeneratedSpan = null; var sourceText = codeDocument.GetSourceText(); var textSpan = razorRange.AsTextSpan(sourceText); var csharpDoc = codeDocument.GetCSharpDocument(); // We want to find the min and max C# source mapping that corresponds with our Razor range. foreach (var mapping in csharpDoc.SourceMappings) { var mappedTextSpan = mapping.OriginalSpan.AsTextSpan(); if (textSpan.OverlapsWith(mappedTextSpan)) { if (minGeneratedSpan is null || mapping.GeneratedSpan.AbsoluteIndex < minGeneratedSpan.Value.AbsoluteIndex) { minGeneratedSpan = mapping.GeneratedSpan; } var mappingEndIndex = mapping.GeneratedSpan.AbsoluteIndex + mapping.GeneratedSpan.Length; if (maxGeneratedSpan is null || mappingEndIndex > maxGeneratedSpan.Value.AbsoluteIndex + maxGeneratedSpan.Value.Length) { maxGeneratedSpan = mapping.GeneratedSpan; } } } // Create a new projected range based on our calculated min/max source spans. if (minGeneratedSpan is not null && maxGeneratedSpan is not null) { var csharpSourceText = codeDocument.GetCSharpSourceText(); var startRange = minGeneratedSpan.Value.AsTextSpan().AsRange(csharpSourceText); var endRange = maxGeneratedSpan.Value.AsTextSpan().AsRange(csharpSourceText); csharpRange = new Range { Start = startRange.Start, End = endRange.End }; Debug.Assert(csharpRange.Start <= csharpRange.End, "Range.Start should not be larger than Range.End"); return(true); } csharpRange = null; return(false); }
static IEnumerable<ITagSpan<SourceMappingTag>> GetTagsWorker(RazorCodeDocument codeDocument, ITextSnapshot snapshot) { var csharpDocument = codeDocument.GetCSharpDocument(); foreach (var mapping in csharpDocument.SourceMappings) { var generatedText = GetGeneratedCodeSnippet(csharpDocument.GeneratedCode, mapping.GeneratedSpan.AbsoluteIndex); var position = Math.Min(mapping.OriginalSpan.AbsoluteIndex, snapshot.Length); var point = new SnapshotPoint(snapshot, position); var tag = new SourceMappingTag(isStart: true, generatedText); var span = new SnapshotSpan(point, 0); yield return new TagSpan<SourceMappingTag>(span, tag); position = Math.Min(mapping.OriginalSpan.AbsoluteIndex + mapping.OriginalSpan.Length, snapshot.Length); point = new SnapshotPoint(snapshot, position); tag = new SourceMappingTag(isStart: false); span = new SnapshotSpan(point, 0); yield return new TagSpan<SourceMappingTag>(span, tag); }
public static SourceText GetCSharpSourceText(this RazorCodeDocument document) { if (document == null) { throw new ArgumentNullException(nameof(document)); } var sourceTextObj = document.Items[CSharpSourceTextKey]; if (sourceTextObj == null) { var csharpDocument = document.GetCSharpDocument(); var sourceText = SourceText.From(csharpDocument.GeneratedCode); document.Items[CSharpSourceTextKey] = sourceText; return(sourceText); } return((SourceText)sourceTextObj); }
private static async Task <TextEdit[]> GetFormattedCSharpEditsAsync( RazorCodeDocument codeDocument, char typedChar, int position, bool insertSpaces, int tabSize) { var generatedCode = codeDocument.GetCSharpDocument().GeneratedCode; var csharpSourceText = SourceText.From(generatedCode); var document = GenerateRoslynCSharpDocument(csharpSourceText); var documentOptions = await GetDocumentOptionsAsync(document, insertSpaces, tabSize); var formattingChanges = await RazorCSharpFormattingInteractionService.GetFormattingChangesAsync( document, typedChar, position, documentOptions, CancellationToken.None).ConfigureAwait(false); var textEdits = formattingChanges.Select(change => change.AsTextEdit(csharpSourceText)).ToArray(); return(textEdits); Document GenerateRoslynCSharpDocument(SourceText csharpSourceText) { var workspace = TestWorkspace.Create(); var project = workspace.CurrentSolution.AddProject("TestProject", "TestAssembly", LanguageNames.CSharp); var document = project.AddDocument("TestDocument", csharpSourceText); return(document); } async Task <DocumentOptionSet> GetDocumentOptionsAsync(Document document, bool insertSpaces, int tabSize) { var documentOptions = await document.GetOptionsAsync().ConfigureAwait(false); documentOptions = documentOptions.WithChangedOption(CodeAnalysis.Formatting.FormattingOptions.TabSize, tabSize) .WithChangedOption(CodeAnalysis.Formatting.FormattingOptions.IndentationSize, tabSize) .WithChangedOption(CodeAnalysis.Formatting.FormattingOptions.UseTabs, !insertSpaces); return(documentOptions); } }
public bool TrySetOutput( DefaultDocumentSnapshot document, RazorCodeDocument codeDocument, VersionStamp inputVersion, VersionStamp outputCSharpVersion, VersionStamp outputHtmlVersion) { lock (_setOutputLock) { if (_inputVersion.HasValue && _inputVersion.Value != inputVersion && _inputVersion == _inputVersion.Value.GetNewerVersion(inputVersion)) { // Latest document is newer than the provided document. return(false); } if (!document.TryGetText(out var source)) { Debug.Fail("The text should have already been evaluated."); return(false); } _source = source; _inputVersion = inputVersion; _outputCSharpVersion = outputCSharpVersion; _outputHtmlVersion = outputHtmlVersion; _outputCSharp = codeDocument.GetCSharpDocument(); _outputHtml = codeDocument.GetHtmlDocument(); _latestDocument = document; var csharpSourceText = codeDocument.GetCSharpSourceText(); _csharpTextContainer.SetText(csharpSourceText); var htmlSourceText = codeDocument.GetHtmlSourceText(); _htmlTextContainer.SetText(htmlSourceText); return(true); } }
private void AssertDocumentCompiles( RazorCodeDocument document, IEnumerable <MetadataReference> compilationReferences, IEnumerable <string> expectedErrors = null) { var cSharp = document.GetCSharpDocument().GeneratedCode; var syntaxTree = CSharpSyntaxTree.ParseText(cSharp); var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); var compilation = CSharpCompilation.Create("CodeGenerationTestAssembly", new[] { syntaxTree }, compilationReferences, options); var diagnostics = compilation.GetDiagnostics(); var errors = diagnostics.Where(d => d.Severity >= DiagnosticSeverity.Warning); if (expectedErrors == null) { Assert.Empty(errors.Select(e => e.GetMessage())); } else { Assert.Equal(expectedErrors, errors.Select(e => e.GetMessage())); } }
public override bool TryMapFromProjectedDocumentRange(RazorCodeDocument codeDocument, Range projectedRange, out Range originalRange) { if (codeDocument is null) { throw new ArgumentNullException(nameof(codeDocument)); } if (projectedRange is null) { throw new ArgumentNullException(nameof(projectedRange)); } originalRange = default; var csharpSourceText = SourceText.From(codeDocument.GetCSharpDocument().GeneratedCode); var range = projectedRange; var startIndex = range.Start.GetAbsoluteIndex(csharpSourceText); if (!TryMapFromProjectedDocumentPosition(codeDocument, startIndex, out var hostDocumentStart, out var _)) { return(false); } var endIndex = range.End.GetAbsoluteIndex(csharpSourceText); if (!TryMapFromProjectedDocumentPosition(codeDocument, endIndex, out var hostDocumentEnd, out var _)) { return(false); } originalRange = new Range( hostDocumentStart, hostDocumentEnd); return(true); }
private async Task <ICollection <RazorToCSharpModel> > ConvertRazorToCSharp(ICollection <ProjectFile> codeFiles) { // The first phase won't include any metadata references for component discovery. This mirrors what the build does. Console.WriteLine($"ConvertRazorToCSharp: {string.Join("\r\n", codeFiles.Select(x => x.Name))}"); var projectEngine = CreateRazorProjectEngine(Array.Empty <MetadataReference>()); var declarations = new List <RazorToCSharpModel>(); foreach (var codeFile in codeFiles.Where(x => x.Name.EndsWith(".razor"))) { RazorProjectItem projectItem = CreateRazorProjectItem(codeFile.Name, codeFile.Content); RazorCodeDocument codeDocument = projectEngine.ProcessDeclarationOnly(projectItem); RazorCSharpDocument cSharpDocument = codeDocument.GetCSharpDocument(); declarations.Add(new RazorToCSharpModel { ProjectItem = projectItem, Code = cSharpDocument.GeneratedCode, Diagnostics = cSharpDocument.Diagnostics.Select(diagnostic => new CustomDiag(diagnostic)), }); } //ToDo Likely Solution foreach (var codeFile in codeFiles.Where(x => x.Name.EndsWith(".cs"))) { declarations.Add(new RazorToCSharpModel { Code = codeFile.Content, Diagnostics = new List <CustomDiag>() }); } // Get initial core assembly var tempAssembly = GetCodeAssembly(declarations); if (tempAssembly.Diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error)) { return(new[] { new RazorToCSharpModel { Diagnostics = tempAssembly.Diagnostics } }); } // add new assemblies to project refs var references = new List <MetadataReference>(_baseCompilation.References) { tempAssembly.Compilation.ToMetadataReference() }; projectEngine = this.CreateRazorProjectEngine(references); var results = new List <RazorToCSharpModel>(); // Iterates through c# code docs and converts to members of generated Razor project foreach (var declaration in declarations.Where(x => x.ProjectItem != null)) { var codeDocument = projectEngine.Process(declaration.ProjectItem); var cSharpDocument = codeDocument.GetCSharpDocument(); results.Add(new RazorToCSharpModel { ProjectItem = declaration.ProjectItem, Code = cSharpDocument.GeneratedCode, Diagnostics = cSharpDocument.Diagnostics.Select(x => new CustomDiag(x)) }); } foreach (var declaration in declarations.Where(x => x.ProjectItem == null)) { results.Add(new RazorToCSharpModel { Code = declaration.Code, Diagnostics = new List <CustomDiag>() }); } return(results); }
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); }
private bool TryMapFromProjectedDocumentRangeInclusive(RazorCodeDocument codeDocument, Range projectedRange, out Range originalRange) { originalRange = default; var csharpDoc = codeDocument.GetCSharpDocument(); var csharpSourceText = SourceText.From(csharpDoc.GeneratedCode); var projectedRangeAsSpan = projectedRange.AsTextSpan(csharpSourceText); var range = projectedRange; var startIndex = projectedRangeAsSpan.Start; var startMappedDirectly = TryMapFromProjectedDocumentPosition(codeDocument, startIndex, out var hostDocumentStart, out _); var endIndex = projectedRangeAsSpan.End; var endMappedDirectly = TryMapFromProjectedDocumentPosition(codeDocument, endIndex, out var hostDocumentEnd, out _); if (startMappedDirectly && endMappedDirectly) { // We strictly mapped the start/end of the projected range. originalRange = new Range(hostDocumentStart, hostDocumentEnd); return(true); } List <SourceMapping> candidateMappings; if (startMappedDirectly) { // Start of projected range intersects with a mapping candidateMappings = csharpDoc.SourceMappings.Where(mapping => IntersectsWith(startIndex, mapping.GeneratedSpan)).ToList(); } else if (endMappedDirectly) { // End of projected range intersects with a mapping candidateMappings = csharpDoc.SourceMappings.Where(mapping => IntersectsWith(endIndex, mapping.GeneratedSpan)).ToList(); } else { // Our range does not intersect with any mapping; we should see if it overlaps generated locations candidateMappings = csharpDoc.SourceMappings.Where(mapping => Overlaps(projectedRangeAsSpan, mapping.GeneratedSpan)).ToList(); } if (candidateMappings.Count == 1) { // We're intersecting or overlapping a single mapping, lets choose that. var mapping = candidateMappings[0]; originalRange = ConvertMapping(codeDocument.Source, mapping); return(true); } else { // More then 1 or exactly 0 intersecting/overlapping mappings return(false); } bool Overlaps(TextSpan projectedRangeAsSpan, SourceSpan span) { var overlapStart = Math.Max(projectedRangeAsSpan.Start, span.AbsoluteIndex); var overlapEnd = Math.Min(projectedRangeAsSpan.End, span.AbsoluteIndex + span.Length); return(overlapStart < overlapEnd); } bool IntersectsWith(int position, SourceSpan span) { return(unchecked ((uint)(position - span.AbsoluteIndex) <= (uint)span.Length)); }
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); }
private async Task <(RazorCodeDocument, VersionStamp, 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 configurationVersion = project.State.ConfigurationVersion; var projectWorkspaceStateVersion = project.State.ProjectWorkspaceStateVersion; 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(configurationVersion) == configurationVersion) { inputVersion = configurationVersion; } if (inputVersion.GetNewerVersion(projectWorkspaceStateVersion) == projectWorkspaceStateVersion) { inputVersion = projectWorkspaceStateVersion; } 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 olderCSharpOutputVersion = default(VersionStamp); var olderHtmlOutputVersion = default(VersionStamp); if (_older?.TaskUnsafeReference != null && _older.TaskUnsafeReference.TryGetTarget(out var taskUnsafe)) { (olderOutput, olderInputVersion, olderCSharpOutputVersion, olderHtmlOutputVersion) = await taskUnsafe.ConfigureAwait(false); if (inputVersion.GetNewerVersion(olderInputVersion) == olderInputVersion) { // Nothing has changed, we can use the cached result. lock (_lock) { TaskUnsafeReference = _older.TaskUnsafeReference; _older = null; return(olderOutput, olderInputVersion, olderCSharpOutputVersion, olderHtmlOutputVersion); } } } // OK we have to generate the code. var importSources = new List <RazorSourceDocument>(); var projectEngine = project.GetProjectEngine(); foreach (var item in imports) { var importProjectItem = item.FilePath == null ? null : projectEngine.FileSystem.GetItem(item.FilePath, item.FileKind); var sourceDocument = await GetRazorSourceDocumentAsync(item.Document, importProjectItem).ConfigureAwait(false); importSources.Add(sourceDocument); } var projectItem = document.FilePath == null ? null : projectEngine.FileSystem.GetItem(document.FilePath, document.FileKind); var documentSource = await GetRazorSourceDocumentAsync(document, projectItem).ConfigureAwait(false); var codeDocument = projectEngine.ProcessDesignTime(documentSource, fileKind: document.FileKind, importSources, project.TagHelpers); var csharpDocument = codeDocument.GetCSharpDocument(); var htmlDocument = codeDocument.GetHtmlDocument(); // 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#/HTML here (not the other artifacts). // This is the reason why we have three versions associated with the document. // // The INPUT version is related the .cshtml files and tag helpers // The CSHARPOUTPUT version is related to the generated C# // The HTMLOUTPUT version is related to the generated HTML // // Examples: // // A change to a tag helper not used by this document - 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 outputCSharpVersion = inputVersion; var outputHtmlVersion = inputVersion; if (olderOutput != null) { if (string.Equals( olderOutput.GetCSharpDocument().GeneratedCode, csharpDocument.GeneratedCode, StringComparison.Ordinal)) { outputCSharpVersion = olderCSharpOutputVersion; } if (string.Equals( olderOutput.GetHtmlDocument().GeneratedHtml, htmlDocument.GeneratedHtml, StringComparison.Ordinal)) { outputHtmlVersion = olderHtmlOutputVersion; } } if (document is DefaultDocumentSnapshot defaultDocument) { defaultDocument.State.HostDocument.GeneratedDocumentContainer.SetOutput( defaultDocument, csharpDocument, htmlDocument, inputVersion, outputCSharpVersion, outputHtmlVersion); } return(codeDocument, inputVersion, outputCSharpVersion, outputHtmlVersion); }
protected void AssertSourceMappingsMatchBaseline(RazorCodeDocument codeDocument) { if (FileName == null) { var message = $"{nameof(AssertSourceMappingsMatchBaseline)} should only be called from an integration test ({nameof(FileName)} is null)."; throw new InvalidOperationException(message); } var csharpDocument = codeDocument.GetCSharpDocument(); Assert.NotNull(csharpDocument); var baselineFileName = Path.ChangeExtension(FileName, ".mappings.txt"); var serializedMappings = SourceMappingsSerializer.Serialize(csharpDocument, codeDocument.Source); if (GenerateBaselines) { var baselineFullPath = Path.Combine(TestProjectRoot, baselineFileName); File.WriteAllText(baselineFullPath, serializedMappings); return; } var testFile = TestFile.Create(baselineFileName, GetType().GetTypeInfo().Assembly); if (!testFile.Exists()) { throw new XunitException($"The resource {baselineFileName} was not found."); } var baseline = testFile.ReadAllText(); // Normalize newlines to match those in the baseline. var actualBaseline = serializedMappings.Replace("\r", "").Replace("\n", "\r\n"); Assert.Equal(baseline, actualBaseline); var syntaxTree = codeDocument.GetSyntaxTree(); var visitor = new CodeSpanVisitor(); visitor.Visit(syntaxTree.Root); var charBuffer = new char[codeDocument.Source.Length]; codeDocument.Source.CopyTo(0, charBuffer, 0, codeDocument.Source.Length); var sourceContent = new string(charBuffer); var spans = visitor.CodeSpans; for (var i = 0; i < spans.Count; i++) { var span = spans[i]; var sourceSpan = span.GetSourceSpan(codeDocument.Source); if (sourceSpan == null) { // Not in the main file, skip. continue; } var expectedSpan = sourceContent.Substring(sourceSpan.AbsoluteIndex, sourceSpan.Length); // See #2593 if (string.IsNullOrWhiteSpace(expectedSpan)) { // For now we don't verify whitespace inside of a directive. We know that directives cheat // with how they bound whitespace/C#/markup to make completion work. if (span.FirstAncestorOrSelf <RazorDirectiveSyntax>() != null) { continue; } } // See #2594 if (string.Equals("@", expectedSpan)) { // For now we don't verify an escaped transition. In some cases one of the @ tokens in @@foo // will be mapped as C# but will not be present in the output buffer because it's not actually C#. continue; } var found = false; for (var j = 0; j < csharpDocument.SourceMappings.Count; j++) { var mapping = csharpDocument.SourceMappings[j]; if (mapping.OriginalSpan == sourceSpan) { var actualSpan = csharpDocument.GeneratedCode.Substring( mapping.GeneratedSpan.AbsoluteIndex, mapping.GeneratedSpan.Length); if (!string.Equals(expectedSpan, actualSpan, StringComparison.Ordinal)) { throw new XunitException( $"Found the span {sourceSpan} in the output mappings but it contains " + $"'{EscapeWhitespace(actualSpan)}' instead of '{EscapeWhitespace(expectedSpan)}'."); } found = true; break; } } if (!found) { throw new XunitException( $"Could not find the span {sourceSpan} - containing '{EscapeWhitespace(expectedSpan)}' " + $"in the output."); } } }
protected void AssertLinePragmas(RazorCodeDocument codeDocument, bool designTime) { if (FileName == null) { var message = $"{nameof(AssertSourceMappingsMatchBaseline)} should only be called from an integration test. ({nameof(FileName)} is null)."; throw new InvalidOperationException(message); } var csharpDocument = codeDocument.GetCSharpDocument(); Assert.NotNull(csharpDocument); var linePragmas = csharpDocument.LinePragmas; designTime = false; if (designTime) { var sourceMappings = csharpDocument.SourceMappings; foreach (var sourceMapping in sourceMappings) { var foundMatchingPragma = false; foreach (var linePragma in linePragmas) { if (sourceMapping.OriginalSpan.LineIndex >= linePragma.StartLineIndex && sourceMapping.OriginalSpan.LineIndex <= linePragma.EndLineIndex) { // Found a match. foundMatchingPragma = true; break; } } Assert.True(foundMatchingPragma, $"No line pragma found for code at line {sourceMapping.OriginalSpan.LineIndex + 1}."); } } else { var syntaxTree = codeDocument.GetSyntaxTree(); var sourceBuffer = new char[syntaxTree.Source.Length]; syntaxTree.Source.CopyTo(0, sourceBuffer, 0, syntaxTree.Source.Length); var sourceContent = new string(sourceBuffer); var classifiedSpans = syntaxTree.GetClassifiedSpans(); foreach (var classifiedSpan in classifiedSpans) { var content = sourceContent.Substring(classifiedSpan.Span.AbsoluteIndex, classifiedSpan.Span.Length); if (!string.IsNullOrWhiteSpace(content) && classifiedSpan.BlockKind != BlockKindInternal.Directive && classifiedSpan.SpanKind == SpanKindInternal.Code) { var foundMatchingPragma = false; foreach (var linePragma in linePragmas) { if (classifiedSpan.Span.LineIndex >= linePragma.StartLineIndex && classifiedSpan.Span.LineIndex <= linePragma.EndLineIndex) { // Found a match. foundMatchingPragma = true; break; } } Assert.True(foundMatchingPragma, $"No line pragma found for code '{content}' at line {classifiedSpan.Span.LineIndex + 1}."); } } } }
private 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 = Path.GetRandomFileName(); 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); }
private MemoryStream CreateAndCompileToStream(string templateSource, params Assembly[] linkedAssemblies) { RazorProjectEngine engine = RazorProjectEngine.Create( RazorConfiguration.Default, RazorProjectFileSystem.Create(@"."), (builder) => { builder.SetNamespace("TemplateNamespace"); }); string fileName = Path.GetRandomFileName(); RazorSourceDocument document = RazorSourceDocument.Create(templateSource, fileName); RazorCodeDocument codeDocument = engine.Process( document, null, new List <RazorSourceDocument>(), new List <TagHelperDescriptor>()); RazorCSharpDocument razorCSharpDocument = codeDocument.GetCSharpDocument(); List <PortableExecutableReference> portableExecutableReferences = new List <PortableExecutableReference> { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("Microsoft.CSharp")).Location), MetadataReference.CreateFromFile(typeof(RazorEngineTemplateBase).Assembly.Location), MetadataReference.CreateFromFile(typeof(ExpandoObject).Assembly.Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("netstandard")).Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Runtime")).Location), }; foreach (Assembly assembly in linkedAssemblies) { portableExecutableReferences.Add(MetadataReference.CreateFromFile(assembly.Location)); } CSharpCompilation compilation = CSharpCompilation.Create( fileName, new[] { CSharpSyntaxTree.ParseText(razorCSharpDocument.GeneratedCode) }, portableExecutableReferences, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); MemoryStream memoryStream = new MemoryStream(); EmitResult emitResult = compilation.Emit(memoryStream); if (!emitResult.Success) { List <Diagnostic> errors = emitResult.Diagnostics.ToList(); RazorEngineCompilationException exception = new RazorEngineCompilationException("Unable to compile template: " + errors.FirstOrDefault()?.ToString()); exception.Errors = errors; throw exception; } memoryStream.Position = 0; return(memoryStream); }
/// <summary> /// Getting the generated code through Microsoft.AspNetCore.Razor.Language /// </summary> /// <param name="dynamicTemplateNamespace"></param> /// <param name="templateSourceCode"></param> /// <param name="generatedCSharpClassName"></param> /// <param name="classBaseType"></param> /// <param name="templateFile"></param> /// <returns></returns> public static string GetGeneratedCode(string dynamicTemplateNamespace, string templateSourceCode, string generatedCSharpClassName, string classBaseType, string templateFile = null) { string systemPath = Directory.GetCurrentDirectory(); string path = null; if (string.IsNullOrWhiteSpace(templateFile)) { path = systemPath; } else { path = Path.GetDirectoryName(templateFile); } RazorProjectFileSystem fs = RazorProjectFileSystem.Create(path); // or '.' RazorProjectEngine engine = RazorProjectEngine.Create(RazorConfiguration.Default, fs, (builder) => { InheritsDirective.Register(builder); FunctionsDirective.Register(builder); SectionDirective.Register(builder); builder.ConfigureClass((document, @class) => { @class.ClassName = generatedCSharpClassName; }); builder.SetNamespace(dynamicTemplateNamespace); // define a namespace for the Template class builder.SetBaseType(classBaseType); builder.AddDefaultImports("@using System", "@using System.Threading.Tasks", "@using System.Collections.Generic", "@using System.Linq", "@using System.Text", "@using RazorEngine", "@using RazorEngine.Templating"); }); string razorRelativePath; string randomRazorFileFullPath = null; if (string.IsNullOrEmpty(templateFile)) { razorRelativePath = Path.GetRandomFileName(); randomRazorFileFullPath = Path.Combine(systemPath, razorRelativePath); File.AppendAllText(randomRazorFileFullPath, templateSourceCode ?? string.Empty, System.Text.Encoding.UTF8); } else { razorRelativePath = templateFile; } RazorProjectItem item = fs.GetItem(razorRelativePath); RazorCodeDocument codeDocument = engine.Process(item); RazorCSharpDocument csharpDocument = codeDocument.GetCSharpDocument(); if (!string.IsNullOrEmpty(randomRazorFileFullPath)) { try { File.Delete(randomRazorFileFullPath); } catch (Exception) { } } if (csharpDocument.Diagnostics.Any()) { var diagnostics = string.Join(Environment.NewLine, csharpDocument.Diagnostics); throw new InvalidOperationException($"One or more parse errors encountered. This will not prevent the generator from continuing: {Environment.NewLine}{diagnostics}."); } //Manual loading of assemblies to prevent DLLs from being lost when compiling classes AppDomain.CurrentDomain.Load(typeof(RazorCompiledItemAttribute).Assembly.FullName); //手动加载程序集,防止编译的类时找不到 DLL return(csharpDocument.GeneratedCode); }