Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved .editorconfig compliance with .NET guidelines #3

Closed
henrygab opened this issue Aug 6, 2018 · 26 comments
Closed

Improved .editorconfig compliance with .NET guidelines #3

henrygab opened this issue Aug 6, 2018 · 26 comments
Labels
enhancement Issues describing an enhancement or pull requests adding an enhancement.

Comments

@henrygab
Copy link
Collaborator

henrygab commented Aug 6, 2018

This is a somewhat long post, expanding somewhat on Dotnet-Boxed/Framework#22.

TLDR;

  1. Split naming rules into those specified by the .NET naming guidelines, and those which are user-preference.
  2. Make it clearer where users can add customizations and overrides without stepping on the .NET naming guidelines.
  3. Make it annoying to use public fields that are disallowed by the .NET Design Guidelines.

Foundational Background

Because of the above, the state for a given field is defined by:

  • {true|false} field type allowed by the .NET design guidelines
  • {null|pascal} naming convention specified by the naming guidelines

The recommendation is to split field name formatting as follows:

  1. {true, pascal} == public const, protected const
  2. {true, pascal} == public static readonly, protected static readonly
  3. user-specified rules for private, internal, and protected fields
  4. {true, null} == catch-all for private, internal, and protected internal fields
  5. {false, *} == any other type of field not in above categories
@henrygab
Copy link
Collaborator Author

henrygab commented Aug 6, 2018

How Naming Convention rules are parsed from a single file

From https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions:

Naming conventions should be ordered from most-specific to least-specific
in the .editorconfig file. The first rule encountered that can be applied
is the only rule that is applied.

From https://editorconfig.org/#file-location:

EditorConfig files are read top to bottom and the most recent rules found take precedence.
Properties from matching EditorConfig sections are applied in the order they were read,
so properties in closer files take precedence.

https://editorconfig.org/#file-format-details

EditorConfig files are read top to bottom and the most recent rules found take precedence.

Thus, both of the following are true:

  • lines of the file are processed in order (as one would expect)
  • for naming conventions, only the first applicable rule is applied

See also https://github.com/MicrosoftDocs/visualstudio-docs/issues/1351, where I ask the official sources about related questions in the multi-file scenario.

@henrygab
Copy link
Collaborator Author

henrygab commented Aug 6, 2018

The pull request, and more specifically the table at 9b7d002 starting at line 282, is informative.

This table lists:

@RehanSaeed
Copy link
Owner

This is a pretty big change that I need time to digest and test against the StyleCop.Analyzers NuGet package to see if the rules are compatible.

@henrygab
Copy link
Collaborator Author

henrygab commented Mar 24, 2019

Hi @RehanSaeed,

Have you taken a look at this change yet? I specifically created the table summarizing the differences between old and new rule sets, and included links to why it appears the updated rule is more appropriate.

Let me know...

@RehanSaeed RehanSaeed added the enhancement Issues describing an enhancement or pull requests adding an enhancement. label Mar 25, 2019
@RehanSaeed
Copy link
Owner

Sorry it took so long to look into this. I tried out some fields and this is what I got:

public const int Foo1 = 1;
internal const int Foo2 = 1;
protected internal const int Foo3 = 1;
protected const int Foo4 = 1;
private protected const int Foo5 = 1; // ____INVALID____
private const int Foo6 = 1;

public static readonly int Bar1 = 1;
internal static readonly int Bar2 = 1;
protected internal static readonly int Bar3 = 1;
protected static readonly int Bar4 = 1;
private protected static readonly int Bar5 = 1; // ____INVALID____
private static readonly int Bar6 = 1;

public readonly int Bar1 = 1; // ____INVALID____
internal readonly int Bar2 = 1;
protected internal readonly int Bar3 = 1;
protected readonly int bar4 = 1; // ____INVALID____
private protected readonly int bar5 = 1; // ____INVALID____
private readonly int Bar6 = 1; // Allowed but lowercase bar6 is not.

