Skip to content

BRO1008: Using directives should be placed correctly

Using directives go inside or outside the namespace, as csharp_using_directive_placement says; without that setting nothing is reported. Replaces StyleCop SA1200, and the SDK's IDE0065, whose fix can break the build.

Example

[*.cs]
csharp_using_directive_placement = outside_namespace

Before

namespace Shop.Orders
{
    using System;
    using System.Collections.Generic;

    public class Order
    {
        public List<DateTime> Changes { get; } = new();
    }
}

After

using System;
using System.Collections.Generic;

namespace Shop.Orders
{
    public class Order
    {
        public List<DateTime> Changes { get; } = new();
    }
}

Why

One place for usings in every file. Where they are also decides how names are looked up: inside the namespace, a using's names come before the enclosing namespaces' members; outside, after them.

How the fix works

All usings of the file move together, and only when no name in the file can be looked up differently afterwards. The SDK's IDE0065 fix doesn't check that: in eShop it made Order ambiguous (CS0104), in Ocelot a type name turned into a namespace (CS0118; reported upstream as dotnet/roslyn#86002). The check follows C#'s lookup order instead of compiling the file again:

  • Inside namespace A.B, a name is looked up in A.B, then in the usings there, then in A, the global namespace, and the global usings. Outside, the usings come last, together with the global usings (implicit usings too).
  • So for every identifier in the file that the moved usings import, the types A or the global namespace have under that name must be the same ones, and if they have none, the global usings must not import anything else under it.
  • Extension methods (names after ., and the ones foreach, await, collection initializers, deconstruction and queries call) that the moved usings bring must not share a name with one from the namespace, its containers or the global usings.
  • A using's own name (using X.Y;) must start with a member of the global namespace that no namespace of A.B has.

Names are compared by text, so the check is conservative. When it fails, nothing is reported.

  • Comments directly above the first using move with it; the file header stays on top. When usings at the top of the file move in, a comment right below them becomes the header and gets a blank line before the code.
  • Outside: the usings go above the namespace (and the comments directly above it), with a blank line before the namespace and none at the start of the file. Relative names get their full name, like BRO1126's fix (using IO; inside namespace System becomes using System.IO;).
  • Inside: below { (indented one level), or below namespace X; after a blank line, with a blank line before what follows.
  • In a multi-targeted project every target framework's copy of the file must agree; otherwise the warning stays.

Configuration

[*.cs]
# The SDK's setting (StyleCop's usingDirectivesPlacement):
#   inside_namespace (StyleCop's default), outside_namespace
#   not set, or anything else (preserve): nothing is reported
csharp_using_directive_placement = outside_namespace

The preset sets outside_namespace. stylebro-migrate writes StyleCop's choice from stylecop.json (usingDirectivesPlacement, inside_namespace when it isn't set, StyleCop's default), so a migrated repository keeps SA1200's behavior. Without the preset and the setting the rule does nothing: where usings go is a choice it doesn't make for you.

Not reported

  • Files where a name could be looked up differently with the usings moved (see above).
  • Files with more than one namespace, with types, attributes or top-level statements outside the namespace, with usings on both levels, global usings, or extern aliases.
  • A directive (#if, #pragma, #nullable, ...) anywhere before the namespace's body. #region/#endregion lines are fine when they aren't among the usings and no region is open around the usings or where they go (Newtonsoft.Json's #region License around the header, above or below the usings, which BRO1112 removes in the same run); a region around the usings isn't.
  • A using sharing its line with other code.
  • With inside_namespace, a file without a namespace (StyleCop reports its usings, but there's nowhere to move them).

Compared with StyleCop

Same positions and output where both report (parity sets using-placement and using-placement-outside), except that StyleCop's fix drops the blank line below namespace X;. StyleCop's fix moves usings without checking what they bind to: in the parity sets it turned Order into another type, and produced code that doesn't compile (CS0234).