Skip to content

Commit

Permalink
Support importing components with @using directives (#276)
Browse files Browse the repository at this point in the history
* Support importing components with @using directives

* Suppress taghelper directive completion in component documents

* feedback

* More feedback

* Update tests

* Update CodeAnalysis.Razor tests

* Flow filekind

* Changes

* More code gen tests

* More tests

* fix

* Added more tests

* Made stuff internal

* Filter out temporary tag helper descriptors

* update

* Do the needful
  • Loading branch information
ajaybhargavb authored Mar 22, 2019
1 parent 1470405 commit 343f377
Show file tree
Hide file tree
Showing 515 changed files with 4,370 additions and 3,845 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,30 @@ public override void WriteComponent(CodeRenderingContext context, ComponentInter
context.CodeWriter.Write(");");
context.CodeWriter.WriteLine();
}

// We want to generate something that references the Component type to avoid
// the "usings directive is unnecessary" message.
// Looks like:
// __o = typeof(SomeNamespace.SomeComponent);
using (context.CodeWriter.BuildLinePragma(node.Source.Value))
{
context.CodeWriter.Write(DesignTimeVariable);
context.CodeWriter.Write(" = ");
context.CodeWriter.Write("typeof(");
context.CodeWriter.Write(node.TagName);
if (node.Component.IsGenericTypedComponent())
{
context.CodeWriter.Write("<");
var typeArgumentCount = node.Component.GetTypeParameters().Count();
for (var i = 1; i < typeArgumentCount; i++)
{
context.CodeWriter.Write(",");
}
context.CodeWriter.Write(">");
}
context.CodeWriter.Write(");");
context.CodeWriter.WriteLine();
}
}

public override void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeIntermediateNode node)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Components
{
internal static class ComponentDiagnosticFactory
{
public static readonly RazorDiagnosticDescriptor CodeBlockInAttribute =
new RazorDiagnosticDescriptor(
"BL9979",
private const string DiagnosticPrefix = "RZ";

public static readonly RazorDiagnosticDescriptor UnsupportedTagHelperDirective = new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}9978",
() =>
"The directives @addTagHelper, @removeTagHelper and @tagHelperPrefix are not valid in a component document." +
"Use '@using <namespace>' directive instead.",
RazorDiagnosticSeverity.Error);

public static RazorDiagnostic Create_UnsupportedTagHelperDirective(SourceSpan? source)
{
return RazorDiagnostic.Create(UnsupportedTagHelperDirective, source ?? SourceSpan.Undefined);
}

public static readonly RazorDiagnosticDescriptor CodeBlockInAttribute = new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}9979",
() =>
"Code blocks delimited by '@{...}' like '@{{ {0} }}' for attributes are no longer supported " +
"These features have been changed to use attribute syntax. " +
Expand All @@ -31,7 +44,7 @@ public static RazorDiagnostic Create_CodeBlockInAttribute(SourceSpan? source, st
}

public static readonly RazorDiagnosticDescriptor UnclosedTag = new RazorDiagnosticDescriptor(
"BL9980",
$"{DiagnosticPrefix}9980",
() => "Unclosed tag '{0}' with no matching end tag.",
RazorDiagnosticSeverity.Error);

Expand All @@ -41,7 +54,7 @@ public static RazorDiagnostic Create_UnclosedTag(SourceSpan? span, string tagNam
}

public static readonly RazorDiagnosticDescriptor UnexpectedClosingTag = new RazorDiagnosticDescriptor(
"BL9981",
$"{DiagnosticPrefix}9981",
() => "Unexpected closing tag '{0}' with no matching start tag.",
RazorDiagnosticSeverity.Error);

Expand All @@ -51,7 +64,7 @@ public static RazorDiagnostic Create_UnexpectedClosingTag(SourceSpan? span, stri
}

public static readonly RazorDiagnosticDescriptor UnexpectedClosingTagForVoidElement = new RazorDiagnosticDescriptor(
"BL9983",
$"{DiagnosticPrefix}9983",
() => "Unexpected closing tag '{0}'. The element '{0}' is a void element, and should be used without a closing tag.",
RazorDiagnosticSeverity.Error);