public int bar1 = 1; // ____INVALID____
internal int Bar2 = 1;
protected internal int bar3 = 1; // Field should be private
protected int bar4 = 1; // ____INVALID____
private protected int bar5 = 1; // ____INVALID____
private int bar6 = 1; // Must begin with an uppercase character
  • The use of INVALID is cool but it needs to be more consistent.
  • private protected is not handled.
  • I would expect readonly fields to be treated the same as non-readonly (See last two sections).
  • private fields must begin with an uppercase character is incorrect.

This is what is valid in the current implementation:

public const int Foo1 = 1;
internal const int Foo2 = 1;
protected internal const int Foo3 = 1;
protected const int Foo4 = 1;
private protected const int Foo5 = 1;
private const int Foo6 = 1;

public static readonly int Bar1 = 1;
internal static readonly int Bar2 = 1;
protected internal static readonly int Bar3 = 1;
protected static readonly int Bar4 = 1;
private protected static readonly int Bar5 = 1;
private static readonly int Bar6 = 1;

public readonly int Bar1 = 1;
internal readonly int Bar2 = 1;
protected internal readonly int Bar3 = 1;
protected readonly int Bar4 = 1;
private protected readonly int bar5 = 1;
private readonly int bar6 = 1;

public int Bar1 = 1;
internal int Bar2 = 1;
protected internal int bar3 = 1;
protected int bar4 = 1;
private protected int bar5 = 1;
private int bar6 = 1;

@henrygab
Copy link
Collaborator Author

henrygab commented Mar 28, 2019

The .NET guidelines state:

"DO NOT provide instance fields that are public or protected."

That page provides strong justification for the rules, mostly related to things breaking encapsulation and therefore causing maintenance nightmares when needing to change something in a later revision.

In fact, apart from a fix to private fields being noisy, I think your investigation shows that the rules I proposed are working as exactly as intended, and in fact highlight significant encapsulation concerns. Here's a breakdown of the differences you noted:

private protected const int Foo5 = 1; // INVALID
private protected static readonly int Bar5 = 1; // INVALID
protected readonly int bar4 = 1; // INVALID
private protected readonly int bar5 = 1; // INVALID
protected int bar4 = 1; // INVALID
private protected int bar5 = 1; // INVALID

The proposed rules are correct. The use of a protected field is disallowed, as it breaks encapsulation and causes versioning issues.

protected internal int bar3 = 1; // Field should be private

Walking through the rules, neither group A nor B should match, as they each require "public". Group "L" should be the first match, and mark it as INVALID, but you wrote the comment "Field should be private". It appears to me that the proposed rules are correct, because the use of a protected field is disallowed, even if marked as "internal". Are you suggesting the rule needs a fix of some sort here?

public readonly int Bar1 = 1; // INVALID
public int bar1 = 1; // INVALID

The proposed rules are correct. Public fields are disallowed unless either (1) public const, or (2) public static readonly. Neither of these lines fits these exceptions (readonly != static readonly).

private readonly int Bar6 = 1; // Allowed but lowercase bar6 is not.
private int bar6 = 1; // Must begin with an uppercase character

