From 31073d01d2c50004a94e4d6e871c3f6c569cc2b3 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 24 Mar 2017 13:01:20 -0700 Subject: [PATCH] Add "#error version" to C# and udpate UpgradeProject to support 7.1 (#18045) --- .../Portable/CSharpResources.Designer.cs | 9 ++ .../CSharp/Portable/CSharpResources.resx | 5 +- .../CSharp/Portable/Errors/ErrorCode.cs | 3 +- .../CSharp/Portable/Parser/DirectiveParser.cs | 25 +++- .../Test/CommandLine/CommandLineTests.cs | 1 + .../Syntax/LexicalAndXml/PreprocessorTests.cs | 114 +++++++++++++++++- .../Portable/CommandLine/CommonCompiler.cs | 19 ++- .../UpgradeProject/UpgradeProjectTests.cs | 44 ++++++- .../Diagnostics/AbstractUserDiagnosticTest.cs | 5 +- .../CSharpUpgradeProjectCodeFixProvider.cs | 4 +- .../XML/ParseTreeDescription.vb | 2 +- 11 files changed, 207 insertions(+), 24 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index b2d5452c58a..048d24d8b40 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -2851,6 +2851,15 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to Compiler version: '{0}'. Language version: {1}.. + /// + internal static string ERR_CompilerAndLanguageVersion { + get { + return ResourceManager.GetString("ERR_CompilerAndLanguageVersion", resourceCulture); + } + } + /// /// Looks up a localized string similar to An expression tree lambda may not contain a COM call with ref omitted on arguments. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index b97e92c32b4..b92caac49a2 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5041,4 +5041,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A value of type 'void' may not be assigned. - \ No newline at end of file + + Compiler version: '{0}'. Language version: {1}. + + diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 5814b2db8a0..3a3eb488744 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1471,5 +1471,6 @@ internal enum ErrorCode ERR_InvalidPreprocessingSymbol = 8301, ERR_FeatureNotAvailableInVersion7_1 = 8302, ERR_LanguageVersionCannotHaveLeadingZeroes = 8303, + ERR_CompilerAndLanguageVersion = 8304, } -} \ No newline at end of file +} diff --git a/src/Compilers/CSharp/Portable/Parser/DirectiveParser.cs b/src/Compilers/CSharp/Portable/Parser/DirectiveParser.cs index 0d137cd1e9d..30f140822c4 100644 --- a/src/Compilers/CSharp/Portable/Parser/DirectiveParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/DirectiveParser.cs @@ -6,6 +6,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax { + using System.Reflection; using Microsoft.CodeAnalysis.Syntax.InternalSyntax; internal class DirectiveParser : SyntaxParser @@ -308,7 +309,29 @@ private DirectiveTriviaSyntax ParseErrorOrWarningDirective(SyntaxToken hash, Syn //could be negative if part of the error text comes from the trailing trivia of the keyword token int triviaOffset = eod.GetLeadingTriviaWidth() - triviaWidth; - eod = this.AddError(eod, triviaOffset, triviaWidth, isError ? ErrorCode.ERR_ErrorDirective : ErrorCode.WRN_WarningDirective, triviaBuilder.ToString()); + string errorText = triviaBuilder.ToString(); + eod = this.AddError(eod, triviaOffset, triviaWidth, isError ? ErrorCode.ERR_ErrorDirective : ErrorCode.WRN_WarningDirective, errorText); + + if (isError) + { + if (errorText.Equals("version", StringComparison.Ordinal)) + { + Assembly assembly = typeof(CSharpCompiler).GetTypeInfo().Assembly; + string version = CommonCompiler.GetAssemblyFileVersion(assembly); + eod = this.AddError(eod, triviaOffset, triviaWidth, ErrorCode.ERR_CompilerAndLanguageVersion, version, + this.Options.SpecifiedLanguageVersion.ToDisplayString()); + } + else + { + const string versionMarker = "version:"; + if (errorText.StartsWith(versionMarker, StringComparison.Ordinal) && + LanguageVersionFacts.TryParse(errorText.Substring(versionMarker.Length), out var languageVersion)) + { + ErrorCode error = this.Options.LanguageVersion.GetErrorCode(); + eod = this.AddError(eod, triviaOffset, triviaWidth, error, "version", new CSharpRequiredLanguageVersion(languageVersion)); + } + } + } } if (isError) diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index e757b3d1849..802740f52b6 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -1307,6 +1307,7 @@ public void LanguageVersionAdded_Canary() // - update the "UpgradeProject" codefixer // - update the IDE drop-down for selecting Language Version // - don't fix the canary test until you update all the tests that include it + // - update the command-line documentation (CommandLine.md) Assert.Equal(LanguageVersion.CSharp7_1, LanguageVersion.Latest.MapSpecifiedToEffectiveVersion()); Assert.Equal(LanguageVersion.CSharp7, LanguageVersion.Default.MapSpecifiedToEffectiveVersion()); } diff --git a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/PreprocessorTests.cs b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/PreprocessorTests.cs index 81883404b1e..d8749595ddc 100644 --- a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/PreprocessorTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/PreprocessorTests.cs @@ -2,10 +2,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; +using System.Reflection; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; @@ -58,7 +61,7 @@ internal struct MemberInfo #endregion - public class PreprocessorTests + public class PreprocessorTests : TestBase { public PreprocessorTests() { @@ -182,7 +185,30 @@ private void VerifyDirectivesSpecial(CSharpSyntaxNode node, params DirectiveInfo { Assert.Equal(exp.Text, ((DefineDirectiveTriviaSyntax)dt).Name.ValueText); // Text } - + break; + case SyntaxKind.ErrorDirectiveTrivia: + if (null != exp.Text) + { + Assert.Equal(exp.Text, ((ErrorDirectiveTriviaSyntax)dt).EndOfDirectiveToken.ToFullString()); + } + break; + case SyntaxKind.LoadDirectiveTrivia: + if (null != exp.Text) + { + Assert.Equal(exp.Text, ((LoadDirectiveTriviaSyntax)dt).File.ValueText); + } + break; + case SyntaxKind.UndefDirectiveTrivia: + if (null != exp.Text) + { + Assert.Equal(exp.Text, ((UndefDirectiveTriviaSyntax)dt).Name.ValueText); + } + break; + case SyntaxKind.ReferenceDirectiveTrivia: + if (null != exp.Text) + { + Assert.Equal(exp.Text, ((ReferenceDirectiveTriviaSyntax)dt).File.ValueText); + } break; case SyntaxKind.LineDirectiveTrivia: var ld = dt as LineDirectiveTriviaSyntax; @@ -217,7 +243,12 @@ private void VerifyDirectivesSpecial(CSharpSyntaxNode node, params DirectiveInfo Assert.NotEqual(SyntaxKind.None, ld.File.Kind()); Assert.Equal(exp.Text, ld.File.Value); } - + break; + default: + if (null != exp.Text) + { + Assert.True(false, String.Format("You are expecting some text in the directive, but this method doesn't know how to verify it for `{0}`.", exp.Kind)); + } break; } // switch } @@ -2560,7 +2591,7 @@ public void TestNegUndefWithBadTokensAfterName() var node = Parse(text); TestRoundTripping(node, text, false); VerifyErrorCode(node, (int)ErrorCode.ERR_EndOfPPLineExpected); - VerifyDirectivesSpecial(node, new DirectiveInfo { Kind = SyntaxKind.UndefDirectiveTrivia, Status = NodeStatus.IsActive, Text = "FOO(" }); + VerifyDirectivesSpecial(node, new DirectiveInfo { Kind = SyntaxKind.UndefDirectiveTrivia, Status = NodeStatus.IsActive, Text = "FOO" }); } [Fact] @@ -2584,7 +2615,7 @@ public void TestNegUndefWithBadNumericalName() var node = Parse(text); TestRoundTripping(node, text, false); VerifyErrorCode(node, (int)ErrorCode.ERR_IdentifierExpected); - VerifyDirectivesSpecial(node, new DirectiveInfo { Kind = SyntaxKind.UndefDirectiveTrivia, Status = NodeStatus.IsActive, Text = "1234" }); + VerifyDirectivesSpecial(node, new DirectiveInfo { Kind = SyntaxKind.UndefDirectiveTrivia, Status = NodeStatus.IsActive, Text = "" }); } [Fact] @@ -2962,6 +2993,71 @@ private void CheckDiagnosticStringFileName(string compilationFileName, string li Assert.Equal(expectedErrorStringFileName, actualErrorStringFileName); } + [Fact] + public void TestErrorWithVersion() + { + var text = "#error version"; + var node = Parse(text, SourceCodeKind.Regular); + TestRoundTripping(node, text, disallowErrors: false); + VerifyDirectivesSpecial(node, new DirectiveInfo + { + Kind = SyntaxKind.ErrorDirectiveTrivia, + Status = NodeStatus.IsActive, + Text = "version" + }); + + node.GetDiagnostics().Verify( + // (1,8): error CS1029: #error: 'version' + // #error version + Diagnostic(ErrorCode.ERR_ErrorDirective, "version").WithArguments("version").WithLocation(1, 8), + // (1,8): error CS8304: Compiler version: '42.42.42.42424 ()'. Language version: 4. + // #error version + Diagnostic(ErrorCode.ERR_CompilerAndLanguageVersion, "version").WithArguments(GetExpectedVersion(), "4").WithLocation(1, 8) + ); + } + + [Fact] + public void TestErrorWithVersionNumber() + { + var text = "#error version:7.1"; + var node = Parse(text, SourceCodeKind.Regular); + TestRoundTripping(node, text, disallowErrors: false); + VerifyDirectivesSpecial(node, new DirectiveInfo + { + Kind = SyntaxKind.ErrorDirectiveTrivia, + Status = NodeStatus.IsActive, + Text = "version:7.1" + }); + + node.GetDiagnostics().Verify( + // (1,8): error CS1029: #error: 'version:7.1' + // #error version:7.1 + Diagnostic(ErrorCode.ERR_ErrorDirective, "version:7.1").WithArguments("version:7.1"), + // (1,8): error CS8025: Feature 'version' is not available in C# 4. Please use language version 7.1 or greater. + // #error version:7.1 + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion4, "version:7.1").WithArguments("version", "7.1").WithLocation(1, 8) + ); + } + + [Fact] + public void TestErrorWithInvalidVersion() + { + var text = "#error version:A.B"; + var node = Parse(text, SourceCodeKind.Regular); + TestRoundTripping(node, text, disallowErrors: false); + VerifyDirectivesSpecial(node, new DirectiveInfo + { + Kind = SyntaxKind.ErrorDirectiveTrivia, + Status = NodeStatus.IsActive, + Text = "version:A.B" + }); + + node.GetDiagnostics().Verify( + // (1,8): error CS1029: #error: 'version:A.B' + // #error version:A.B + Diagnostic(ErrorCode.ERR_ErrorDirective, "version:A.B").WithArguments("version:A.B").WithLocation(1, 8) + ); + } #endregion #region #line @@ -3874,5 +3970,13 @@ public void TestLoadWithComment() } #endregion + + private static string GetExpectedVersion() + { + Assembly assembly = typeof(CSharpCompiler).GetTypeInfo().Assembly; + string fileVersion = assembly.GetCustomAttribute().Version; + string hash = CommonCompiler.ExtractShortCommitHash(assembly.GetCustomAttribute().Hash); + return $"{fileVersion} ({hash})"; + } } } diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs index 062041c9eb9..8297fe9c10c 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @@ -128,18 +128,15 @@ public CommonCompiler(CommandLineParser parser, string responseFile, string[] ar /// internal virtual string GetAssemblyFileVersion() { - if (_clientDirectory != null) - { - Assembly assembly = Type.GetTypeInfo().Assembly; - var name = $"{assembly.GetName().Name}.dll"; - var filePath = Path.Combine(_clientDirectory, name); - var fileVersionInfo = FileVersionInfo.GetVersionInfo(filePath); - string hash = ExtractShortCommitHash(assembly.GetCustomAttribute()?.Hash); - - return $"{fileVersionInfo.FileVersion} ({hash})"; - } + Assembly assembly = Type.GetTypeInfo().Assembly; + return GetAssemblyFileVersion(assembly); + } - return ""; + internal static string GetAssemblyFileVersion(Assembly assembly) + { + string assemblyVersion = assembly.GetCustomAttribute()?.Version; + string hash = ExtractShortCommitHash(assembly.GetCustomAttribute()?.Hash); + return $"{assemblyVersion} ({hash})"; } internal static string ExtractShortCommitHash(string hash) diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs index ce971a1e8b1..4dac20e87c1 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/UpgradeProject/UpgradeProjectTests.cs @@ -39,7 +39,7 @@ internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProvider } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)] - public async Task UpgradeProjectToDefault() + public async Task UpgradeProjectFromCSharp6ToDefault() { await TestLanguageVersionUpgradedAsync( @" @@ -55,7 +55,7 @@ void A() } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)] - public async Task UpgradeProjectToCSharp7() + public async Task UpgradeProjectFromCSharp6ToCSharp7() { await TestLanguageVersionUpgradedAsync( @" @@ -71,6 +71,46 @@ void A() index: 1); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)] + public async Task UpgradeProjectFromCSharp7ToLatest() + { + await TestLanguageVersionUpgradedAsync( +@" +class Program +{ +#error version:[|7.1|] +}", + LanguageVersion.Latest, + new CSharpParseOptions(LanguageVersion.CSharp7)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)] + public async Task UpgradeProjectFromCSharp7_1ToLatest() + { + await TestLanguageVersionUpgradedAsync( +@" +class Program +{ +#error version:[|7.1|] +}", + LanguageVersion.Latest, + new CSharpParseOptions(LanguageVersion.CSharp7_1)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)] + public async Task UpgradeProjectFromCSharp7ToCSharp7_1() + { + await TestLanguageVersionUpgradedAsync( +@" +class Program +{ +#error [|version:7.1|] +}", + LanguageVersion.CSharp7_1, + new CSharpParseOptions(LanguageVersion.CSharp7), + index: 1); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)] public async Task UpgradeAllProjectsToDefault() { diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs b/src/EditorFeatures/TestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs index b2b8d083363..e88221f1d72 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -69,7 +70,9 @@ protected bool TryGetDocumentAndSelectSpan(TestWorkspace workspace, out Document protected Document GetDocumentAndAnnotatedSpan(TestWorkspace workspace, out string annotation, out TextSpan span) { - var hostDocument = workspace.Documents.Single(d => d.AnnotatedSpans.Any()); + var annotatedDocuments = workspace.Documents.Where(d => d.AnnotatedSpans.Any()); + Debug.Assert(!annotatedDocuments.IsEmpty(), "No annotated span found"); + var hostDocument = annotatedDocuments.Single(); var annotatedSpan = hostDocument.AnnotatedSpans.Single(); annotation = annotatedSpan.Key; span = annotatedSpan.Value.Single(); diff --git a/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs b/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs index 504e942cd69..fe6529b5841 100644 --- a/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UpgradeProject/CSharpUpgradeProjectCodeFixProvider.cs @@ -22,9 +22,11 @@ internal class CSharpUpgradeProjectCodeFixProvider : AbstractUpgradeProjectCodeF private const string CS8025 = nameof(CS8025); // error CS8025: Feature is not available in C# 4. Please use language version X or greater. private const string CS8026 = nameof(CS8026); // error CS8026: Feature is not available in C# 5. Please use language version X or greater. private const string CS8059 = nameof(CS8059); // error CS8059: Feature is not available in C# 6. Please use language version X or greater. + private const string CS8107 = nameof(CS8107); // error CS8059: Feature is not available in C# 7.0. Please use language version X or greater. + private const string CS8302 = nameof(CS8302); // error CS8302: Feature is not available in C# 7.1. Please use language version X or greater. public override ImmutableArray FixableDiagnosticIds { get; } = - ImmutableArray.Create(CS8022, CS8023, CS8024, CS8025, CS8026, CS8059); + ImmutableArray.Create(CS8022, CS8023, CS8024, CS8025, CS8026, CS8059, CS8107, CS8302); public override string UpgradeThisProjectResource => CSharpFeaturesResources.Upgrade_this_project_to_csharp_language_version_0; public override string UpgradeAllProjectsResource => CSharpFeaturesResources.Upgrade_all_csharp_projects_to_language_version_0; diff --git a/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicSyntaxGenerator/XML/ParseTreeDescription.vb b/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicSyntaxGenerator/XML/ParseTreeDescription.vb index bdea20a30cd..e7ac06bec7c 100644 --- a/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicSyntaxGenerator/XML/ParseTreeDescription.vb +++ b/src/Tools/Source/CompilerGeneratorTools/Source/VisualBasicSyntaxGenerator/XML/ParseTreeDescription.vb @@ -138,7 +138,7 @@ Public Class ParseTree Return Enumerations(enumString) End If - ReportError(referencingElement, "{0} is not a valid field type", enumString) + ReportError(referencingElement, "{0} is not a valid field type. You should add a node-kind entry in the syntax.xml.", enumString) Return Nothing End Function -- GitLab