Expand All @@ -61,7 +74,7 @@ public static RazorDiagnostic Create_UnexpectedClosingTagForVoidElement(SourceSp
}

public static readonly RazorDiagnosticDescriptor InvalidHtmlContent = new RazorDiagnosticDescriptor(
"BL9984",
$"{DiagnosticPrefix}9984",
() => "Found invalid HTML content. Text '{0}'",
RazorDiagnosticSeverity.Error);

Expand All @@ -71,7 +84,7 @@ public static RazorDiagnostic Create_InvalidHtmlContent(SourceSpan? span, string
}

public static readonly RazorDiagnosticDescriptor MultipleComponents = new RazorDiagnosticDescriptor(
"BL9985",
$"{DiagnosticPrefix}9985",
() => "Multiple components use the tag '{0}'. Components: {1}",
RazorDiagnosticSeverity.Error);

Expand All @@ -81,7 +94,7 @@ public static RazorDiagnostic Create_MultipleComponents(SourceSpan? span, string
}

public static readonly RazorDiagnosticDescriptor UnsupportedComplexContent = new RazorDiagnosticDescriptor(
"BL9986",
$"{DiagnosticPrefix}9986",
() => "Component attributes do not support complex content (mixed C# and markup). Attribute: '{0}', text '{1}'",
RazorDiagnosticSeverity.Error);