OK, found the fix. I just need to set group L to "Silent". I submitted a new pull request (#5). This pull request will now allow you to name your private fields however you like, without emitting any warning.

@RehanSaeed, would appreciate your thoughts, as with the "silent" fix, all the differences you noted seem to be properly flagged as lurking problems (at least according to the .NET guidelines).

@RehanSaeed
Copy link
Owner

RehanSaeed commented Mar 28, 2019

Still lots of work to do. Here I have added const, static readonly, static, readonly, and normal fields but have also added uppercase and lowercase variants of each. I have added comments against each with what is actually happening and then what I think should be happening.

class Program
    {
        public const int Foo1 = 1;             // Valid - Good
        internal const int Foo2 = 1;           // Valid - Good
        protected internal const int Foo3 = 1; // Valid - Good
        protected const int Foo4 = 1;          // Valid - Good
        private protected const int Foo5 = 1;  // ____INVALID____ - Should be valid
        private const int Foo6 = 1;            // Valid - Good

        public const int foo1 = 1;             // Must be uppercase - Good
        internal const int foo2 = 1;           // Valid - Should be uppercase
        protected internal const int foo3 = 1; // Valid - Should be uppercase
        protected const int foo4 = 1;          // Should be uppercase - Good
        private protected const int foo5 = 1;  // ____INVALID____ - Should be uppercase
        private const int foo6 = 1;            // Valid - Should be uppercase

        public static readonly int Bar1 = 1;             // Valid - Good
        internal static readonly int Bar2 = 1;           // Valid - Good
        protected internal static readonly int Bar3 = 1; // Valid - Good
        protected static readonly int Bar4 = 1;          // Valid - Good
        private protected static readonly int Bar5 = 1;  // ____INVALID____ - Should be valid
        private static readonly int Bar6 = 1;            // Valid - Good

        public static readonly int bar1 = 1;             // Must be uppercase - Good
        internal static readonly int bar2 = 1;           // Valid - Should be uppercase
        protected internal static readonly int bar3 = 1; // Valid - Should be uppercase
        protected static readonly int bar4 = 1;          // Must be uppercase - Good
        private protected static readonly int bar5 = 1;  // ____INVALID____ - Should be uppercase
        private static readonly int bar6 = 1;            // Valid - Should be uppercase

        public static int Bay1 = 1;             // ____INVALID____ - Should be valid
        internal static int Bay2 = 1;           // Valid - Good
        protected internal static int Bay3 = 1; // Valid - Good
        protected static int Bay4 = 1;          // ____INVALID____ - Should be valid
        private protected static int Bay5 = 1;  // ____INVALID____ - Should be valid
        private static int Bay6 = 1;            // Valid - Good

        public static int bay1 = 1;             // ____INVALID____ - Should be uppercase
        internal static int bay2 = 1;           // Valid - Should be uppercase
        protected internal static int bay3 = 1; // Valid - Should be uppercase
        protected static int bay4 = 1;          // ____INVALID____ - Should be uppercase
        private protected static int bay5 = 1;  // ____INVALID____ - Should be uppercase
        private static int bay6 = 1;            // Valid - Should be uppercase

        public readonly int Baz1 = 1;             // ____INVALID____ - Good
        internal readonly int Baz2 = 1;           // Valid - Should be ____INVALID____
        protected internal readonly int Baz3 = 1; // Valid - Should be ____INVALID____
        protected readonly int Baz4 = 1;          // ____INVALID____ - Good
        private protected readonly int Baz5 = 1;  // ____INVALID____ - Good??? What do the docs say
        private readonly int Baz6 = 1;            // Valid - Should be lower case

        public readonly int baz1 = 1;             // ____INVALID____ - Good
        internal readonly int baz2 = 1;           // Valid - Should be ____INVALID____
        protected internal readonly int baz3 = 1; // Valid - Should be ____INVALID____
        protected readonly int baz4 = 1;          // ____INVALID____ - Good
        private protected readonly int baz5 = 1;  // ____INVALID____ - Good??? What do the docs say
        private readonly int baz6 = 1;            // Valid - Good

        public int Bam1 = 1;             // ____INVALID____ - Good
        internal int Bam2 = 1;           // Valid - Should be ____INVALID____
        protected internal int Bam3 = 1; // Valid - Should be ____INVALID____
        protected int Bam4 = 1;          // ____INVALID____ - Good
        private protected int Bam5 = 1;  // ____INVALID____ - Good??? What do the docs say
        private int Bam6;                // Valid - Should be lowercase

        public int bam1 = 1;             // ____INVALID____ - Good
        internal int bam2 = 1;           // Valid - Should be ____INVALID____
        protected internal int bam3 = 1; // Valid - Should be ____INVALID____
        protected int bam4 = 1;          // ____INVALID____ - Good
        private protected int bam5 = 1;  // ____INVALID____ - Good??? What do the docs say
        private int bam6;                // Valid - Good

        void Foo() // To get around warnings telling you fields must be readonly.
        {
            Bay6 = 1;
            bay6 = 1;
            Bam6 = 1;
            bam6 = 1;
        }
}

@henrygab
Copy link
Collaborator Author

henrygab commented Mar 29, 2019

@RehanSaeed, it seems I was solving a different problem. The problem I had been solving was to only include the rules from the .NET guidelines. In contrast, your comments suggest that you also require the .editorconfig to have rules for private fields.

Not a problem! I wrote the rules specifically to allow such flexibility, and even had comments showing where those new rules should exist. But, as a proper set of naming rules is tricky, I also went ahead and generated the rules that you appear to be asking for.... with the exception that protected fields are still not allowed.

Please check out pull request #5.
For a fully-commented example source, I created a gist.

@RehanSaeed
Copy link
Owner

Yes, I see. I think it makes sense to make the rules comprehensive. The .NET Guidelines leave a lot of room for interpretation.

Getting closer. Not quite there yet:

class Program
{
    public const int Foo1 = 1;             // Valid - Good
    internal const int Foo2 = 1;           // Valid - Good
    protected internal const int Foo3 = 1; // Valid - Good
    protected const int Foo4 = 1;          // Valid - Good
    private protected const int Foo5 = 1;  // Valid - Good
    private const int Foo6 = 1;            // Valid - Good

    public const int foo1 = 1;             // Must be uppercase - Good
    internal const int foo2 = 1;           // Must be uppercase - Good
    protected internal const int foo3 = 1; // Must be uppercase - Good
    protected const int foo4 = 1;          // Must be uppercase - Good
    private protected const int foo5 = 1;  // Must be uppercase - Good
    private const int foo6 = 1;            // Must be uppercase - Good

    public static readonly int Bar1 = 1;             // Valid - Good
    internal static readonly int Bar2 = 1;           // Valid - Good
    protected internal static readonly int Bar3 = 1; // Valid - Good
    protected static readonly int Bar4 = 1;          // Valid - Good
    private protected static readonly int Bar5 = 1;  // Valid - Good
    private static readonly int Bar6 = 1;            // Valid - Good

    public static readonly int bar1 = 1;             // Must be uppercase - Good
    internal static readonly int bar2 = 1;           // Must be uppercase - Good
    protected internal static readonly int bar3 = 1; // Must be uppercase - Good
    protected static readonly int bar4 = 1;          // Must be uppercase - Good
    private protected static readonly int bar5 = 1;  // Must be uppercase - Good
    private static readonly int bar6 = 1;            // Must be uppercase - Good

    public static int Bay1 = 1;             // ____INVALID____ - Should be valid
    internal static int Bay2 = 1;           // Valid - Good
    protected internal static int Bay3 = 1; // ____INVALID____ - Should be valid
    protected static int Bay4 = 1;          // ____INVALID____ - Should be valid
    private protected static int Bay5 = 1;  // Valid - Good
    private static int Bay6 = 1;            // Valid - Good

    public static int bay1 = 1;             // ____INVALID____ - Should be uppercase
    internal static int bay2 = 1;           // Must be uppercase - Good
    protected internal static int bay3 = 1; // ____INVALID____ - Should be uppercase
    protected static int bay4 = 1;          // ____INVALID____ - Should be uppercase
    private protected static int bay5 = 1;  // Must be uppercase - Good
    private static int bay6 = 1;            // Must be uppercase - Good

    public readonly int Baz1 = 1;             // ____INVALID____ - Good
    internal readonly int Baz2 = 1;           // Valid - Should be ____INVALID____
    protected internal readonly int Baz3 = 1; // ____INVALID____ - Good
    protected readonly int Baz4 = 1;          // ____INVALID____ - Good
    private protected readonly int Baz5 = 1;  // ____INVALID____ - Good??? What do the docs say
    private readonly int Baz6 = 1;            // Valid - Should be lower case

    public readonly int baz1 = 1;             // ____INVALID____ - Good
    internal readonly int baz2 = 1;           // Must be uppercase - Should be ____INVALID____
    protected internal readonly int baz3 = 1; // ____INVALID____ - Good
    protected readonly int baz4 = 1;          // ____INVALID____ - Good
    private protected readonly int baz5 = 1;  // ____INVALID____ - Good??? What do the docs say
    private readonly int baz6 = 1;            // Must be uppercase - Should be Valid

    public int Bam1 = 1;             // ____INVALID____ - Good
    internal int Bam2 = 1;           // Valid - Should be ____INVALID____
    protected internal int Bam3 = 1; // ____INVALID____ - Good
    protected int Bam4 = 1;          // ____INVALID____ - Good
    private protected int Bam5 = 1;  // Valid - Should be ____INVALID____??? What do the docs say
    private int Bam6;                // Valid - Should be lowercase

    public int bam1 = 1;             // ____INVALID____ - Good
    internal int bam2 = 1;           // Must be uppercase - Should be ____INVALID____
    protected internal int bam3 = 1; // ____INVALID____ - Good
    protected int bam4 = 1;          // ____INVALID____ - Good
    private protected int bam5 = 1;  // Must be uppercase - Should be ____INVALID____??? What do the docs say
    private int bam6;                // Must be uppercase - Should be valid

    void Foo() // To get around warnings telling you fields must be readonly.
    {
        Bay6 = 1;
        bay6 = 1;
        Bam6 = 1;
        bam6 = 1;
    }
}

@henrygab
Copy link
Collaborator Author

henrygab commented Apr 4, 2019

@RehanSaeed , I think many of the things you indicate as "should be valid" are not, actually, valid under the .NET Guidelines.

Let me address these in three groups:
(1) .NET Guidelines defined behavior
(2) new C# 7.2 support for private protected
(3) where .NET Guidelines are silent (non-public fields, such as internal or private)

Each is posted below (hyperlinks included above). Each post ends with a question for you.

@henrygab
Copy link
Collaborator Author

henrygab commented Apr 4, 2019

Group 1 -- .NET Guidelines

The rules applicable to this group:
.NET guidelines state:

"DO NOT provide instance fields that are public or protected."
Note: fields marked protected internal are still considered protected fields, as they are exposed outside the encapsulation boundary identically to protected fields.

The .NET Guidelines provide two exceptions to public fields:

We exclude constant and static read-only fields from this strict restriction, because such fields, almost by definition, are never required to change.

These are covered in the rules as follows:

  • public const -- this is covered by rule set A
  • public static readonly -- this is covered by rule set B

You suggest the items in this first group should be valid. However, they are each public fields, and thus the .NET Guidelines prohibit them, because they do not fall into one of the two enumerated exceptions.

    public static int Bay1 = 1;             // ____INVALID____ - Should be valid
    public static int bay1 = 1;             // ____INVALID____ - Should be uppercase
    protected internal static int Bay3 = 1; // ____INVALID____ - Should be valid
    protected internal static int bay3 = 1; // ____INVALID____ - Should be uppercase
    protected static int Bay4 = 1;          // ____INVALID____ - Should be valid
    protected static int bay4 = 1;          // ____INVALID____ - Should be uppercase

In view of the above, do you agree that the rules I provided accurately mark each of the above as invalid? If not, can you point to the rules that indicate these should be allowed / valid / PascalCase?

@henrygab
Copy link
Collaborator Author

henrygab commented Apr 4, 2019

Group 2 -- new C# 7.2 support for private protected

You correctly ask what the docs (.NET Guidelines) say. The .NET Guidelines were written prior to this combination of permissions being defined, and as thus (technically) silent. However, the .NET Guidelines explain their rationale.

The principle of encapsulation is one of the most important notions in object-oriented design. This principle states that data stored inside an object should be accessible only to that object.
A useful way to interpret the principle is to say that a type should be designed so that changes to fields of that type (name or type changes) can be made without breaking code other than for members of the type. This interpretation immediately implies that all fields must be private.

The definition of private protected is:

A private protected member is accessible by types derived from the containing class, but only within its containing assembly.

Based on the above definition, it is clear that the private protected accessibility modifier is more restrictive than an internal field. Internal fields are treated as private fields by the guidelines. Therefore, because private protected provides stronger encapsulation than internal, the .NET Guidelines do not apply.

Therefore, the following two items are NOT defined by the .NET Guideline rules for fields (A/B, L/M). However, I DID include them in your private rule sets (C/D), and will address them in the third post.

    private protected int Bam5 = 1;  // Valid - Should be ____INVALID____??? What do the docs say
    private protected int bam5 = 1;  // Must be uppercase - Should be ____INVALID____??? What do the docs say

Do you agree with the above analysis, and thus believe the .NET Guidelines would not directly apply to this field type? If not, can you help me understand the alternatives (and preferably, support for the alternative)?

@henrygab
Copy link
Collaborator Author

henrygab commented Apr 4, 2019

Group 3 -- Additional / extended rules

Presuming you agree with the above two posts, we have only a few items remaining. Each of these falls outside the .NET Guidelines, but generally matches your prior-stated desires, which I will summarize here in the order the rules are applied.

Note: These rules do not apply to public fields, which are matched by Rulesets A/B.
The custom rulesets are C, D, and K.

  1. PascalCase for all non-private constant fields, for consistency with .NET Guidelines (Ruleset C)
  2. PascalCase for all non-private static readonly fields, for consistency with .NET Guidelines (Ruleset D)
  3. PascalCase for all non-private fields, for consistency with .NET Guidelines (Ruleset K)

Again, these are long, so I will break into three posts:

@henrygab
Copy link
Collaborator Author

henrygab commented Apr 4, 2019

Group 3a - New C# 7.2 private protected fields

    private protected int Bam5 = 1;  // Valid - Should be ____INVALID____???
    private protected int bam5 = 1;  // Must be uppercase - Should be ____INVALID____???

The documentation describes private protected as:

Access is limited to the containing class or types derived from the containing class within the current assembly. Available since C# 7.2.

This means that these fields have MORE restricted access than internal fields. The .NET Guidelines do not cover naming conventions for field that are only accessible within the assembly that defines the class, and thus are not disallowed by the .NET Guidelines. Therefore, they are clearly allowed (i.e., they are not invalid) according to the .NET Guidelines, although they do fall in the custom rule bucket.

There is a strong argument that these should, nonetheless, use PascalCase, which is what is being enforced.

Do you agree that these should be PascalCase, or would you prefer to enforce camelCasing, similar to what I now believe you want to do with fully-private fields?

@henrygab
Copy link
Collaborator Author

henrygab commented Apr 4, 2019

Group 3b - Internal fields

You previously wrote:

This is what is valid in the current implementation:
...
internal readonly int Bar2 = 1;
...
internal int Bar2 = 1;
...
I would expect readonly fields to be treated the same as non-readonly

Based on the above comments, I understood that you desired both internal and internal readonly fields to be considered valid (although you were silent as to casing -- I applied PascalCasing).

However, just now, you indicated that each of the below should be found as INVALID:

    internal int Bam2 = 1;           // Valid - Should be ____INVALID____
    internal int bam2 = 1;           // Must be uppercase - Should be ____INVALID____
    internal readonly int baz2 = 1;           // Must be uppercase - Should be ____INVALID____

I am now at a loss as to what your expectations are for internal and internal readonly fields, as the rules provided seem to reflect your prior statements.

Can you help me understand what rules you expect for internal and internal readonly fields, and why?

@henrygab
Copy link
Collaborator Author

henrygab commented Apr 4, 2019

Group 3c - Fully private fields

Per your earlier comments, you wrote only the following:

private fields must begin with an uppercase character is incorrect.

Now, you write the following:

    private int Bam6;                // Valid - Should be lowercase
    private int bam6;                // Must be uppercase - Should be valid
    private readonly int baz6 = 1;            // Must be uppercase - Should be Valid

Based on the above, is it accurate to state that you want to enforce camelCasing for all private fields?

@RehanSaeed
Copy link
Owner

RehanSaeed commented Apr 5, 2019

Group 1 -- .NET Guidelines

I think you are correct. So we end up with the following expected result:

    public static int Bay1 = 1;             // ____INVALID____
    public static int bay1 = 1;             // ____INVALID____
    protected internal static int Bay3 = 1; // ____INVALID____
    protected internal static int bay3 = 1; // ____INVALID____
    protected static int Bay4 = 1;          // ____INVALID____
    protected static int bay4 = 1;          // ____INVALID____

@RehanSaeed
Copy link
Owner

Group 2 -- new C# 7.2 support for private protected

I suspect we should treat private protected the same as protected but was not sure about the guidelines.

@RehanSaeed
Copy link
Owner

Group 3a - New C# 7.2 private protected fields

As I've said above, I suspect the best thing to do is treat private protected the same as protected i.e. ____INVALID____ because they are very similar. Therefore, the question of casing doesn't come up. What do you think?

@RehanSaeed
Copy link
Owner

Group 3b - Internal fields

Given this:

"DO NOT provide instance fields that are public or protected."
Note: fields marked protected internal are still considered protected fields, as they are exposed outside the encapsulation boundary identically to protected fields.

I think internal also counts here. So therefore all of this would be ____INVALID____ right?

internal int Bam2 = 1;           // Valid - Should be ____INVALID____
internal int bam2 = 1;           // Must be uppercase - Should be ____INVALID____
internal readonly int baz2 = 1;           // Must be uppercase - Should be ____INVALID____

@RehanSaeed
Copy link
Owner

Group 3c - Fully private fields

Based on the above, is it accurate to state that you want to enforce camelCasing for all private fields?

Yes, that goes along with the guidelines does it not?

@RehanSaeed
Copy link
Owner

I think it's most useful to look at the test cases below. If I'm right, based on all your comments this is what we're now aiming for:

class Program
{
    public const int Foo1 = 1;             // Valid
    internal const int Foo2 = 1;           // Valid
    protected internal const int Foo3 = 1; // Valid
    protected const int Foo4 = 1;          // Valid
    private protected const int Foo5 = 1;  // Valid
    private const int Foo6 = 1;            // Valid

    public const int foo1 = 1;             // Must be uppercase
    internal const int foo2 = 1;           // Must be uppercase
    protected internal const int foo3 = 1; // Must be uppercase
    protected const int foo4 = 1;          // Must be uppercase
    private protected const int foo5 = 1;  // Must be uppercase
    private const int foo6 = 1;            // Must be uppercase

    public static readonly int Bar1 = 1;             // Valid
    internal static readonly int Bar2 = 1;           // Valid
    protected internal static readonly int Bar3 = 1; // Valid
    protected static readonly int Bar4 = 1;          // Valid
    private protected static readonly int Bar5 = 1;  // Valid
    private static readonly int Bar6 = 1;            // Valid

    public static readonly int bar1 = 1;             // Must be uppercase
    internal static readonly int bar2 = 1;           // Must be uppercase
    protected internal static readonly int bar3 = 1; // Must be uppercase
    protected static readonly int bar4 = 1;          // Must be uppercase
    private protected static readonly int bar5 = 1;  // Must be uppercase
    private static readonly int bar6 = 1;            // Must be uppercase

    public static int Bay1 = 1;             // ____INVALID____
    internal static int Bay2 = 1;           // ____INVALID____
    protected internal static int Bay3 = 1; // ____INVALID____
    protected static int Bay4 = 1;          // ____INVALID____
    private protected static int Bay5 = 1;  // ____INVALID____
    private static int Bay6 = 1;            // Valid

    public static int bay1 = 1;             // ____INVALID____
    internal static int bay2 = 1;           // ____INVALID____
    protected internal static int bay3 = 1; // ____INVALID____
    protected static int bay4 = 1;          // ____INVALID____
    private protected static int bay5 = 1;  // ____INVALID____
    private static int bay6 = 1;            // Must be uppercase

    public readonly int Baz1 = 1;             // ____INVALID____
    internal readonly int Baz2 = 1;           // ____INVALID____
    protected internal readonly int Baz3 = 1; // ____INVALID____
    protected readonly int Baz4 = 1;          // ____INVALID____
    private protected readonly int Baz5 = 1;  // ____INVALID____
    private readonly int Baz6 = 1;            // Should be lower case

    public readonly int baz1 = 1;             // ____INVALID____
    internal readonly int baz2 = 1;           // ____INVALID____
    protected internal readonly int baz3 = 1; // ____INVALID____
    protected readonly int baz4 = 1;          // ____INVALID____
    private protected readonly int baz5 = 1;  // ____INVALID____
    private readonly int baz6 = 1;            // Valid

    public int Bam1 = 1;             // ____INVALID____
    internal int Bam2 = 1;           // ____INVALID____
    protected internal int Bam3 = 1; // ____INVALID____
    protected int Bam4 = 1;          // ____INVALID____
    private protected int Bam5 = 1;  // ____INVALID____
    private int Bam6;                // Should be lowercase

    public int bam1 = 1;             // ____INVALID____
    internal int bam2 = 1;           // ____INVALID____
    protected internal int bam3 = 1; // ____INVALID____
    protected int bam4 = 1;          // ____INVALID____
    private protected int bam5 = 1;  // ____INVALID____
    private int bam6;                // Valid

    void Foo() // To get around warnings telling you fields must be readonly.
    {
        Bay6 = 1;
        bay6 = 1;
        Bam6 = 1;
        bam6 = 1;
    }
}

This is so complex. Apologies if this is taking too long. Give me a shout on Teams and we can talk through any differences in our understanding. GitHub comments are not a great forum for this kind of problem.

@henrygab
Copy link
Collaborator Author

henrygab commented Apr 5, 2019

@RehanSaeed
👍 Yes, it's complex. But, we're getting close! So close! 👍

I'd already created a GIST that documents each option. The comments start with "good" if the rule matches expectations, followed by parenthesis indicated the expected results, followed by an indication of which Ruleset causes that result. You can load that GIST into a solution and compile.

It seems we're down to two questions:

  1. For internal and private protected fields, do you want to prohibit them entirely?
    (more in-depth discussion of group 2/3a/3b is below … I recommend allowing and requiring PascalCasing...)
    If you feel the .NET Guidelines prohibit this type of field, can you please help me understand by pointing me to a source?
    FYI, the source I found says:

The field-naming guidelines apply to static public and protected fields. Internal and private fields are not covered by guidelines, and public or protected instance fields are not allowed by the member design guidelines.

  1. For group 3c, you indicate the .NET Guidelines specify somewhere that private fields must be camelCase.
    Because I cannot find where naming of private fields is specified by .NET Naming Guidelines, can you please help me understand by pointing me to a source?

Differentiating between what the .NET Guidelines require, and what they leave to the user's preferences, is a key part of this Issue and Pull Request.

Group 2 / 3a / 3b

For the purposes of the .NET Naming Guidelines, private protected is not similar to protected, but instead similar to internal, because it does not break encapsulation at the assembly level. internal fields also do not break the encapsulation at the assembly level, and therefore are outside the scope of the .NET Naming Guidelines.

There are many valid reasons to allow internal fields and use internal fields. Any rule in .editorconfig dealing with them would be user-specific. You previously indicated that internal fields would be PascalCasing ('must be uppercase'). I agree that is the right result, and that's what the rules currently apply....

@henrygab
Copy link
Collaborator Author

(Summarizing out-of-band conversation with @RehanSaeed)

  1. Because private protected are internal to an assembly, we agree that (for .NET Naming Guideline purposes) they are essentially internal variables with additional restrictions. internal makes sense because they are never exposed outside the containing assembly.

  2. We agree that the .NET Naming Guidelines specify no naming convention for private protected, because internal variables are explicitly out of scope there.

  3. We agree that the .NET Naming Guidelines do not prohibit private protected fields, again because internal variables are explicitly out of scope.

  4. In answer to the question about private fields needing to be camelCase, you indicated that while this is not a .NET Naming Guidelines requirement, it IS a StyleCop rule. No problem, that can be easily enforced.

  5. As a new request, you wanted a version of some sort within the file's header comments, to help people determine what version file they have. We agreed that a YYYYMMDD format would be a good first effort towards that goal..

  6. As a new request, you wanted a link to the .NET Naming Guidelines added to the README.md.

I'll ping again with an updated pull request.

@henrygab
Copy link
Collaborator Author

@RehanSaeed Pull request updated.
The PR includes a sample C# project.
If there's anything that is not working as you expect, please add to the sample project in the branch, or refer to the sample code in the project. Having the project files should greatly simplify precise targeting of any issues.

@RehanSaeed
Copy link
Owner

A sample C# project was a good idea, so kudos. A few minor changes requested. Also added you as a core contributor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Issues describing an enhancement or pull requests adding an enhancement.
Projects
None yet
Development

No branches or pull requests

2 participants