diff --git a/src/Analyzers/Internal.AspNetCore.Analyzers/src/Internal.AspNetCore.Analyzers.csproj b/src/Analyzers/Internal.AspNetCore.Analyzers/src/Internal.AspNetCore.Analyzers.csproj
new file mode 100644
index 0000000000..2ce18ea5b7
--- /dev/null
+++ b/src/Analyzers/Internal.AspNetCore.Analyzers/src/Internal.AspNetCore.Analyzers.csproj
@@ -0,0 +1,24 @@
+
+
+
+ ASP.NET Core internal use analyzers.
+ netstandard1.3
+ $(PackageTags);analyzers
+ $(NoWarn);CS1591
+ analyzers/dotnet/cs/
+
+ false
+ true
+ true
+ true
+
+
+
+
+
+
+
diff --git a/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubternalityAnalyzer.cs b/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubternalityAnalyzer.cs
new file mode 100644
index 0000000000..b66aa38c2e
--- /dev/null
+++ b/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubternalityAnalyzer.cs
@@ -0,0 +1,279 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Internal.AspNetCore.Analyzers
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class PubternalityAnalyzer : DiagnosticAnalyzer
+ {
+ public PubternalityAnalyzer()
+ {
+ SupportedDiagnostics = ImmutableArray.Create(new[]
+ {
+ PubturnalityDescriptors.PUB0001,
+ PubturnalityDescriptors.PUB0002
+ });
+ }
+
+ public override ImmutableArray SupportedDiagnostics { get; }
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(analysisContext =>
+ {
+ analysisContext.RegisterSymbolAction(symbolAnalysisContext => AnalyzeTypeUsage(symbolAnalysisContext), SymbolKind.Namespace);
+ analysisContext.RegisterSyntaxNodeAction(syntaxContext => AnalyzeTypeUsage(syntaxContext), SyntaxKind.IdentifierName);
+ });
+ }
+
+ private void AnalyzeTypeUsage(SymbolAnalysisContext context)
+ {
+ var ns = (INamespaceSymbol)context.Symbol;
+ if (IsInternal(ns))
+ {
+ return;
+ }
+
+ foreach (var namespaceOrTypeSymbol in ns.GetMembers())
+ {
+ if (namespaceOrTypeSymbol.IsType)
+ {
+ CheckType((ITypeSymbol)namespaceOrTypeSymbol, context);
+ }
+ }
+ }
+
+ private void CheckType(ITypeSymbol typeSymbol, SymbolAnalysisContext context)
+ {
+ if (IsPrivate(typeSymbol) || IsPrivate(typeSymbol.ContainingType))
+ {
+ return;
+ }
+
+ if (typeSymbol.BaseType != null)
+ {
+ CheckType(context, typeSymbol.BaseType, typeSymbol.DeclaringSyntaxReferences);
+ }
+
+ foreach (var member in typeSymbol.GetMembers())
+ {
+ CheckMember(context, member);
+ }
+
+ foreach (var innerType in typeSymbol.GetTypeMembers())
+ {
+ CheckType(innerType, context);
+ }
+
+ if (typeSymbol is INamedTypeSymbol namedTypeSymbol)
+ {
+ // Check delegate signatures
+ if (namedTypeSymbol.DelegateInvokeMethod != null)
+ {
+ CheckMethod(context, namedTypeSymbol.DelegateInvokeMethod);
+ }
+ }
+ }
+
+ private void CheckMember(SymbolAnalysisContext context, ISymbol symbol)
+ {
+ if (IsPrivate(symbol))
+ {
+ return;
+ }
+
+ switch (symbol)
+ {
+ case IFieldSymbol fieldSymbol:
+ {
+ CheckType(context, fieldSymbol.Type, fieldSymbol.DeclaringSyntaxReferences);
+ break;
+ }
+ case IPropertySymbol propertySymbol:
+ {
+ CheckType(context, propertySymbol.Type, propertySymbol.DeclaringSyntaxReferences);
+ break;
+ }
+ case IMethodSymbol methodSymbol:
+ {
+ // Skip compiler generated members that we already explicitly check
+ switch (methodSymbol.MethodKind)
+ {
+ case MethodKind.EventAdd:
+ case MethodKind.EventRaise:
+ case MethodKind.EventRemove:
+ case MethodKind.PropertyGet:
+ case MethodKind.PropertySet:
+ case MethodKind.DelegateInvoke:
+ case MethodKind.Ordinary when methodSymbol.ContainingType.TypeKind == TypeKind.Delegate:
+ return;
+ }
+
+ CheckMethod(context, methodSymbol);
+ break;
+ }
+ case IEventSymbol eventSymbol:
+ CheckType(context, eventSymbol.Type, eventSymbol.DeclaringSyntaxReferences);
+ break;
+ }
+ }
+
+ private void CheckMethod(SymbolAnalysisContext context, IMethodSymbol methodSymbol)
+ {
+ if (IsPrivate(methodSymbol))
+ {
+ return;
+ }
+
+ foreach (var parameter in methodSymbol.Parameters)
+ {
+ CheckType(context, parameter.Type, parameter.DeclaringSyntaxReferences);
+ }
+
+ CheckType(context, methodSymbol.ReturnType, methodSymbol.DeclaringSyntaxReferences);
+ }
+
+ private static bool IsPrivate(ISymbol symbol)
+ {
+ return symbol != null &&
+ (symbol.DeclaredAccessibility == Accessibility.Private ||
+ symbol.DeclaredAccessibility == Accessibility.Internal ||
+ IsInternal(symbol.ContainingNamespace));
+ }
+
+ private void CheckAttributes(SymbolAnalysisContext context, ImmutableArray attributes)
+ {
+ foreach (var attributeData in attributes)
+ {
+ CheckType(context, attributeData.AttributeClass, attributeData.ApplicationSyntaxReference);
+ }
+ }
+
+ private void CheckType(SymbolAnalysisContext context, ITypeSymbol symbol, SyntaxReference syntax)
+ {
+ var pubternalType = GetPubternalType(symbol);
+ if (pubternalType != null)
+ {
+ ReportPUB0001(context, pubternalType, syntax);
+ }
+ }
+ private void CheckType(SymbolAnalysisContext context, ITypeSymbol symbol, ImmutableArray syntaxReferences)
+ {
+ var pubternalType = GetPubternalType(symbol);
+ if (pubternalType != null)
+ {
+ foreach (var syntaxReference in syntaxReferences)
+ {
+ ReportPUB0001(context, pubternalType, syntaxReference);
+ }
+ }
+ }
+
+ private static void ReportPUB0001(SymbolAnalysisContext context, ITypeSymbol pubternalType, SyntaxReference syntax)
+ {
+ var syntaxNode = syntax.GetSyntax();
+ var location = syntaxNode.GetLocation();
+
+ if (syntaxNode is BaseTypeDeclarationSyntax baseTypeDeclarationSyntax)
+ {
+ location = baseTypeDeclarationSyntax.Identifier.GetLocation();
+ }
+
+ if (syntaxNode is DelegateDeclarationSyntax delegateDeclarationSyntax)
+ {
+ location = delegateDeclarationSyntax.ReturnType.GetLocation();
+ }
+
+ if (syntaxNode is BasePropertyDeclarationSyntax propertyDeclaration)
+ {
+ location = propertyDeclaration.Type.GetLocation();
+ }
+
+ if (syntaxNode is MethodDeclarationSyntax method)
+ {
+ location = method.ReturnType.GetLocation();
+ }
+
+ if (syntaxNode is VariableDeclaratorSyntax variableDeclarator)
+ {
+ if (variableDeclarator.Parent is VariableDeclarationSyntax fieldDeclaration)
+ {
+ location = fieldDeclaration.Type.GetLocation();
+ }
+ }
+
+ context.ReportDiagnostic(Diagnostic.Create(PubturnalityDescriptors.PUB0001, location, pubternalType.ToDisplayString()));
+ }
+
+ private ITypeSymbol GetPubternalType(ITypeSymbol symbol)
+ {
+ if (IsInternal(symbol.ContainingNamespace))
+ {
+ return symbol;
+ }
+ else
+ {
+ if (symbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType)
+ {
+ foreach (var argument in namedTypeSymbol.TypeArguments)
+ {
+ var argumentSymbol = GetPubternalType(argument);
+ if (argumentSymbol != null)
+ {
+ return argumentSymbol;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void AnalyzeTypeUsage(SyntaxNodeAnalysisContext syntaxContext)
+ {
+ var identifier = (IdentifierNameSyntax)syntaxContext.Node;
+
+ var symbolInfo = ModelExtensions.GetTypeInfo(syntaxContext.SemanticModel, identifier, syntaxContext.CancellationToken);
+ if (symbolInfo.Type == null)
+ {
+ return;
+ }
+
+ var type = symbolInfo.Type;
+ if (!IsInternal(type.ContainingNamespace))
+ {
+ // don't care about non-pubternal type references
+ return;
+ }
+
+ if (!syntaxContext.ContainingSymbol.ContainingAssembly.Equals(type.ContainingAssembly))
+ {
+ syntaxContext.ReportDiagnostic(Diagnostic.Create(PubturnalityDescriptors.PUB0002, identifier.GetLocation(), type.ToDisplayString()));
+ }
+ }
+
+ private static bool IsInternal(INamespaceSymbol ns)
+ {
+ while (ns != null)
+ {
+ if (ns.Name == "Internal")
+ {
+ return true;
+ }
+
+ ns = ns.ContainingNamespace;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubturnalityDescriptors.cs b/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubturnalityDescriptors.cs
new file mode 100644
index 0000000000..6064ebaf34
--- /dev/null
+++ b/src/Analyzers/Internal.AspNetCore.Analyzers/src/PubturnalityDescriptors.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis;
+
+namespace Internal.AspNetCore.Analyzers
+{
+ internal class PubturnalityDescriptors
+ {
+ public static DiagnosticDescriptor PUB0001 = new DiagnosticDescriptor(
+ "PUB0001",
+ "Pubternal type in public API",
+ "Pubternal type ('{0}') usage in public API",
+ "Usage",
+ DiagnosticSeverity.Warning, true);
+
+ public static DiagnosticDescriptor PUB0002 = new DiagnosticDescriptor(
+ "PUB0002",
+ "Cross assembly pubternal reference",
+ "Cross assembly pubternal type ('{0}') reference",
+ "Usage",
+ DiagnosticSeverity.Error, false);
+ }
+}
diff --git a/src/Analyzers/Internal.AspNetCore.Analyzers/test/Internal.AspNetCore.Analyzers.Tests.csproj b/src/Analyzers/Internal.AspNetCore.Analyzers/test/Internal.AspNetCore.Analyzers.Tests.csproj
new file mode 100644
index 0000000000..99dcca9dc1
--- /dev/null
+++ b/src/Analyzers/Internal.AspNetCore.Analyzers/test/Internal.AspNetCore.Analyzers.Tests.csproj
@@ -0,0 +1,15 @@
+
+
+
+ $(DefaultNetCoreTargetFramework);net472
+ true
+
+ false
+
+
+
+
+
+
+
+
diff --git a/src/Analyzers/Internal.AspNetCore.Analyzers/test/PubternabilityAnalyzerTests.cs b/src/Analyzers/Internal.AspNetCore.Analyzers/test/PubternabilityAnalyzerTests.cs
new file mode 100644
index 0000000000..ebcd5fa1e8
--- /dev/null
+++ b/src/Analyzers/Internal.AspNetCore.Analyzers/test/PubternabilityAnalyzerTests.cs
@@ -0,0 +1,260 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.AspNetCore.Analyzer.Testing;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Internal.AspNetCore.Analyzers.Tests
+{
+ public class PubternabilityAnalyzerTests : DiagnosticVerifier
+ {
+
+ private const string InternalDefinitions = @"
+namespace A.Internal.Namespace
+{
+ public class C {}
+ public delegate C CD ();
+ public class CAAttribute: System.Attribute {}
+
+ public class Program
+ {
+ public static void Main() {}
+ }
+}";
+ public PubternabilityAnalyzerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
+ {
+ }
+
+ [Theory]
+ [MemberData(nameof(PublicMemberDefinitions))]
+ public async Task PublicExposureOfPubternalTypeProducesPUB0001(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ public class T
+ {{
+ {member}
+ }}
+}}");
+ var diagnostic = Assert.Single(await GetDiagnostics(code.Source));
+ Assert.Equal("PUB0001", diagnostic.Id);
+ AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location);
+ }
+
+ [Theory]
+ [MemberData(nameof(PublicMemberWithAllowedDefinitions))]
+ public async Task PublicExposureOfPubternalMembersSometimesAllowed(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ public class T
+ {{
+ {member}
+ }}
+}}");
+ Assert.Empty(await GetDiagnostics(code.Source));
+ }
+
+
+ [Theory]
+ [MemberData(nameof(PublicTypeDefinitions))]
+ public async Task PublicExposureOfPubternalTypeProducesInTypeDefinitionPUB0001(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ {member}
+}}");
+ var diagnostic = Assert.Single(await GetDiagnostics(code.Source));
+ Assert.Equal("PUB0001", diagnostic.Id);
+ AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location);
+ }
+
+ [Theory]
+ [MemberData(nameof(PublicMemberDefinitions))]
+ public async Task PrivateUsageOfPubternalTypeDoesNotProduce(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ internal class T
+ {{
+ {member}
+ }}
+}}");
+ var diagnostics = await GetDiagnostics(code.Source);
+ Assert.Empty(diagnostics);
+ }
+
+ [Theory]
+ [MemberData(nameof(PrivateMemberDefinitions))]
+ public async Task PrivateUsageOfPubternalTypeDoesNotProduceInPublicClasses(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ public class T
+ {{
+ {member}
+ }}
+}}");
+ var diagnostics = await GetDiagnostics(code.Source);
+ Assert.Empty(diagnostics);
+ }
+
+
+ [Theory]
+ [MemberData(nameof(PublicTypeWithAllowedDefinitions))]
+ public async Task PublicExposureOfPubternalTypeSometimesAllowed(string member)
+ {
+ var code = GetSourceFromNamespaceDeclaration($@"
+namespace A
+{{
+ {member}
+}}");
+ var diagnostics = await GetDiagnostics(code.Source);
+ Assert.Empty(diagnostics);
+ }
+
+ [Theory]
+ [MemberData(nameof(PrivateMemberDefinitions))]
+ [MemberData(nameof(PublicMemberDefinitions))]
+ public async Task DefinitionOfPubternalCrossAssemblyProducesPUB0002(string member)
+ {
+ var code = TestSource.Read($@"
+using A.Internal.Namespace;
+namespace A
+{{
+ internal class T
+ {{
+ {member}
+ }}
+}}");
+
+ var diagnostic = Assert.Single(await GetDiagnosticWithProjectReference(code.Source));
+ Assert.Equal("PUB0002", diagnostic.Id);
+ AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location);
+ }
+
+ [Theory]
+ [MemberData(nameof(TypeUsages))]
+ public async Task UsageOfPubternalCrossAssemblyProducesPUB0002(string usage)
+ {
+ var code = TestSource.Read($@"
+using A.Internal.Namespace;
+namespace A
+{{
+ public class T
+ {{
+ private void M()
+ {{
+ {usage}
+ }}
+ }}
+}}");
+ var diagnostic = Assert.Single(await GetDiagnosticWithProjectReference(code.Source));
+ Assert.Equal("PUB0002", diagnostic.Id);
+ AnalyzerAssert.DiagnosticLocation(code.DefaultMarkerLocation, diagnostic.Location);
+ }
+
+ public static IEnumerable