public static RazorHtmlDocument GetHtmlDocument(RazorCodeDocument codeDocument) { var options = codeDocument.GetCodeGenerationOptions(); if (options == null || !options.DesignTime) { // Not needed in run time. This pass generates the backing HTML document that is used to provide HTML intellisense. return(null); } var writer = new RazorHtmlWriter(codeDocument.Source); var syntaxTree = codeDocument.GetSyntaxTree(); writer.Visit(syntaxTree.Root); var generatedHtml = writer.Builder.ToString(); Debug.Assert( writer.Source.Length == writer.Builder.Length, $"The backing HTML document should be the same length as the original document. Expected: {writer.Source.Length} Actual: {writer.Builder.Length}"); var razorHtmlDocument = new DefaultRazorHtmlDocument(generatedHtml, options); return(razorHtmlDocument); }
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { if (codeDocument == null) { throw new ArgumentNullException(nameof(codeDocument)); } if (documentNode == null) { throw new ArgumentNullException(nameof(documentNode)); } var codeGenerationOptions = codeDocument.GetCodeGenerationOptions(); if (codeGenerationOptions == null || !codeGenerationOptions.SuppressPrimaryMethodBody) { return; } var method = documentNode.FindPrimaryMethod(); if (method == null) { return; } method.Children.Clear(); // After we clear all of the method body there might be some unused fields, which can be // blocking if compiling with warnings as errors. Suppress this warning so that it doesn't // get annoying in VS. documentNode.Children.Insert(documentNode.Children.IndexOf(documentNode.FindPrimaryNamespace()), new CSharpCodeIntermediateNode() { Children = { // Field is assigned but never used new IntermediateToken() { Content = "#pragma warning disable 0414" + Environment.NewLine, Kind = TokenKind.CSharp, }, // Field is never assigned new IntermediateToken() { Content = "#pragma warning disable 0649" + Environment.NewLine, Kind = TokenKind.CSharp, }, // Field is never used new IntermediateToken() { Content = "#pragma warning disable 0169" + Environment.NewLine, Kind = TokenKind.CSharp, }, }, }); }
// In general documents will have a relative path (relative to the project root). // We can only really compute a nice namespace when we know a relative path. // // However all kinds of thing are possible in tools. We shouldn't barf here if the document isn't // set up correctly. public static bool TryComputeNamespace(this RazorCodeDocument document, bool fallbackToRootNamespace, out string @namespace) { if (document == null) { throw new ArgumentNullException(nameof(document)); } var filePath = document.Source.FilePath; if (filePath == null || document.Source.RelativePath == null || filePath.Length < document.Source.RelativePath.Length) { @namespace = null; return(false); } // If the document or it's imports contains a @namespace directive, we want to use that over the root namespace. var baseNamespace = string.Empty; var appendSuffix = true; var lastNamespaceContent = string.Empty; var lastNamespaceLocation = SourceSpan.Undefined; var importSyntaxTrees = document.GetImportSyntaxTrees(); if (importSyntaxTrees != null) { // ImportSyntaxTrees is usually set. Just being defensive. foreach (var importSyntaxTree in importSyntaxTrees) { if (importSyntaxTree != null && NamespaceVisitor.TryGetLastNamespaceDirective(importSyntaxTree, out var importNamespaceContent, out var importNamespaceLocation)) { lastNamespaceContent = importNamespaceContent; lastNamespaceLocation = importNamespaceLocation; } } } var syntaxTree = document.GetSyntaxTree(); if (syntaxTree != null && NamespaceVisitor.TryGetLastNamespaceDirective(syntaxTree, out var namespaceContent, out var namespaceLocation)) { lastNamespaceContent = namespaceContent; lastNamespaceLocation = namespaceLocation; } StringSegment relativePath = document.Source.RelativePath; // If there are multiple @namespace directives in the heirarchy, // we want to pick the closest one to the current document. if (!string.IsNullOrEmpty(lastNamespaceContent)) { baseNamespace = lastNamespaceContent; var directiveLocationDirectory = NormalizeDirectory(lastNamespaceLocation.FilePath); var sourceFilePath = new StringSegment(document.Source.FilePath); // We're specifically using OrdinalIgnoreCase here because Razor treats all paths as case-insensitive. if (!sourceFilePath.StartsWith(directiveLocationDirectory, StringComparison.OrdinalIgnoreCase) || sourceFilePath.Length <= directiveLocationDirectory.Length) { // The most relevant directive is not from the directory hierarchy, can't compute a suffix. appendSuffix = false; } else { // We know that the document containing the namespace directive is in the current document's heirarchy. // Let's compute the actual relative path that we'll use to compute the namespace suffix. relativePath = sourceFilePath.Subsegment(directiveLocationDirectory.Length); } } else if (fallbackToRootNamespace) { var options = document.GetCodeGenerationOptions() ?? document.GetDocumentIntermediateNode()?.Options; baseNamespace = options?.RootNamespace; appendSuffix = true; } if (string.IsNullOrEmpty(baseNamespace)) { // There was no valid @namespace directive and we couldn't compute the RootNamespace. @namespace = null; return(false); } var builder = new StringBuilder(); // Sanitize the base namespace, but leave the dots. var segments = new StringTokenizer(baseNamespace, NamespaceSeparators); var first = true; foreach (var token in segments) { if (token.IsEmpty) { continue; } if (first) { first = false; } else { builder.Append('.'); } CSharpIdentifier.AppendSanitized(builder, token); } if (appendSuffix) { // If we get here, we already have a base namespace and the relative path that should be used as the namespace suffix. segments = new StringTokenizer(relativePath, PathSeparators); var previousLength = builder.Length; foreach (var token in segments) { if (token.IsEmpty) { continue; } previousLength = builder.Length; builder.Append('.'); CSharpIdentifier.AppendSanitized(builder, token); } // Trim the last segment because it's the FileName. builder.Length = previousLength; } @namespace = builder.ToString(); return(true); // We want to normalize the path of the file containing the '@namespace' directive to just the containing // directory with a trailing separator. // // Not using Path.GetDirectoryName here because it doesn't meet these requirements, and we want to handle // both 'view engine' style paths and absolute paths. // // We also don't normalize the separators here. We expect that all documents are using a consistent style of path. // // If we can't normalize the path, we just return null so it will be ignored. StringSegment NormalizeDirectory(string path) { if (string.IsNullOrEmpty(path)) { return(default);