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

Wire through editorconfig #27232

Conversation

agocke
Copy link
Member

@agocke agocke commented May 29, 2018

This PR provides a new command line compiler option: "/analyzerconfig:" that
takes a set of "analyzer config" files (supersets of EditorConfig files)
that the compiler will consider for diagnostic configuration.

@agocke agocke added this to the 15.8 milestone May 29, 2018
@agocke agocke requested a review from jaredpar May 29, 2018 22:15
@agocke agocke requested a review from a team as a code owner May 29, 2018 22:15
@agocke agocke force-pushed the wire-through-editorconfig branch from 90a2ddd to d09083f Compare May 29, 2018 22:59
@@ -244,6 +246,231 @@ internal SourceText TryReadFileContent(CommandLineSourceFile file, IList<Diagnos
}
}

internal static ImmutableArray<ImmutableDictionary<string, ReportDiagnostic>> GetAnalyzerConfigOptions(
IReadOnlyList<string> sourcePaths,
Copy link
Member

@jaredpar jaredpar May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why IReadOnlyList<string> here? Next method takes an ImmutableArray<string> for a paths parameter. #Resolved

Copy link
Member Author

@agocke agocke May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is used for testing and I pass in an array instead of an ImmutableArray because the syntax is shorter and clearer. #Resolved

diagId = diagId.Substring(0, diagId.Length - DiagnosticOptionSuffix.Length);

ReportDiagnostic? severity = null;
switch (CaseInsensitiveComparison.ToLower(value))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switch (CaseInsensitiveComparison.ToLower(value)) [](start = 23, length = 50)

How often are we going to be parsing this? Seems like we're creating an extra allocation per property here. Know it's annoying but may want to consider wiriting out this if by hand.

key.EndsWith(DiagnosticOptionSuffix, StringComparison.Ordinal))
{
string diagId = key.Substring(DiagnosticOptionPrefix.Length);
diagId = diagId.Substring(0, diagId.Length - DiagnosticOptionSuffix.Length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be done as a single Substring operation?


readEditorConfigFiles(analyzerConfigPaths, analyzerConfigs);

if (diagnostics.HasAnyErrors())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this check be done first? The code above has no impact on the content of this DiagnosticBag instance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see now. That's really subtle as you capture some of the context (diagnostics) but pass some explicitly (analyzerConfigs). I think you should be consistent here and do one or the other.


In reply to: 191616102 [](ancestors = 191616102)

string fileContent = TryReadFileContent(configPath, diagnostics, out string normalizedPath);
if (fileContent is null)
{
// Error reading a file. Bail out and report error.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't see how an error gets reported here.


processedDirs.Add(directory);
var forwardPath = PathWithForwardSlashSeparators(directory);
var editorConfig = EditorConfig.Parse(fileContent, forwardPath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do errors get reported on this path?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are never any parse errors for EditorConfig files. Any invalid sections are skipped and no errors are reported.

@agocke agocke closed this May 31, 2018
@agocke agocke reopened this May 31, 2018
@agocke
Copy link
Member Author

agocke commented May 31, 2018

test windows_debug_spanish_unit32_prtest please

@agocke agocke force-pushed the wire-through-editorconfig branch from 47750e8 to 8df6338 Compare June 6, 2018 01:23
@agocke agocke closed this Jun 6, 2018
@agocke agocke reopened this Jun 6, 2018
@gafter gafter self-assigned this Jun 7, 2018
@gafter
Copy link
Member

gafter commented Jun 7, 2018

This PR is dependent on #27119 and contains commits from that PR.

I think this is no longer true? #Resolved

@gafter gafter self-requested a review June 7, 2018 00:30
@agocke
Copy link
Member Author

agocke commented Jun 7, 2018

Yup! Merged commits have been removed to assist reviewing

@agocke
Copy link
Member Author

agocke commented Jun 7, 2018

@jaredpar Any more comments here?

(e1, e2) => e1.Directory.Length.CompareTo(e2.Directory.Length)));

var allDiagnosticOptions =
ArrayBuilder<ImmutableDictionary<string, ReportDiagnostic>>.GetInstance(sourcePaths.Count);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if the intent is ordinal comparison consider using an explicit comparer here. This makes it clear to later developers that the scenario was considered.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're thinking of the ImmutableDictionary. This is an ImmutableArray of ImmutableDictionaries, so the only comparer I expect to work here is reference comparison. The later creation of the ImmutableDictionaries should have explicit comparers.

@@ -5339,4 +5339,13 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_OutVariableCannotBeByRef" xml:space="preserve">
<value>An out variable cannot be declared as a ref local</value>
</data>
<data name="WRN_InvalidSeverityInAnalyzerConfig" xml:space="preserve">
<value>The diagnostic '{0}' was given an invalid severity '{1}' in analyzer config file.</value>
Copy link
Member

@gafter gafter Jun 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in analyzer config file [](start = 68, length = 23)

"in analyzer config file"? In THE analyzer config file? In AN analyzer config file? Can we identify the offending config file? I presume this message identifies the offending file and line.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the parser does not hold enough information to produce an exact location. I've changed the method to include the directory of the editorconfig file, which should be unambiguous since there is only more than one in a rare error scenario. However, I think producing the full path wouldn't be too hard, it's just that I've refactored this API slightly in a later PR so I'd prefer to fix it later. I've filed https://github.com/dotnet/roslyn/projects/18#card-10413696 to track this issue.

<value>Invalid severity in analyzer config file.</value>
</data>
<data name="ERR_MultipleAnalyzerConfigsInSameDir" xml:space="preserve">
<value>Multiple analyzer configs cannot be in the same directory ('{0}').</value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

analyzer configs [](start = 20, length = 16)

... analyzer config files cannot ...

Copy link
Member

@jaredpar jaredpar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

/// A set of paths to EditorConfig-compatible analyzer config files.
/// </summary>
public ImmutableArray<string> AnalyzerConfigPaths { get; internal set; }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing. Why do we call them "paths" instead of a filenames? Why do we use string instead of CommandLineSourceFile like we do for AdditionalFiles and EmbeddedFiles?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this is a set of paths to directories containing editor config files?


In reply to: 193898640 [](ancestors = 193898640)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A file name usually just refers to the name of the file, i.e. for a file /a/b/c.cs the file name would be c.cs, while the path would be /a/b/c.cs. Right now these happen to be absolute paths, but I didn't want to guarantee that in the API.

As to why I didn't choose to use CommandLineSourceFile -- I'm not sure why we did choose that for AdditionalFiles and EmbeddedFiles. CommandLineSourceFile is a struct with two entries: Path, which is the same as the string here, and IsScript which is a bool that is always false, since by definition only source files can be script files. I have a feeling that the helper for parsing separated paths happened to return CommandLineSourceFiles and these additions couldn't be bothered to convert. Otherwise I see no reason for them just to be paths.


ReportDiagnostic? severity;
if (CaseInsensitiveComparison.Equals(value, "default"))
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be done using a switch on value.ToLowerInvariant(). Since we're looking at ASCII, no fancy Unicode-correct case insensitivity is needed.

Why do we require an exact match for the prefix and suffix of the key, but compare the middle part using relaxed case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed this to use OrdinalIgnoreCase. Jared suggested not calling ToLower or ToUpper because it will allocate an additional string for each property. The if is more verbose, but avoids that.

As to the mixed casing -- the key is always lower-cased by the EditorConfig parser, while the value is case-preserving, so we compare the value case-insensitively, but the key case-sensitively.

@@ -244,6 +246,233 @@ internal SourceText TryReadFileContent(CommandLineSourceFile file, IList<Diagnos
}
}

internal static ImmutableArray<ImmutableDictionary<string, ReportDiagnostic>> GetAnalyzerConfigOptions(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetAnalyzerConfigOptions [](start = 86, length = 24)

I lack context to understand what this method is supposed to do. What are the arguments? Can you add a doc comment please?

@gafter
Copy link
Member

gafter commented Jun 7, 2018

Is there a design doc that will help me know what this PR is trying to do?

@agocke
Copy link
Member Author

agocke commented Jun 7, 2018

@gafter Great point. I will write up a doc to accompany the feature. It will follow closely what's described on this page: https://editorconfig.org/

@@ -65,8 +71,7 @@ private EditorConfig(Section globalSection, ImmutableArray<Section> namedSection
/// <summary>
/// Gets whether this editorconfig is a the topmost editorconfig.
/// </summary>
public bool IsRoot
=> GlobalSection.Properties.TryGetValue("root", out string val) && val == "true";
public bool IsRoot => GlobalSection.Properties.TryGetValue("root", out string val) && val == "true";

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the topmost editorconfig file in the project lacks this property, it should still be considered the root. How do we handle that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is bad. Root should mean that no properties from higher editorconfigs should apply, not necessarily that there couldn't be editorconfigs higher in the directory tree. This work is tracked at #27644

Copy link
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants