// In general documents will have a relative path (relative to the project root). // We can only really compute a nice class/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. private bool TryComputeNamespaceAndClass(string filePath, string relativePath, out string @namespace, out string @class) { if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length) { @namespace = null; @class = null; return(false); } // Try and infer a namespace from the project directory. We don't yet have the ability to pass // the namespace through from the project. var trimLength = relativePath.Length + (relativePath.StartsWith("/") ? 0 : 1); var baseDirectory = filePath.Substring(0, filePath.Length - trimLength); var lastSlash = baseDirectory.LastIndexOfAny(PathSeparators); var baseNamespace = lastSlash == -1 ? baseDirectory : baseDirectory.Substring(lastSlash + 1); if (string.IsNullOrEmpty(baseNamespace)) { @namespace = null; @class = null; return(false); } var builder = new StringBuilder(); // Sanitize the base namespace, but leave the dots. var segments = baseNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries); builder.Append(CSharpIdentifier.SanitizeClassName(segments[0])); for (var i = 1; i < segments.Length; i++) { builder.Append('.'); builder.Append(CSharpIdentifier.SanitizeClassName(segments[i])); } segments = relativePath.Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries); // Skip the last segment because it's the FileName. for (var i = 0; i < segments.Length - 1; i++) { builder.Append('.'); builder.Append(CSharpIdentifier.SanitizeClassName(segments[i])); } @namespace = builder.ToString(); @class = CSharpIdentifier.SanitizeClassName(Path.GetFileNameWithoutExtension(relativePath)); return(true); }
/// <inheritdoc /> protected override void OnDocumentStructureCreated( RazorCodeDocument codeDocument, NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method) { if (!TryComputeNamespaceAndClass( codeDocument.Source.FilePath, codeDocument.Source.RelativePath, out var computedNamespace, out var computedClass)) { // If we can't compute a nice namespace (no relative path) then just generate something // mangled. computedNamespace = BaseNamespace; computedClass = CSharpIdentifier.GetClassNameFromPath(codeDocument.Source.FilePath) ?? "__BlazorComponent"; } if (MangleClassNames) { computedClass = "__" + computedClass; } @namespace.Content = computedNamespace; @class.BaseType = ComponentsApi.ComponentBase.FullTypeName; @class.ClassName = computedClass; @class.Modifiers.Clear(); @class.Modifiers.Add("public"); var documentNode = codeDocument.GetDocumentIntermediateNode(); var typeParamReferences = documentNode.FindDirectiveReferences(TypeParamDirective.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, }); // We need to call the 'base' method as the first statement. var callBase = new CSharpCodeIntermediateNode(); callBase.Annotations.Add(BuildRenderTreeBaseCallAnnotation, true); callBase.Children.Add(new IntermediateToken { Kind = TokenKind.CSharp, Content = $"base.{ComponentsApi.ComponentBase.BuildRenderTree}(builder);" }); method.Children.Insert(0, callBase); }