module
Analyzer::CSharp::Common
Direct including types
- Analyzer::CSharp::AspNetCoreMvc
- Analyzer::CSharp::AspNetMvc
- Analyzer::CSharp::Carter
- Analyzer::CSharp::Cli
- Analyzer::CSharp::FastEndpoints
- Analyzer::CSharp::HttpListener
- Analyzer::CSharp::MinimalApis
- Analyzer::CSharp::SignalR
Defined in:
analyzer/analyzers/csharp/common.crConstant Summary
-
ASPNET_CORE_NAMESPACE_RE =
/\bMicrosoft\.AspNetCore\b/ -
ASP.NET Core and classic ASP.NET MVC 5 spell a controller almost identically —
public class UserController : Controllerwith[HttpGet]action attributes — and both analyzers are handed every.csfile in the scan. In a solution holding both (a migration in progress, the shape noir gets pointed at) each read the other's controllers: 35 phantomcs_aspnet_core_mvcendpoints out of the classic fixture, 6 the other way, several with the wrong route template applied on the way out.The namespaces are unmistakable and mutually exclusive — a type can only come from one of them — so each analyzer skips a file that names the other's. Nothing positive is required, so a helper file that names neither is still analyzed by both, exactly as before.
-
ASPNET_FRAMEWORK_NAMESPACE_RE =
/\bSystem\.Web\.(?:Mvc|Http|Routing)\b/ -
CARTER_MODULE_RE =
/\bI?CarterModule\b/ -
A Carter module is either
class X : ICarterModuleorclass X : CarterModule— the latter being Carter's abstract base, which adds a constructor base path (: base("/directors")) and the request filters. Both shapes own theirAddRoutesbody, so the Carter analyzer claims them and the minimal-API analyzer skips them.\bCarterModule\balone does not match insideICarterModule(IandCare both word characters), hence the explicit optionalI. -
EXPLICIT_BINDING_NAME_RE =
/\[From(?:Query|Route|Body|Header|Form|Cookie)\s*\(\s*(?:[A-Za-z]+\s*(?:=|:)\s*)?@?"([^"]+)"/ -
[FromRoute(Name = "organizationId")] Guid sponsoringOrgIdbinds the route valueorganizationId; the C# identifier is only the local name. Reporting the identifier invents a parameter the client cannot send and hides the one it must. -
KNOWN_SERVICE_TYPES =
Set {"CancellationToken", "HttpContext", "HttpRequest", "HttpResponse", "ClaimsPrincipal", "IServiceProvider", "LinkGenerator", "ILoggerFactory", "IConfiguration", "IWebHostEnvironment", "IHostEnvironment"} -
Concrete framework types that are always resolved from DI / the request pipeline rather than bound from user input.
-
SERVICE_FORM_INPUT_TYPES =
Set {"IFormFile", "IFormFileCollection", "IFormCollection"} -
IFormFile/IFormFileCollection/IFormCollectionare interfaces but bind from the request body (file upload / form), not from DI — keep them as request inputs even though they match the interface rule below. -
SERVICE_TYPE_SUFFIXES =
["Repository", "Service", "Services", "Manager", "Mediator", "Mapper", "Accessor", "Dispatcher", "Publisher", "DbContext", "Context", "Logger", "Handler", "DataSource"] of ::String -
High-precision suffixes that mark a type as a dependency-injected collaborator. Deliberately conservative: suffixes that collide with common domain/entity names (e.g.
Client,Provider,Factory) are left out so request DTOs aren't dropped by mistake. Interface-typed DI is caught separately by theI<Pascal>rule.HandlerandDataSourcejoined the list after a minimal-API sweep: Bitwarden injectsAccessRequestEndpointsHandler handlerstraight into its handler delegates and Carter's sample injectsEndpointDataSource, both of which surfaced as query parameters. Neither suffix names a request DTO in practice.
Class Method Summary
- .aspnet_core_source?(content : String) : Bool
- .aspnet_framework_source?(content : String) : Bool
- .carter_module_source?(content : String) : Bool
-
.csharp_service_type?(type_name : String) : Bool
Heuristic for whether a parameter's type names a dependency-injected service (DbContext, repository, MediatR sender, mapper, …) rather than a value bound from the request.
-
.csharp_test_path?(relative_path : String) : Bool
Standard .NET test-source conventions:
- .explicit_binding_name(param_def : String) : String | Nil
-
.project_root_for(path : String, roots : Array(String)) : String | Nil
The longest project root containing
path, or nil when the file sits outside every discovered project (no.csprojin the scan at all, the shape most fixtures and single-file samples have). -
.project_roots(csproj_paths : Array(String)) : Array(String)
Directories that own a
.csproj, longest first. -
.route_placeholder_name(raw : String) : String
Strips a route-template placeholder down to its bare parameter name:
{id:int}→id,{slug?}→slug,{*catchAll}→catchAll,{id=5}→id(a default value),{**slug:regex(a=b)}→slug.
Class Method Detail
Heuristic for whether a parameter's type names a dependency-injected service (DbContext, repository, MediatR sender, mapper, …) rather than a value bound from the request. ASP.NET Core can't be statically resolved against its DI container, so we lean on near-universal naming conventions in real code:
- Interfaces (
I+ PascalCase) are never deserialized from a request body and never bound from the query string — they're DI or special pipeline types. The[a-z]third-char guard keeps acronym value types likeIPAddress(I-P-A) out of the net. - A small set of concrete framework types and service suffixes.
Returns false for the form-upload interfaces, which are request inputs.
Standard .NET test-source conventions:
/test/and/tests/parent directories — Microsoft's own repos park unit + integration tests undersrc/<Project>/test/...(aspnetcore) ortests/...(smaller solutions)./testassets/— aspnetcore's helper-controller convention for spinning up a real server inside the test harness.Tests.cs/Test.csfilename — xUnit / NUnit / MSTest suffix convention.
dotnet/aspnetcore alone parks ~3,600 phantom endpoints under
src/Mvc/test/... and similar trees. Production code never
adopts any of these.
Takes the scan-base-relative path (Analyzer#base_relative_path),
never the absolute one. The conventions describe a location inside
the solution, so on an absolute path a test/ directory above the
scan base suppressed the whole project.
The longest project root containing path, or nil when the file sits
outside every discovered project (no .csproj in the scan at all, the
shape most fixtures and single-file samples have).
Directories that own a .csproj, longest first. A .NET project is the
unit that owns a routing table: MapControllerRoute in one project's
Program.cs says nothing about a controller compiled into a sibling
project, even though both sit under one configured scan base.
Strips a route-template placeholder down to its bare parameter name:
{id:int} → id, {slug?} → slug, {*catchAll} → catchAll,
{id=5} → id (a default value), {**slug:regex(a=b)} → slug.
The =default form was previously kept verbatim, so a template like
/items/{id=5} emitted a parameter literally named id=5.