Expand All @@ -93,7 +106,7 @@ public static RazorDiagnostic Create_UnsupportedComplexContent(IntermediateNode

public static readonly RazorDiagnosticDescriptor PageDirective_CannotBeImported =
new RazorDiagnosticDescriptor(
"BL9987",
$"{DiagnosticPrefix}9987",
() => ComponentResources.PageDirectiveCannotBeImported,
RazorDiagnosticSeverity.Error);

Expand All @@ -107,7 +120,7 @@ public static RazorDiagnostic CreatePageDirective_CannotBeImported(SourceSpan so

public static readonly RazorDiagnosticDescriptor PageDirective_MustSpecifyRoute =
new RazorDiagnosticDescriptor(
"BL9988",
$"{DiagnosticPrefix}9988",
() => "The @page directive must specify a route template. The route template must be enclosed in quotes and begin with the '/' character.",
RazorDiagnosticSeverity.Error);

Expand All @@ -119,7 +132,7 @@ public static RazorDiagnostic CreatePageDirective_MustSpecifyRoute(SourceSpan? s

public static readonly RazorDiagnosticDescriptor BindAttribute_Duplicates =
new RazorDiagnosticDescriptor(
"BL9989",
$"{DiagnosticPrefix}9989",
() => "The attribute '{0}' was matched by multiple bind attributes. Duplicates:{1}",
RazorDiagnosticSeverity.Error);

Expand All @@ -135,7 +148,7 @@ public static RazorDiagnostic CreateBindAttribute_Duplicates(SourceSpan? source,

public static readonly RazorDiagnosticDescriptor EventHandler_Duplicates =
new RazorDiagnosticDescriptor(
"BL9990",
$"{DiagnosticPrefix}9990",
() => "The attribute '{0}' was matched by multiple event handlers attributes. Duplicates:{1}",
RazorDiagnosticSeverity.Error);

Expand All @@ -151,7 +164,7 @@ public static RazorDiagnostic CreateEventHandler_Duplicates(SourceSpan? source,

public static readonly RazorDiagnosticDescriptor BindAttribute_InvalidSyntax =
new RazorDiagnosticDescriptor(
"BL9991",
$"{DiagnosticPrefix}9991",
() => "The attribute names could not be inferred from bind attribute '{0}'. Bind attributes should be of the form" +
"'bind', 'bind-value' or 'bind-value-change'",
RazorDiagnosticSeverity.Error);
Expand All @@ -166,7 +179,7 @@ public static RazorDiagnostic CreateBindAttribute_InvalidSyntax(SourceSpan? sour
}

public static readonly RazorDiagnosticDescriptor DisallowedScriptTag = new RazorDiagnosticDescriptor(
"BL9992",
$"{DiagnosticPrefix}9992",
() => "Script tags should not be placed inside components because they cannot be updated dynamically. To fix this, move the script tag to the 'index.html' file or another static location. For more information see https://go.microsoft.com/fwlink/?linkid=872131",
RazorDiagnosticSeverity.Error);

Expand All @@ -180,7 +193,7 @@ public static RazorDiagnostic Create_DisallowedScriptTag(SourceSpan? source)

public static readonly RazorDiagnosticDescriptor TemplateInvalidLocation =
new RazorDiagnosticDescriptor(
"BL9994",
$"{DiagnosticPrefix}9994",
() => "Razor templates cannot be used in attributes.",
RazorDiagnosticSeverity.Error);

Expand All @@ -191,7 +204,7 @@ public static RazorDiagnostic Create_TemplateInvalidLocation(SourceSpan? source)

public static readonly RazorDiagnosticDescriptor ChildContentSetByAttributeAndBody =
new RazorDiagnosticDescriptor(
"BL9995",
$"{DiagnosticPrefix}9995",
() => "The child content property '{0}' is set by both the attribute and the element contents.",
RazorDiagnosticSeverity.Error);

Expand All @@ -202,7 +215,7 @@ public static RazorDiagnostic Create_ChildContentSetByAttributeAndBody(SourceSpa

public static readonly RazorDiagnosticDescriptor ChildContentMixedWithExplicitChildContent =
new RazorDiagnosticDescriptor(
"BL9996",
$"{DiagnosticPrefix}9996",
() => "Unrecognized child content inside component '{0}'. The component '{0}' accepts child content through the " +
"following top-level items: {1}.",
RazorDiagnosticSeverity.Error);
Expand All @@ -215,7 +228,7 @@ public static RazorDiagnostic Create_ChildContentMixedWithExplicitChildContent(S

public static readonly RazorDiagnosticDescriptor ChildContentHasInvalidAttribute =
new RazorDiagnosticDescriptor(
"BL9997",
$"{DiagnosticPrefix}9997",
() => "Unrecognized attribute '{0}' on child content element '{1}'.",
RazorDiagnosticSeverity.Error);

Expand All @@ -226,7 +239,7 @@ public static RazorDiagnostic Create_ChildContentHasInvalidAttribute(SourceSpan?

public static readonly RazorDiagnosticDescriptor ChildContentHasInvalidParameter =
new RazorDiagnosticDescriptor(
"BL9998",
$"{DiagnosticPrefix}9998",
() => "Invalid parameter name. The parameter name attribute '{0}' on child content element '{1}' can only include literal text.",
RazorDiagnosticSeverity.Error);

Expand All @@ -237,7 +250,7 @@ public static RazorDiagnostic Create_ChildContentHasInvalidParameter(SourceSpan?

public static readonly RazorDiagnosticDescriptor ChildContentRepeatedParameterName =
new RazorDiagnosticDescriptor(
"BL9999",
$"{DiagnosticPrefix}9999",
() => "The child content element '{0}' of component '{1}' uses the same parameter name ('{2}') as enclosing child content " +
"element '{3}' of component '{4}'. Specify the parameter name like: '<{0} Context=\"another_name\"> to resolve the ambiguity",
RazorDiagnosticSeverity.Error);
Expand Down Expand Up @@ -265,7 +278,7 @@ public static RazorDiagnostic Create_ChildContentRepeatedParameterName(

public static readonly RazorDiagnosticDescriptor GenericComponentMissingTypeArgument =
new RazorDiagnosticDescriptor(
"BL10000",
$"{DiagnosticPrefix}10000",
() => "The component '{0}' is missing required type arguments. Specify the missing types using the attributes: {1}.",
RazorDiagnosticSeverity.Error);

Expand All @@ -282,7 +295,7 @@ public static RazorDiagnostic Create_GenericComponentMissingTypeArgument(

public static readonly RazorDiagnosticDescriptor GenericComponentTypeInferenceUnderspecified =
new RazorDiagnosticDescriptor(
"BL10001",
$"{DiagnosticPrefix}10001",
() => "The type of component '{0}' cannot be inferred based on the values provided. Consider specifying the type arguments " +
"directly using the following attributes: {1}.",
RazorDiagnosticSeverity.Error);
Expand All @@ -300,7 +313,7 @@ public static RazorDiagnostic Create_GenericComponentTypeInferenceUnderspecified

public static readonly RazorDiagnosticDescriptor ChildContentHasInvalidParameterOnComponent =
new RazorDiagnosticDescriptor(
"BL10002",
$"{DiagnosticPrefix}10002",
() => "Invalid parameter name. The parameter name attribute '{0}' on component '{1}' can only include literal text.",
RazorDiagnosticSeverity.Error);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ internal class ComponentDocumentClassifierPass : DocumentClassifierPassBase
{
public static readonly string ComponentDocumentKind = "component.1.0";
private static readonly object BuildRenderTreeBaseCallAnnotation = new object();
private static readonly char[] PathSeparators = new char[] { '/', '\\' };
private static readonly char[] NamespaceSeparators = new char[] { '.' };

/// <summary>
/// The fallback value of the root namespace. Only used if the fallback root namespace
Expand Down Expand Up @@ -57,13 +55,7 @@ protected override void OnDocumentStructureCreated(
ClassDeclarationIntermediateNode @class,
MethodDeclarationIntermediateNode method)
{
var options = codeDocument.GetDocumentIntermediateNode().Options;
if (!TryComputeNamespaceAndClass(
options,
codeDocument.Source.FilePath,
codeDocument.Source.RelativePath,
out var computedNamespace,
out var computedClass))
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.
Expand All @@ -74,7 +66,7 @@ protected override void OnDocumentStructureCreated(

if (MangleClassNames)
{
computedClass = "__" + computedClass;
computedClass = ComponentMetadata.MangleClassName(computedClass);
}

@namespace.Content = computedNamespace;
Expand Down Expand Up @@ -121,59 +113,6 @@ protected override void OnDocumentStructureCreated(
method.Children.Insert(0, callBase);
}

// 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(
RazorCodeGenerationOptions options,
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;
}

var rootNamespace = options.RootNamespace;
if (string.IsNullOrEmpty(rootNamespace))
{
@namespace = null;
@class = null;
return false;
}

var builder = new StringBuilder();

// Sanitize the base namespace, but leave the dots.
var segments = rootNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries);
builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[0]));
for (var i = 1; i < segments.Length; i++)
{
builder.Append('.');
builder.Append(CSharpIdentifier.SanitizeIdentifier(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.SanitizeIdentifier(segments[i]));
}

@namespace = builder.ToString();
@class = CSharpIdentifier.SanitizeIdentifier(Path.GetFileNameWithoutExtension(relativePath));

return true;
}

internal static bool IsBuildRenderTreeBaseCall(CSharpCodeIntermediateNode node)
=> node.Annotations[BuildRenderTreeBaseCallAnnotation] != null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,8 @@ public IReadOnlyList<RazorProjectItem> GetImports(RazorProjectItem projectItem)
var imports = new List<RazorProjectItem>()
{
new VirtualProjectItem(DefaultUsingImportContent),
new VirtualProjectItem(@"@addTagHelper ""*, Microsoft.AspNetCore.Components"""),
};

// Try and infer a namespace from the project directory. We don't yet have the ability to pass
// the namespace through from the project.
if (projectItem.PhysicalPath != null && projectItem.FilePath != null)
{
// Avoiding the path-specific APIs here, we want to handle all styles of paths
// on all platforms
var trimLength = projectItem.FilePath.Length + (projectItem.FilePath.StartsWith("/") ? 0 : 1);
if (projectItem.PhysicalPath.Length > trimLength)
{
var baseDirectory = projectItem.PhysicalPath.Substring(0, projectItem.PhysicalPath.Length - trimLength);
var lastSlash = baseDirectory.LastIndexOfAny(PathSeparators);
var baseNamespace = lastSlash == -1 ? baseDirectory : baseDirectory.Substring(lastSlash + 1);
if (!string.IsNullOrEmpty(baseNamespace))
{
imports.Add(new VirtualProjectItem($@"@addTagHelper ""*, {baseNamespace}"""));
}
}
}

// We add hierarchical imports second so any default directive imports can be overridden.
imports.AddRange(GetHierarchicalImports(ProjectEngine.FileSystem, projectItem));

Expand Down
Loading

0 comments on commit 343f377

Please sign in to comment.