diff --git a/doc/Class Lifecycle Generation.md b/doc/Class Lifecycle Generation.md index cce3508..311cebc 100644 --- a/doc/Class Lifecycle Generation.md +++ b/doc/Class Lifecycle Generation.md @@ -28,7 +28,7 @@ } ``` -1. Compile ([the generation process occurs at compile time](https://github.com/nventive/Uno.SourceGeneration/issues/9)). +1. Compile ([the generation process occurs at compile time](https://github.com/unoplatform/Uno.SourceGeneration/issues/9)). 1. It will generate the following public methods for you: ``` csharp partial class MyClass : IDisposable diff --git a/doc/Immutable Generation.md b/doc/Immutable Generation.md index 6b88135..1426822 100644 --- a/doc/Immutable Generation.md +++ b/doc/Immutable Generation.md @@ -255,7 +255,7 @@ The generated code will produce the following effect: ``` > For more information on the `Uno.Core` package: -> * On Github: +> * On Github: > * On Nuget: # FAQ diff --git a/readme.md b/readme.md index b3766b4..1147fb7 100644 --- a/readme.md +++ b/readme.md @@ -60,7 +60,7 @@ Features: # Have questions? Feature requests? Issues? -Make sure to visit our [FAQ](doc/faq.md), [StackOverflow](https://stackoverflow.com/questions/tagged/uno-platform), [create an issue](https://github.com/nventive/Uno.CodeGen/issues) or [visit our gitter](https://gitter.im/uno-platform/Lobby). +Make sure to visit our [FAQ](doc/faq.md), [StackOverflow](https://stackoverflow.com/questions/tagged/uno-platform), [create an issue](https://github.com/unoplatform/Uno.CodeGen/issues) or [visit our gitter](https://gitter.im/uno-platform/Lobby). # Contributing diff --git a/src/Uno.ClassLifecycle/Uno.ClassLifecycle.csproj b/src/Uno.ClassLifecycle/Uno.ClassLifecycle.csproj index 553ee7f..0cf72d0 100644 --- a/src/Uno.ClassLifecycle/Uno.ClassLifecycle.csproj +++ b/src/Uno.ClassLifecycle/Uno.ClassLifecycle.csproj @@ -15,17 +15,19 @@ This package is part of the Uno.CodeGen to generate object life cycle methods in Uno bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml Copyright (C) 2015-2018 nventive inc. - all rights reserved - https://github.com/nventive/Uno.CodeGen - https://github.com/nventive/Uno.CodeGen - https://nv-assets.azurewebsites.net/logos/uno.png + https://github.com/unoplatform/Uno.CodeGen + https://github.com/unoplatform/Uno.CodeGen + uno.png - + all runtime; build; native; contentfiles; analyzers + + diff --git a/src/Uno.CodeGen.ClassLifecycle/ClassLifecycleGenerator.cs b/src/Uno.CodeGen.ClassLifecycle/ClassLifecycleGenerator.cs index 55da4b5..a0d8f18 100644 --- a/src/Uno.CodeGen.ClassLifecycle/ClassLifecycleGenerator.cs +++ b/src/Uno.CodeGen.ClassLifecycle/ClassLifecycleGenerator.cs @@ -124,7 +124,7 @@ private void Generate(LifecycleMethods methods) { writer.WriteLine("// "); writer.WriteLine("// ***************************************************************************************************************************"); - writer.WriteLine("// This file has been generated by Uno.CodeGen (ClassLifecycleGenerator), available at https://github.com/nventive/Uno.CodeGen"); + writer.WriteLine("// This file has been generated by Uno.CodeGen (ClassLifecycleGenerator), available at https://github.com/unoplatform/Uno.CodeGen"); writer.WriteLine("// ***************************************************************************************************************************"); writer.WriteLine("// "); writer.WriteLine("#pragma warning disable"); diff --git a/src/Uno.CodeGen.ClassLifecycle/Uno.CodeGen.ClassLifecycle.csproj b/src/Uno.CodeGen.ClassLifecycle/Uno.CodeGen.ClassLifecycle.csproj index 138a786..9f8794f 100644 --- a/src/Uno.CodeGen.ClassLifecycle/Uno.CodeGen.ClassLifecycle.csproj +++ b/src/Uno.CodeGen.ClassLifecycle/Uno.CodeGen.ClassLifecycle.csproj @@ -1,57 +1,64 @@ - + net461;netstandard1.3;netstandard2.0 - true - Generator of class lifecycle - true - True - - full - True - nventive - nventive + true + Generator of class lifecycle + true + True + + full + True + nventive + nventive This package provides a generator which generates the class life cycle method using the attributes from Uno.ClassLifecycle. This package is part of the Uno.CodeGen to generate classes lifecycle methods in your project. - Uno - Copyright (C) 2015-2018 nventive inc. - all rights reserved - https://github.com/nventive/Uno.CodeGen - https://github.com/nventive/Uno.CodeGen - https://nv-assets.azurewebsites.net/logos/uno.png - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - NU1701 - - - - - - - - - - - true - build - - - - - - - + Uno + Copyright (C) 2015-2018 nventive inc. - all rights reserved + https://github.com/unoplatform/Uno.CodeGen + https://github.com/unoplatform/Uno.CodeGen + uno.png + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + NU1701 + + + + + + + + + + + true + build + + + + + + + + + diff --git a/src/Uno.CodeGen.RoslynHelpers/Extensions/DisposableAction.cs b/src/Uno.CodeGen.RoslynHelpers/Extensions/DisposableAction.cs new file mode 100644 index 0000000..c22df97 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Extensions/DisposableAction.cs @@ -0,0 +1,42 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Uno.RoslynHelpers +{ + internal partial class DisposableAction : IDisposable + { + public DisposableAction(Action action) + { + Action = action; + } + + public Action Action { get; private set; } + + #region IDisposable Members + + public void Dispose() + { + Action(); + } + + #endregion + } +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Extensions/IIndentedStringBuilder.cs b/src/Uno.CodeGen.RoslynHelpers/Extensions/IIndentedStringBuilder.cs new file mode 100644 index 0000000..ca113a4 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Extensions/IIndentedStringBuilder.cs @@ -0,0 +1,74 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; + +namespace Uno.RoslynHelpers +{ + internal partial interface IIndentedStringBuilder + { + /// + /// Gets the current indentation level + /// + int CurrentLevel { get; } + + /// + /// Appends text using the current indentation level + /// + /// + void Append(string text); + + /// + /// Appends formatted text using the current indentation level + /// + void AppendFormat(IFormatProvider formatProvider, string pattern, params object[] replacements); + + /// + /// Appends a line using the current indentation level + /// + void AppendLine(); + + /// + /// Writes the provided text and adds line using the current indentation level + /// + void AppendLine(string text); + + /// + /// Creates an indentation block + /// + /// The indentation level of the new block. + /// A disposable that will close the block + IDisposable Block(int count = 1); + + /// + /// Creates an indentation block, e.g. using a C# curly braces. + /// + /// A disposable that will close the block + IDisposable Block(IFormatProvider formatProvider, string pattern, params object[] parameters); + + /// + /// Adds an indentation + /// + /// + /// + IDisposable Indent(int count = 1); + + /// + /// Provides a string representing the complete builder. + /// + string ToString(); + } +} \ No newline at end of file diff --git a/src/Uno.CodeGen.RoslynHelpers/Extensions/IndentedStringBuilder.cs b/src/Uno.CodeGen.RoslynHelpers/Extensions/IndentedStringBuilder.cs new file mode 100644 index 0000000..5fff3c4 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Extensions/IndentedStringBuilder.cs @@ -0,0 +1,98 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; +using System.Collections.Generic; +using System.Text; + +namespace Uno.RoslynHelpers +{ + /// + /// A C# code indented builder. + /// + internal partial class IndentedStringBuilder : IIndentedStringBuilder + { + private readonly StringBuilder _stringBuilder; + + public int CurrentLevel { get; private set; } + + public IndentedStringBuilder() + : this(new StringBuilder()) + { + } + + public IndentedStringBuilder(StringBuilder stringBuilder) + { + _stringBuilder = stringBuilder; + } + + public virtual IDisposable Indent(int count = 1) + { + CurrentLevel += count; + return new DisposableAction(() => CurrentLevel -= count); + } + + public virtual IDisposable Block(int count = 1) + { + var current = CurrentLevel; + + CurrentLevel += count; + Append("{".Indent(current)); + AppendLine(); + + return new DisposableAction(() => + { + CurrentLevel -= count; + Append("}".Indent(current)); + AppendLine(); + }); + } + + public virtual IDisposable Block(IFormatProvider formatProvider, string pattern, params object[] parameters) + { + AppendFormat(formatProvider, pattern, parameters); + AppendLine(); + + return Block(); + } + + public virtual void Append(string text) + { + _stringBuilder.Append(text); + } + + public virtual void AppendFormat(IFormatProvider formatProvider, string pattern, params object[] replacements) + { + _stringBuilder.AppendFormat(formatProvider, pattern.Indent(CurrentLevel), replacements); + } + + public virtual void AppendLine() + { + _stringBuilder.AppendLine(); + } + + public virtual void AppendLine(string text) + { + _stringBuilder.Append(text.Indent(CurrentLevel)); + } + + public override string ToString() + { + return _stringBuilder.ToString(); + } + } + +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Extensions/IndentedStringBuilderExtensions.cs b/src/Uno.CodeGen.RoslynHelpers/Extensions/IndentedStringBuilderExtensions.cs new file mode 100644 index 0000000..68acbe2 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Extensions/IndentedStringBuilderExtensions.cs @@ -0,0 +1,58 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace Uno.RoslynHelpers +{ + internal static partial class IndentedStringBuilderExtensions + { + public static void AppendLine(this IIndentedStringBuilder builder, IFormatProvider formatProvider, string pattern, params object[] replacements) + { + builder.AppendFormat(formatProvider, pattern, replacements); + builder.AppendLine(); + } + + public static void AppendLine(this IIndentedStringBuilder builder, IFormatProvider formatProvider, int indentLevel, string pattern, params object[] replacements) + { + builder.AppendFormat(formatProvider, pattern.Indent(indentLevel), replacements); + builder.AppendLine(); + } + + public static void AppendLineInvariant(this IIndentedStringBuilder builder, string pattern, params object[] replacements) + { + builder.AppendLine(CultureInfo.InvariantCulture, pattern, replacements); + } + + public static void AppendLineInvariant(this IIndentedStringBuilder builder, int indentLevel, string pattern, params object[] replacements) + { + builder.AppendLine(CultureInfo.InvariantCulture, indentLevel, pattern, replacements); + } + + public static void AppendFormatInvariant(this IIndentedStringBuilder builder, string pattern, params object[] replacements) + { + builder.AppendFormat(CultureInfo.InvariantCulture, pattern, replacements); + } + + public static IDisposable BlockInvariant(this IIndentedStringBuilder builder, string pattern, params object[] parameters) + { + return builder.Block(CultureInfo.InvariantCulture, pattern, parameters); + } + } +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Extensions/StringExtensions.cs b/src/Uno.CodeGen.RoslynHelpers/Extensions/StringExtensions.cs new file mode 100644 index 0000000..7b2e312 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Extensions/StringExtensions.cs @@ -0,0 +1,35 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Uno.RoslynHelpers +{ + internal static partial class StringExtensions + { + private static readonly Regex _newLineRegex = new Regex(@"^", RegexOptions.Compiled | RegexOptions.Multiline); + + public static string Indent(this string text, int indentCount = 1) + { + return _newLineRegex.Replace(text, new String('\t', indentCount)); + } + } +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Helpers/CyclomaticComplexityWalker.cs b/src/Uno.CodeGen.RoslynHelpers/Helpers/CyclomaticComplexityWalker.cs new file mode 100644 index 0000000..73454ef --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Helpers/CyclomaticComplexityWalker.cs @@ -0,0 +1,134 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Uno.RoslynHelpers.Helpers +{ + public class CyclomaticComplexityWalker : CSharpSyntaxWalker + { + private Dictionary _codeRegionInfos; + private SyntaxNode _currentCodeRegionRoot; + private int _nestingLevel = 0; + private SemanticModel _model; + + public IEnumerable Results { get { return _codeRegionInfos.Values; } } + + public CyclomaticComplexityWalker(SyntaxNode entryNode, SemanticModel model) + { + _codeRegionInfos = new Dictionary(); + _codeRegionInfos[entryNode] = new RegionInfo(entryNode); + _model = model; + } + + + public override void Visit(SyntaxNode node) + { + var priorRegion = _currentCodeRegionRoot; + if (node is MethodDeclarationSyntax) + { + _currentCodeRegionRoot = node; + if (!_codeRegionInfos.ContainsKey(_currentCodeRegionRoot)) + { + _codeRegionInfos[_currentCodeRegionRoot] = new RegionInfo(_currentCodeRegionRoot); + } + } + + bool isSimplePredicate = node.IsExtendedSyntaxOfType(ExtendedSyntaxType.SimplePredicate); + bool isAnonymousFunction = node.IsExtendedSyntaxOfType(ExtendedSyntaxType.AnonymousFunctionExpression); + + if (isAnonymousFunction && _currentCodeRegionRoot != null) + { + if (!IsBuildDefinition(node)) + { + _nestingLevel++; + _codeRegionInfos[_currentCodeRegionRoot].PredicateCount += _nestingLevel; + } + else + { + _codeRegionInfos[_currentCodeRegionRoot].PredicateCount += 1; + } + } + + if (isSimplePredicate && _currentCodeRegionRoot != null) + { + _codeRegionInfos[_currentCodeRegionRoot].PredicateCount += 1; + } + + base.Visit(node); + + if (isAnonymousFunction && !IsBuildDefinition(node)) + { + _nestingLevel--; + } + + _currentCodeRegionRoot = priorRegion; + } + + private bool IsBuildDefinition(SyntaxNode node) + { + var simpleLambda = node as SimpleLambdaExpressionSyntax; + + if (simpleLambda != null) + { + var parameterSymbol = _model.GetDeclaredSymbol(simpleLambda.Parameter); + + var isBuilderType = parameterSymbol?.ToDisplayString()?.EndsWith("Builder"); + + if (isBuilderType ?? false) + { + return true; + } + } + + return false; + } + + private static int Factorial(int input) + { + if (input < 0) + { + throw new ArgumentOutOfRangeException("Factorial is only defined for non-negative integers."); + } + + int answer = 1; + + while (input > 1) + { + answer *= input--; + } + + return answer; + } + + public class RegionInfo + { + public SyntaxNode RegionRoot { get; } + public int PredicateCount { get; set; } + public int CyclomaticComplexity { get { return PredicateCount + 1; } } + + public RegionInfo(SyntaxNode regionRoot) + { + RegionRoot = regionRoot; + } + } + } +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Helpers/ExtendedSyntaxType.cs b/src/Uno.CodeGen.RoslynHelpers/Helpers/ExtendedSyntaxType.cs new file mode 100644 index 0000000..0d484d2 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Helpers/ExtendedSyntaxType.cs @@ -0,0 +1,362 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Uno.RoslynHelpers.Helpers +{ + /// + /// Used to provided delegate predicates, wrapped as ExtendedSyntaxType objects, to facilitate the + /// identification of more complex or composite syntax types that do not exist by default. + /// + public class ExtendedSyntaxType + { + public const bool DefaultConsiderParametersAsPotentialTargets = false; + + /// + /// Provides all the potential syntax kinds that define binary expressions. Does not + /// include the various member access expression ("a.b", "a?.b", "a->b") syntax kinds + /// + public static readonly SyntaxKind[] BinaryExpressionSyntaxKinds = + Enum.GetValues(typeof(SyntaxKind)) + .Cast() + .Select(SyntaxFacts.GetBinaryExpression) + .Where(kind => kind != SyntaxKind.None) + .ToArray(); + + private static readonly HashSet _simplePredicateTypes = new HashSet + { + typeof (IfStatementSyntax), + typeof (CaseSwitchLabelSyntax), + typeof (ConditionalExpressionSyntax), + typeof (WhileStatementSyntax), + typeof (ForStatementSyntax), + typeof (DoStatementSyntax) + }; + + private static readonly HashSet _binaryExpressionPredicateConnectors = new HashSet + { + SyntaxKind.LogicalOrExpression, + SyntaxKind.LogicalAndExpression, + SyntaxKind.CoalesceExpression + }; + + private static readonly HashSet _syntaxTypesWithOptionalBlock = new HashSet() + { + typeof(IfStatementSyntax), + typeof(ElseClauseSyntax), + typeof(ForStatementSyntax), + typeof(ForEachStatementSyntax), + typeof(WhileStatementSyntax), + typeof(DoStatementSyntax), + typeof(UsingStatementSyntax), + typeof(SimpleLambdaExpressionSyntax), + typeof(ParenthesizedLambdaExpressionSyntax), + typeof(FixedStatementSyntax), + typeof(LockStatementSyntax) + }; + + #region Delegate declarations + + public delegate bool CheckExtendedSyntaxKindDelegate(SyntaxNode node); + + #endregion + + #region Exposed delegate verification strategies + + public CheckExtendedSyntaxKindDelegate CheckIfExtendedType { get; } + + #endregion + + + #region Util methods + + /// + /// Returns the extended version of the given syntax node, so that it can be used in methods that require extended syntax types + /// + /// The syntax node type + /// The extended syntax version of the given node + public static ExtendedSyntaxType AsExtended() where T : SyntaxNode + { + return new ExtendedSyntaxType(node => node is T); + } + + #endregion + + + #region Exposed extended rules + + /// + /// Indicates if the given node is a loop (for, while, foreach, etc) + /// + public static readonly ExtendedSyntaxType LoopStatementSyntax = new ExtendedSyntaxType(IsLoopStatementSyntax); + + /// + /// Indicates if the given node is returned as part of a "return" statement + /// + public static readonly ExtendedSyntaxType Returned = new ExtendedSyntaxType(IsReturned); + + /// + /// Indicates if the given node is a conditional expression or a binary expression defined with a coalesce (??) operator + /// + public static readonly ExtendedSyntaxType ConditionalOrCoalesce = new ExtendedSyntaxType(IsConditionalOrCoalesce); + + /// + /// Indicates if the given node is a reference boundary, which is to say that it is either a statement, a lambda expression, + /// or the condition of a conditional expression + /// + public static readonly ExtendedSyntaxType ReferenceBoundary = new ExtendedSyntaxType(IsReferenceBoundary); + + /// + /// Indicates if the given node is the condition of a conditional expression + /// + public static readonly ExtendedSyntaxType ConditionInConditionalExpression = new ExtendedSyntaxType(IsNodeConditionInConditionalExpression); + + /// + /// Indicates if the given node is a lambda expression + /// + public static readonly ExtendedSyntaxType LambdaExpression = new ExtendedSyntaxType(IsLambdaExpression); + + /// + /// Indicates if the given node is an anonymous function (either a lambda or an anonymous method) + /// + public static readonly ExtendedSyntaxType AnonymousFunctionExpression = new ExtendedSyntaxType(IsAnonymousFunctionExpression); + + /// + /// Indicates if the given node is a predicate that results in different conditional execution + /// paths when encountered (if statement, loop, binary operators, etc) + /// + public static readonly ExtendedSyntaxType SimplePredicate = new ExtendedSyntaxType(IsSimplePredicate); + public static readonly ExtendedSyntaxType BodiedLambda = new ExtendedSyntaxType(IsBodiedLambda); + + public static readonly ExtendedSyntaxType TakesOptionalBlock = new ExtendedSyntaxType(CanTakeOptionalBlock); + + public static readonly ExtendedSyntaxType VariableAssignmentOrDeclarationTarget = new ExtendedSyntaxType(IsAssignmentOrDeclarationTarget); + public static readonly ExtendedSyntaxType VariableAssignmentOrDeclarationSource = new ExtendedSyntaxType(IsAnonymousFunctionExpression); + + public static readonly ExtendedSyntaxType VariableAssignmentTarget = new ExtendedSyntaxType(IsAssignmentTarget); + public static readonly ExtendedSyntaxType VariableAssignmentSource = new ExtendedSyntaxType(IsAssignmentSource); + public static readonly ExtendedSyntaxType VariableDeclarationTarget = new ExtendedSyntaxType(IsDeclarationTarget); + public static readonly ExtendedSyntaxType VariableDeclarationSource = new ExtendedSyntaxType(IsDeclarationSource); + + #endregion + + protected ExtendedSyntaxType(CheckExtendedSyntaxKindDelegate extendedCheckDelegate) + { + CheckIfExtendedType = extendedCheckDelegate; + } + + #region Extended syntax check predicates + + private static bool IsAnonymousFunctionExpression(SyntaxNode node) + { + return IsLambdaExpression(node) || node.IsSyntaxOfType(); + } + + private static bool IsLoopStatementSyntax(SyntaxNode node) + { + var loopAncestor = + node.FirstAncestorOrSelf() + ?? node.FirstAncestorOrSelf() + ?? node.FirstAncestorOrSelf() + ?? (StatementSyntax)node.FirstAncestorOrSelf(); + + return loopAncestor != null; + } + + private static bool IsConditionalOrCoalesce(SyntaxNode node) + { + return node.IsSyntaxOfType() || node.IsKind(SyntaxKind.CoalesceExpression); + } + + /// + /// Returns true if a node is a 'boundary' in the sense that its ancestors have no direct reference to its descendants. (eg a StatementSyntax, + /// a lambda expression, a ConditionalStatement that the node is on the far left-hand side of, etc.) + /// + /// The syntax node to check. + /// True if the node is a boundary, false otherwise. + private static bool IsReferenceBoundary(SyntaxNode node) + { + return node is StatementSyntax || + IsLambdaExpression(node) || + IsNodeConditionInConditionalExpression(node) || + node is ArgumentListSyntax; + } + + /// + /// Indicates if this node is directly or conditionally returned in the context of its declaration (whether in a lambda or a declared method) + /// + /// The node to check + /// Is the node is an active member of a return statement + private static bool IsReturned(SyntaxNode node) + { + // Checks if the given node is a lambda statement or is a non-lambda method return statement + Func isLambdaOrMethodReturnPredicate = targetNode => + { + return IsLambdaExpression(targetNode) || + (targetNode.Parent.IsSyntaxOfType() && + targetNode.Parent.GetFirstAncestorWhere(ancestor => IsLambdaExpression(targetNode)) == null); + }; + + + if (isLambdaOrMethodReturnPredicate(node.Parent)) + { + return true; + } + + // Get all the ancestors while we haven't encountered a return or a lambda statement, + // skipping over conditional, coalesce, cast, and parenthesized expressions. + var ancestorSyntaxTypesThatCanBeEncountered = new[] + { + ConditionalOrCoalesce, + AsExtended(), + AsExtended() + }; + + var ancestorsUntilReturnOrLambda = node + .Ancestors() + .TakeWhile(ancestor => !isLambdaOrMethodReturnPredicate(ancestor) && + node.IsOneOfThoseExtendedSyntaxTypes(ancestorSyntaxTypesThatCanBeEncountered)); + + var returnOrLambdaAncestorNode = ancestorsUntilReturnOrLambda.LastOrDefault()?.Parent; + + // Get what is returned + // ReturnStatementSyntax: return + // SimpleLambdaExpressionSyntax: () => + // ParenthesizedLambdaExpressionSyntax: () => { return } + SyntaxNode returnedTarget; + if (returnOrLambdaAncestorNode.IsSyntaxOfType()) + { + returnedTarget = returnOrLambdaAncestorNode + .GetAsSyntaxOfType() + .Expression; + } + else if (node.IsSyntaxOfType()) + { + returnedTarget = returnOrLambdaAncestorNode + .GetAsSyntaxOfType() + .Body + ?.GetAsSyntaxOfType() + ?.Expression; + } + else if (node.IsSyntaxOfType()) + { + returnedTarget = returnOrLambdaAncestorNode + .GetAsSyntaxOfType() + .Body + .GetAsSyntaxOfType() + .Statements + .FirstOrDefault(); + } + else + { + return false; + } + + // One final check to see if the returned target is not a condition of a conditional expression (in which case it is not returned) + return returnedTarget != null && returnedTarget.GetAsSyntaxOfType()?.Condition != node; + } + + private static bool IsLambdaExpression(SyntaxNode node) + { + return node.IsSyntaxOfType() || + node.IsSyntaxOfType(); + } + + private static bool IsNodeConditionInConditionalExpression(SyntaxNode node) + { + return node != null && (node.Parent as ConditionalExpressionSyntax)?.Condition == node; + } + private static bool IsAssignmentTarget(SyntaxNode node) + { + return node != null && node.Parent.GetAsSyntaxOfType()?.Left == node; + } + + private static bool IsDeclarationTarget(SyntaxNode node) + { + return node.IsSyntaxOfType() || node.IsSyntaxOfType(); + } + + private static bool IsAssignmentOrDeclarationTarget(SyntaxNode node) + { + return IsAssignmentTarget(node) || IsDeclarationTarget(node); + } + + private static bool IsAssignmentSource(SyntaxNode node) + { + return node != null && node.Parent.GetAsSyntaxOfType()?.Right == node; + } + + private static bool IsDeclarationSource(SyntaxNode node) + { + var grandParent = node?.Parent?.Parent; + if (grandParent == null) + { + return false; + } + + return (grandParent.GetAsSyntaxOfType()?.Initializer?.Value == node); + } + + private static bool IsAssignmentOrDeclarationSource(SyntaxNode node) + { + return IsAssignmentSource(node) || IsDeclarationSource(node); + } + + private static bool IsSimplePredicate(SyntaxNode node) + { + return _simplePredicateTypes.Contains(node.GetType()) || _binaryExpressionPredicateConnectors.Contains(node.Kind()); + } + + /// + /// Indicates if the syntax node is a type that may optionally be followed by a BlockSyntax (curly brackets): if, else, loops, usings, lambdas... + /// + /// + /// + private static bool CanTakeOptionalBlock(SyntaxNode node) + { + return _syntaxTypesWithOptionalBlock.Contains(node.GetType()); + } + + private static bool IsBodiedLambda(SyntaxNode node) + { + var nodeAsSimpleLambda = node as SimpleLambdaExpressionSyntax; + if (nodeAsSimpleLambda != null) + { + return nodeAsSimpleLambda.Body is BlockSyntax; + } + + var nodeAsParenthesizedLambda = node as ParenthesizedLambdaExpressionSyntax; + if (nodeAsParenthesizedLambda != null) + { + return nodeAsParenthesizedLambda.Body is BlockSyntax; + } + + return false; + } + + + #endregion + } +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Helpers/SyntaxSymbolPairing.cs b/src/Uno.CodeGen.RoslynHelpers/Helpers/SyntaxSymbolPairing.cs new file mode 100644 index 0000000..616950e --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Helpers/SyntaxSymbolPairing.cs @@ -0,0 +1,55 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace Uno.RoslynHelpers.Helpers +{ + /// + /// Used to pair a syntax node to a custom semantic representation of that node + /// + /// The type of the syntax node + /// The type of the symbol that is chosen as the semantic representation of the node + public class SyntaxSymbolPairing + where TSyntax : SyntaxNode + where TSymbol : ISymbol + { + private readonly Lazy _lazyTypeSymbolInitializer; + private readonly Lazy _lazySymbolInitializer; + + public ITypeSymbol TypeSymbol => _lazyTypeSymbolInitializer.Value; + public TSymbol Symbol => _lazySymbolInitializer.Value; + public TSyntax Node { get; } + + public SyntaxSymbolPairing(TSyntax node, Func syntaxTransform, Func typeSymbolTransform = null) + { + Node = node; + + _lazySymbolInitializer = new Lazy(() => syntaxTransform == null ? default(TSymbol) : syntaxTransform.Invoke(Node)); + _lazyTypeSymbolInitializer = new Lazy(() => typeSymbolTransform?.Invoke(Node)); + } + + public SyntaxSymbolPairing(TSyntax node, TSymbol symbol, ITypeSymbol typeSymbol = null) + : this(node, syntax => symbol, syntax => typeSymbol) + { + } + } +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/MethodSymbolExtensions.cs b/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/MethodSymbolExtensions.cs new file mode 100644 index 0000000..5a21950 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/MethodSymbolExtensions.cs @@ -0,0 +1,275 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis +{ + public static class MethodSymbolExtensions + { + private const bool DefaultMatchUsingInheritance = true; + + private static bool HasMatchingParametersWith(this IMethodSymbol current, IMethodSymbol other) + { + var currentArgTypes = current.Parameters.Select(param => param.Type); + var otherArgTypes = other.Parameters.Select(param => param.Type); + return currentArgTypes.SequenceEqual(otherArgTypes); + } + + /// + /// Check if two methods have the same signature, which means same name, return type and parameters + /// + /// The current method + /// Another method + /// If true, the name of the methods will be used for the equality comparaison + /// True if the methods have the same signature + public static bool IsSameSignatureAs(this IMethodSymbol current, IMethodSymbol other, bool considerNameForEquivalence = true) + { + current = current.GetReducedFromOrSelf(); + other = other.GetReducedFromOrSelf(); + + return (!considerNameForEquivalence || current.Name == other.Name) && + current.ReturnType.EqualsType(other.ReturnType) && + current.HasMatchingParametersWith(other); + } + + /// + /// Indicates if a candidate method is the async equivalent of another target method + /// + /// The potential method symbol that is the async equivalent of the target + /// The target method + /// The analysis context + /// + /// If true, the comparison will include a check to see if the candidate has a matching Task as its return type + /// For example, Task<int> Foo() is the equivalent of int Foo() + /// + /// If true, a positive match will require the candidate method to be declared with the async keyword + /// True if the candidate method is the async equivalent of the target method + public static bool IsAsyncEquivalentOf( + this IMethodSymbol asyncCandidate, + IMethodSymbol target, + SyntaxNodeAnalysisContext context, + bool compareUsingTaskEquivalence = false, + bool needsToHaveAsyncKeyword = false) + { + if (needsToHaveAsyncKeyword && !asyncCandidate.IsAsync) + { + return false; + } + + var hasMatchingParameters = asyncCandidate.HasMatchingParametersWith(target); + var matchingAsyncEquivalentName = $"{target.Name}Async"; + + asyncCandidate = asyncCandidate.GetReducedFromOrSelf(); + target = target.GetReducedFromOrSelf(); + + if (!asyncCandidate.Name.Equals(matchingAsyncEquivalentName, StringComparison.OrdinalIgnoreCase) || + !hasMatchingParameters) + { + return false; + } + + if (!compareUsingTaskEquivalence) + { + return true; + } + + var asyncCandidateReturnType = asyncCandidate.ReturnType as INamedTypeSymbol; + + return asyncCandidateReturnType != null && + asyncCandidateReturnType.IsGenericType && + asyncCandidate.ReturnType.OriginalDefinition.DerivesFromType(context) && + asyncCandidateReturnType.TypeArguments.FirstOrDefault()?.Equals(target.ReturnType) == true; + } + + /// + /// Indicates if the current method is of a given name and part of a given type + /// + /// The current method symbol + /// The name of the to check in the provided type + /// The name of the type that the current method must be contained in + /// The context + /// If true, the comparison will involve checking if the method's containing type derives from the given type. + /// If false, the comparison will compare the method's containing type and the given type directly for equality instead + /// True if a method with the same name as the current method exists in the given type + public static bool IsNamedMethodOnType( + this IMethodSymbol methodSymbol, + string methodName, + string typeName, + SyntaxNodeAnalysisContext context, + bool matchUsingInheritance = DefaultMatchUsingInheritance) + { + if (methodSymbol == null || methodSymbol.Name != methodName) + { + return false; + } + + return matchUsingInheritance + ? methodSymbol.ContainingType.DerivesFromType(typeName, context) + : methodSymbol.ContainingType.IsOfType(typeName, context); + } + + /// + /// Indicates if the current method is of a given name and contained in a given type + /// + /// The type that the current method must be contained in + /// The current method symbol + /// The name that the current moethod name must match + /// The context + /// If true, the comparison will involve checking if the method's containing type derives from the given type. + /// If false, the comparison will compare the method's containing type and the given type directly for equality instead + /// True if a method with the same name as the current method exists in the given type + public static bool IsNamedMethodOnType( + this IMethodSymbol methodSymbol, + string methodName, + SyntaxNodeAnalysisContext context, + bool matchUsingInheritance = DefaultMatchUsingInheritance) + { + if (methodSymbol == null || methodSymbol.Name != methodName) + { + return false; + } + + return matchUsingInheritance + ? methodSymbol.ContainingType.DerivesFromType(context) + : methodSymbol.ContainingType.IsOfType(context); + } + + /// + /// Indicates if the current method is of a given name and contained in any of the given types + /// + /// The current method symbol + /// The name that the current moethod name must match + /// The context + /// If true, the types will be compared by checking if the method's containing type derives from any of the given types. + /// If false, the types will be compared for equality instead + /// The name of the types in one of which the current method must be contained + /// True if a method with the same name as the current method exists in the given type + public static bool IsNamedMethodOnAnyOfTheseTypes( + this IMethodSymbol methodSymbol, + string methodName, + SyntaxNodeAnalysisContext context, + bool matchUsingInheritance = DefaultMatchUsingInheritance, + params string[] typeNames) + { + return typeNames.Any(typeName => methodSymbol.IsNamedMethodOnType(methodName, typeName, context, matchUsingInheritance)); + } + + /// + /// Checks if a similar method is declared and accessible in a given type + /// + /// The method to check + /// The type to check agaisnt + /// The syntax analysis context + /// If true, the name of the methods will be used for the equality comparaison + /// True if the method belongs to this + public static bool HasSimilarMethodDeclaredInType( + this IMethodSymbol currentMethod, + ITypeSymbol type, + SyntaxNodeAnalysisContext context, + bool considerNameForEquivalence = true) + { + var typeMethods = currentMethod.ContainingType.EqualsType(type) + ? type.GetAllAccessibleMethodsFromWithinType(context, false, false, currentMethod.Name) + : type.GetAllPubliclyAccessibleMethodsFromType(context, false, false, currentMethod.Name); + + return typeMethods.Any(m => m.IsSameSignatureAs(currentMethod, considerNameForEquivalence)); + } + + /// + /// Checks if a similar method is declared and accessible in a given type or any of its ancestor + /// + /// The method to check + /// The type to check agaisnt + /// The syntax analysis context + /// If true, the name of the methods will be used for the equality comparaison + /// True if the method belongs to this + public static bool HasSimilarMethodDeclaredInTypeOrAncestors( + this IMethodSymbol currentMethod, + ITypeSymbol type, + SyntaxNodeAnalysisContext context, + bool considerNameForEquivalence = true) + { + var typeAndAncestorsMethods = currentMethod.ContainingType.EqualsType(type) + ? type.GetAllAccessibleMethodsFromWithinType(context, true, false, currentMethod.Name) + : type.GetAllPubliclyAccessibleMethodsFromType(context, true, false, currentMethod.Name); + + return typeAndAncestorsMethods.Any(m => m.IsSameSignatureAs(currentMethod, considerNameForEquivalence)); + } + + /// + /// Returns the reduced version of this method or the method itself + /// + /// The method to check + /// The method the current method was reduced from or the current method itself + public static IMethodSymbol GetReducedFromOrSelf(this IMethodSymbol currentMethod) + { + return currentMethod.ReducedFrom ?? currentMethod; + } + + /// + /// Indicates if the current method is a constructor (normal, static, shared) or a destructor + /// + /// The current method + /// True if the current method is a constructor or a destructor + public static bool IsConstructorOrDestructor(this IMethodSymbol currentMethod) => currentMethod.IsConstructor() || currentMethod.IsDestructor(); + + /// + /// Indicates if the current method is a constructor (normal, static, shared) + /// + /// The current method + /// True if the current method is a constructor or a destructor + public static bool IsDestructor(this IMethodSymbol currentMethod) => + currentMethod.MethodKind == MethodKind.SharedConstructor || + currentMethod.MethodKind == MethodKind.StaticConstructor; + + /// + /// Indicates if the current method is a destructor (normal, static, shared) + /// + /// The current method + /// True if the current method is a constructor or a destructor + public static bool IsConstructor(this IMethodSymbol currentMethod) => + currentMethod.MethodKind == MethodKind.Constructor || + currentMethod.MethodKind == MethodKind.Destructor; + + /// + /// Indicates if the method is a delegate invocation + /// + /// TThe current method + /// True if the method is a delegate invocation + public static bool IsDelegateInvocation(this IMethodSymbol currentMethod) + { + return currentMethod.MethodKind == MethodKind.DelegateInvoke; + } + + /// + /// Indicates if the method if a lambda of an anonymous function declaration + /// + /// + /// True if the method is a lambda or delegate declaration + public static bool IsLambdaOrDelegateDeclaration(this IMethodSymbol currentMethod) + { + return currentMethod.MethodKind == MethodKind.LambdaMethod || currentMethod.MethodKind == MethodKind.AnonymousFunction; + } + } +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/SymbolExtensions.cs b/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/SymbolExtensions.cs new file mode 100644 index 0000000..bece9e9 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/SymbolExtensions.cs @@ -0,0 +1,398 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.CodeAnalysis +{ + /// + /// Roslyn symbol extensions + /// + internal static class SymbolExtensions + { + public static IEnumerable GetProperties(this INamedTypeSymbol symbol) => symbol.GetMembers().OfType(); + + public static IEnumerable GetAllEvents(this INamedTypeSymbol symbol) + { + do + { + foreach (var member in GetEvents(symbol)) + { + yield return member; + } + + symbol = symbol.BaseType; + + if (symbol == null) + { + break; + } + + } while (symbol.Name != "Object"); + } + + public static IEnumerable GetEvents(INamedTypeSymbol symbol) => symbol.GetMembers().OfType(); + + /// + /// Determines if the symbol inherits from the specified type. + /// + /// The current symbol + /// A potential base class. + public static bool Is(this INamedTypeSymbol symbol, string typeName) + { + do + { + if (symbol.ToDisplayString() == typeName) + { + return true; + } + + symbol = symbol.BaseType; + + if (symbol == null) + { + break; + } + + } while (symbol.Name != "Object"); + + return false; + } + + /// + /// Determines if the symbol inherits from the specified type. + /// + /// The current symbol + /// A potential base class. + public static bool Is(this INamedTypeSymbol symbol, INamedTypeSymbol other) + { + do + { + if (symbol == other) + { + return true; + } + + symbol = symbol.BaseType; + + if (symbol == null) + { + break; + } + + } while (symbol.Name != "Object"); + + return false; + } + + public static bool IsPublic(this ISymbol symbol) => symbol.DeclaredAccessibility == Accessibility.Public; + + /// + /// Returns true if the symbol can be accessed from the current module + /// + /// + /// + public static bool IsLocallyPublic(this ISymbol symbol, IModuleSymbol currentSymbol) => + symbol.DeclaredAccessibility == Accessibility.Public + || + ( + symbol.Locations.Any(l => l.MetadataModule == currentSymbol) + && symbol.DeclaredAccessibility == Accessibility.Internal + ); + + public static IEnumerable GetMethods(this INamedTypeSymbol resolvedType) + { + return resolvedType.GetMembers().OfType(); + } + + public static IEnumerable GetFields(this INamedTypeSymbol resolvedType) + { + return resolvedType.GetMembers().OfType(); + } + + public static IEnumerable GetFieldsWithAttribute(this ITypeSymbol resolvedType, string name) + { + return resolvedType + .GetMembers() + .OfType() + .Where(f => f.FindAttribute(name) != null); + } + + public static AttributeData FindAttribute(this ISymbol property, string attributeClassFullName) + { + return property.GetAttributes().FirstOrDefault(a => a.AttributeClass.ToDisplayString() == attributeClassFullName); + } + + public static AttributeData FindAttribute(this ISymbol property, INamedTypeSymbol attributeClassSymbol) + { + return property.GetAttributes().FirstOrDefault(a => a.AttributeClass == attributeClassSymbol); + } + + public static AttributeData FindAttributeFlattened(this ISymbol property, INamedTypeSymbol attributeClassSymbol) + { + return property.GetAllAttributes().FirstOrDefault(a => a.AttributeClass == attributeClassSymbol); + } + + /// + /// Returns the element type of the IEnumerable, if any. + /// + /// + /// + public static ITypeSymbol EnumerableOf(this ITypeSymbol resolvedType) + { + var intf = resolvedType + .GetAllInterfaces(includeCurrent: true) + .FirstOrDefault(i => i.ToDisplayString().StartsWith("System.Collections.Generic.IEnumerable", StringComparison.OrdinalIgnoreCase)); + + return intf?.TypeArguments.First(); + } + + public static IEnumerable GetAllInterfaces(this ITypeSymbol symbol, bool includeCurrent = true) + { + if (symbol != null) + { + if (includeCurrent && symbol.TypeKind == TypeKind.Interface) + { + yield return (INamedTypeSymbol)symbol; + } + + do + { + foreach (var intf in symbol.Interfaces) + { + yield return intf; + + foreach (var innerInterface in intf.GetAllInterfaces()) + { + yield return innerInterface; + } + } + + symbol = symbol.BaseType; + + if (symbol == null) + { + break; + } + + } while (symbol.Name != "Object"); + } + } + + public static bool IsNullable(this ITypeSymbol type) + { + return ((type as INamedTypeSymbol)?.IsGenericType ?? false) + && type.OriginalDefinition.ToDisplayString().Equals("System.Nullable", StringComparison.OrdinalIgnoreCase); + } + + public static bool IsNullable(this ITypeSymbol type, out ITypeSymbol nullableType) + { + if (type.IsNullable()) + { + nullableType = ((INamedTypeSymbol)type).TypeArguments.First(); + return true; + } + else + { + nullableType = null; + return false; + } + } + + public static ITypeSymbol NullableOf(this ITypeSymbol type) + { + return type.IsNullable() + ? ((INamedTypeSymbol)type).TypeArguments.First() + : null; + } + + public static IEnumerable GetNamespaceTypes(this INamespaceSymbol sym) + { + foreach (var child in sym.GetTypeMembers()) + { + yield return child; + } + + foreach (var ns in sym.GetNamespaceMembers()) + { + foreach (var child2 in GetNamespaceTypes(ns)) + { + yield return child2; + } + } + } + private static readonly Dictionary _fullNamesMaping = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {"string", typeof(string).ToString()}, + {"long", typeof(long).ToString()}, + {"int", typeof(int).ToString()}, + {"short", typeof(short).ToString()}, + {"ulong", typeof(ulong).ToString()}, + {"uint", typeof(uint).ToString()}, + {"ushort", typeof(ushort).ToString()}, + {"byte", typeof(byte).ToString()}, + {"double", typeof(double).ToString()}, + {"float", typeof(float).ToString()}, + {"decimal", typeof(decimal).ToString()}, + {"bool", typeof(bool).ToString()}, + }; + + public static string GetFullName(this INamespaceOrTypeSymbol type) + { + IArrayTypeSymbol arrayType = type as IArrayTypeSymbol; + if (arrayType != null) + { + return $"{arrayType.ElementType.GetFullName()}[]"; + } + + ITypeSymbol t; + if ((type as ITypeSymbol).IsNullable(out t)) + { + return $"System.Nullable`1[{t.GetFullName()}]"; + } + + var name = type.ToDisplayString(); + + string output; + + if (_fullNamesMaping.TryGetValue(name, out output)) + { + output = name; + } + + return output; + } + + public static string GetFullMetadataName(this INamespaceOrTypeSymbol symbol) + { + ISymbol s = symbol; + var sb = new StringBuilder(s.MetadataName); + + var last = s; + s = s.ContainingSymbol; + + if (s == null) + { + return symbol.GetFullName(); + } + + while (!IsRootNamespace(s)) + { + if (s is ITypeSymbol && last is ITypeSymbol) + { + sb.Insert(0, '+'); + } + else + { + sb.Insert(0, '.'); + } + sb.Insert(0, s.MetadataName); + + s = s.ContainingSymbol; + } + + var namedType = symbol as INamedTypeSymbol; + + if (namedType?.TypeArguments.Any() ?? false) + { + var genericArgs = string.Join(",", namedType.TypeArguments.Select(GetFullMetadataName)); + sb.Append($"[{ genericArgs }]"); + } + + return sb.ToString(); + } + + private static bool IsRootNamespace(ISymbol s) + { + return s is INamespaceSymbol && ((INamespaceSymbol)s).IsGlobalNamespace; + } + /// + /// Return attributes on the current type and all its ancestors + /// + /// + /// + public static IEnumerable GetAllAttributes(this ISymbol symbol) + { + while (symbol != null) + { + foreach (var attribute in symbol.GetAttributes()) + { + yield return attribute; + } + + symbol = (symbol as INamedTypeSymbol)?.BaseType; + } + } + + /// + /// Return properties of the current type and all of its ancestors + /// + /// + /// + public static IEnumerable GetAllProperties(this INamedTypeSymbol symbol) + { + while (symbol != null) + { + foreach (var property in symbol.GetMembers().OfType()) + { + yield return property; + } + + symbol = symbol.BaseType; + } + } + + /// + /// Converts declared accessibility on a symbol to a string usable in generated code. + /// + /// The symbol to get an accessibility string for. + /// Accessibility in format "public", "protected internal", etc. + public static string GetAccessibilityAsCSharpCodeString(this ISymbol symbol) + { + switch (symbol.DeclaredAccessibility) + { + case Accessibility.Private: + return "private"; + case Accessibility.ProtectedOrInternal: + return "protected internal"; + case Accessibility.Protected: + return "protected"; + case Accessibility.Internal: + return "internal"; + case Accessibility.Public: + return "public"; + } + + throw new ArgumentOutOfRangeException($"{symbol.DeclaredAccessibility} is not supported."); + } + + /// + /// Returns a boolean value indicating whether the symbol is decorated with all the given attributes + /// + /// The extended symbol + /// The given attributes + /// + public static bool HasAttributes(this ISymbol symbol, params INamedTypeSymbol[] attributes) + { + var currentSymbolAttributes = symbol.GetAttributes(); + return currentSymbolAttributes.Any() && currentSymbolAttributes.All(x => attributes.Contains(x.AttributeClass)); + } + } +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/SyntaxNodeExtensions.cs b/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/SyntaxNodeExtensions.cs new file mode 100644 index 0000000..6c45ec4 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/SyntaxNodeExtensions.cs @@ -0,0 +1,540 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.IO; +using Uno.RoslynHelpers.Helpers; + +namespace Microsoft.CodeAnalysis +{ + public static class SyntaxNodeExtensions + { + public const bool DefaultConsiderParametersAsPotentialTargets = false; + public const bool DefaultPrioritizeSkip = false; + + /// + /// Returns the semantic symbol of the provided syntax node + /// + /// The syntax node + /// The syntax analysis context + /// The symbol of the provided syntax node + public static ISymbol GetSymbol(this SyntaxNode syntaxNode, SyntaxNodeAnalysisContext context) + { + return context.SemanticModel.GetSymbolInfo(syntaxNode).Symbol; + } + + /// + /// Returns the semantic symbol of the provided syntax node + /// + /// The syntax node + /// The syntax semantic model + /// The symbol of the provided syntax node + public static ISymbol GetSymbol(this SyntaxNode syntaxNode, SemanticModel model) + { + return model.GetSymbolInfo(syntaxNode).Symbol; + } + + /// + /// Returns the semantic declared symbol of the provided syntax node as the provided symbol type + /// + /// The syntax node + /// The syntax semantic model + /// The symbol of the provided syntax node + public static ISymbol GetDeclaredSymbol(this SyntaxNode syntaxNode, SemanticModel model) + { + return model.GetDeclaredSymbol(syntaxNode); + } + + /// + /// Returns the semantic declared symbol of the provided syntax node as the provided symbol type + /// + /// The syntax node + /// The syntax analysis context + /// The symbol of the provided syntax node + public static ISymbol GetDeclaredSymbol(this SyntaxNode syntaxNode, SyntaxNodeAnalysisContext context) + { + return context.SemanticModel.GetDeclaredSymbol(syntaxNode); + } + + /// + /// Returns the semantic symbol of the provided syntax node, or the declared symbol if the previous cast was invalid + /// + /// The syntax node + /// The syntax analysis context + /// The symbol or declared symbol of the provided syntax node + public static ISymbol GetSymbolOrDeclared(this SyntaxNode syntaxNode, SyntaxNodeAnalysisContext context) + { + return syntaxNode.GetSymbol(context) ?? syntaxNode.GetDeclaredSymbol(context); + } + + /// + /// Returns the semantic symbol of the provided syntax node, or the declared symbol if the previous cast was invalid + /// + /// + /// + /// The symbol or declared symbol of the provided syntax node + public static ISymbol GetSymbolOrDeclared(this SyntaxNode syntaxNode, SemanticModel semanticModel) + { + return semanticModel.GetSymbolInfo(syntaxNode).Symbol ?? semanticModel.GetDeclaredSymbol(syntaxNode); + } + + /// + /// The the enclosing symbol for the current syntax node + /// + /// The syntax node + /// The current semantic model + /// + public static ISymbol GetEnclosingSymbol(this SyntaxNode syntaxNode, SemanticModel semanticModel) + { + var position = syntaxNode.SpanStart; + return semanticModel.GetEnclosingSymbol(position); + } + + /// + /// Returns the semantic symbol of the provided syntax node + /// + /// The syntax node + /// The syntax semantic model + /// The symbol of the provided syntax node + public static TSymbolType GetSymbolAs(this SyntaxNode syntaxNode, SemanticModel model) where TSymbolType : class, ISymbol + { + return model.GetSymbolInfo(syntaxNode).Symbol as TSymbolType; + } + + /// + /// Returns the semantic symbol of the provided syntax node + /// + /// The syntax node + /// The syntax analysis context + /// The symbol of the provided syntax node + public static TSymbolType GetSymbolAs(this SyntaxNode syntaxNode, SyntaxNodeAnalysisContext context) where TSymbolType : class, ISymbol + { + return context.SemanticModel.GetSymbolInfo(syntaxNode).Symbol as TSymbolType; + } + + /// + /// Returns the semantic declared symbol of the provided syntax node as the provided symbol type + /// + /// The syntax node + /// The syntax analysis context + /// The symbol of the provided syntax node + public static TSymbolType GetDeclaredSymbolAs(this SyntaxNode syntaxNode, SyntaxNodeAnalysisContext context) where TSymbolType : class, ISymbol + { + return context.SemanticModel.GetDeclaredSymbol(syntaxNode) as TSymbolType; + } + + /// + /// Returns the semantic declared symbol of the provided syntax node as the provided symbol type + /// + /// The syntax node + /// The syntax analysis context + /// The symbol of the provided syntax node + public static TSymbolType GetDeclaredSymbolAs(this SyntaxNode syntaxNode, SemanticModel model) where TSymbolType : class, ISymbol + { + return model.GetDeclaredSymbol(syntaxNode) as TSymbolType; + } + + /// + /// Returns the semantic symbol of the provided syntax node, or the declared symbol if the previous cast was invalid + /// + /// The syntax node + /// The syntax analysis context + /// The symbol or declared symbol of the provided syntax node + public static TSymbolType GetSymbolOrDeclaredAs(this SyntaxNode syntaxNode, SyntaxNodeAnalysisContext context) where TSymbolType : class, ISymbol + { + return syntaxNode.GetSymbolAs(context) ?? syntaxNode.GetDeclaredSymbolAs(context); + } + + /// + /// Returns the semantic symbol of the provided syntax node, or the declared symbol if the previous cast was invalid + /// + /// The syntax node + /// The syntax semantic model + /// The symbol or declared symbol of the provided syntax node + public static TSymbolType GetSymbolOrDeclaredAs(this SyntaxNode syntaxNode, SemanticModel model) where TSymbolType : class, ISymbol + { + return syntaxNode.GetSymbolAs(model) ?? syntaxNode.GetDeclaredSymbolAs(model); + } + + /// + /// Finds first ancestor node such that predicate is met, unless terminateAt is met first. Returns null if terminateAt is true or if no ancestor meets predicate. + /// + /// The child node to start from. + /// The condition to return an ancestor. + /// If this is true for any ancestor, the search will be terminated. + /// The ancestor node that meets the predicate condition, or null if terminateAt is satisfied first or if no ancestor is found. + public static SyntaxNode GetFirstAncestorWhere(this SyntaxNode node, Func predicate, + Func terminateAt = null) + { + terminateAt = terminateAt ?? (_ => false); + + return node.Ancestors() + .TakeWhile(n => !terminateAt(n)) + .Where(predicate) + .FirstOrDefault(); + } + + /// + /// Finds first ancestor node of type T, unless terminateAt is satisfied first. Returns null if terminateAt is true or if no ancestor is of type T. + /// + /// The SyntaxNode type of interest. + /// The child node to start from. + /// If this is true for any ancestor, the search will be terminated. + /// The first ancestor node of type T, or null if terminateAt is satisfied first or if no ancestor of type T is found. + public static T GetFirstAncestorOfType(this SyntaxNode node, Func terminateAt = null) + where T : SyntaxNode + { + return GetFirstAncestorWhere(node, n => n is T, terminateAt) as T; + } + + /// + /// Get ancestor of a particular type which could 'refer to' node. This will return null if it encounters a 'boundary' (eg a StatementSyntax not of the + /// type of interest, a lambda expression, a ConditionalStatement that the node is on the left-hand side of, etc.), or if no ancestor of type T is found. + /// + /// The SyntaxNode type of interest. + /// The child node to start from. + /// The first ancestor node of type T, or null if a boundary node is encountered first or if no ancestor of type T is found. + public static T GetReferringAncestorOfType(this SyntaxNode node) + where T : SyntaxNode + { + return node.GetFirstAncestorOfType(terminateAt: n => !(n is T) && n.IsExtendedSyntaxOfType(ExtendedSyntaxType.ReferenceBoundary)); + } + + /// + /// Returns true if a node is the condition in a conditional expression. + /// + /// + /// + private static bool IsNodeConditionInConditionalExpression(this SyntaxNode node) + { + return node != null && (node.Parent as ConditionalExpressionSyntax)?.Condition == node; + } + + /// + /// Returns the the type symbol that this syntax node provides, if it has one (i.e InvocationSyntax -> invocation return type) + /// + /// The syntax node + /// The syntax analysis context + /// If true, the converted type (implicit conversion) is returned + /// The type symbol that the syntax node represents + public static ITypeSymbol GetAsTypeSymbol(this SyntaxNode syntaxNode, SyntaxNodeAnalysisContext context, bool useConvertedType = false) + { + var symbolTypeInfo = context.SemanticModel.GetTypeInfo(syntaxNode); + var type = useConvertedType ? symbolTypeInfo.ConvertedType : symbolTypeInfo.Type; + + return type.IsOfType(context) ? null : type; + } + + + /// + /// Indicates if the current syntax node is of the trageted syntax type + /// + /// + /// + /// + public static bool IsSyntaxOfType(this SyntaxNode current) where TSymbolType : SyntaxNode + { + return current is TSymbolType; + } + + /// + /// Returns the syntax node safe-casted into a certain syntax type + /// + /// The syntax type the node should be cast to + /// The syntax node to convert + /// + public static TSymbolType GetAsSyntaxOfType(this SyntaxNode current) where TSymbolType : SyntaxNode + { + return current as TSymbolType; + } + + public static SyntaxSymbolPairing LinkNodeToSymbol( + this TSyntax syntaxNode, + Func syntaxToSymbol, + SyntaxNodeAnalysisContext? context = null) + where TSyntax : SyntaxNode where TSymbol : ISymbol + { + return syntaxNode.LinkNodeToSymbol(node => node, syntaxToSymbol, context); + } + + public static SyntaxSymbolPairing LinkNodeToSymbol( + this TSourceSyntax syntaxNode, + Func syntaxTransform, + Func syntaxToSymbol, + SyntaxNodeAnalysisContext? context = null) + where TSourceSyntax : SyntaxNode + where TResultSyntax : SyntaxNode + where TSymbol : ISymbol + { + return new SyntaxSymbolPairing( + syntaxNode, + node => syntaxToSymbol(syntaxTransform(node)), + node => context.HasValue ? node.GetAsTypeSymbol(context.Value) : null + ); + } + + /// + /// Links each syntax node to a specified semantic representation of that node. + /// + /// The nodes to map to symbols + /// Describes the transition from syntax to symbol applied to a node + /// to obtain the desired semantic representation of that node + /// [Optionnal] The analysis context. Must be provided if the + /// needs to be computed + /// A self-contained pairing between the given syntax node and its equivalent semantic representation + public static IEnumerable> LinkNodesToSymbols( + this IEnumerable syntaxNodes, + Func syntaxToSymbol, + SyntaxNodeAnalysisContext? context = null) + where TSyntax : SyntaxNode where TSymbol : ISymbol + { + return syntaxNodes.Select(node => node.LinkNodeToSymbol(syntaxToSymbol, context)); + } + + /// + /// Indicates if this node is directly or conditionally returned in the context of its declaration + /// + /// The node to check + /// Is the node is an active member of a return statement + public static bool IsReturned(this SyntaxNode node) + { + if (node.Parent.IsSyntaxOfType()) + { + return true; + } + + var ancestorsUntilReturn = node.Ancestors() + .TakeWhile(ancestor => !ancestor.IsSyntaxOfType() && IsConditionalOrCoalesce(node)); + + var returnStatement = ancestorsUntilReturn.LastOrDefault()?.Parent?.GetAsSyntaxOfType(); + return returnStatement != null && returnStatement.Expression.GetAsSyntaxOfType()?.Condition != node; + } + + private static bool IsConditionalOrCoalesce(SyntaxNode node) + { + return node.IsSyntaxOfType() || node.IsKind(SyntaxKind.CoalesceExpression); + } + + /// + /// Returns the nearest ancestor of type IfStatementSyntax where the syntax node is declared + /// + /// The provided syntax node + /// The nearest ancestor of type IfStatementSyntax where the syntax node is declared + public static IfStatementSyntax GetDeclaringIfStatement(this SyntaxNode syntaxNode) + { + return syntaxNode.FirstAncestorOrSelf(); + } + + /// + /// Return the nearest ancestor of any of the four loop type (for, while, do-while, foreach) where the syntax node is declared + /// + /// The provided syntax node + /// The nearest ancestor of any of the four loop type (for, while, do-while, foreach) where the syntax node is declared + public static StatementSyntax GetDeclaringLoopStatement(this SyntaxNode syntaxNode) + { + return syntaxNode.FirstAncestorOrSelf() ?? syntaxNode.FirstAncestorOrSelf() ?? + (StatementSyntax)syntaxNode.FirstAncestorOrSelf() ?? syntaxNode.FirstAncestorOrSelf(); + } + + /// + /// Checks if the given node is a certain type of syntax. This type is actually one of the pre-defined check declared in ExtendedSyntaxType + /// + /// The current node + /// The target extended syntax type to check the current node against + /// True if the node matches the provided extended type + public static bool IsExtendedSyntaxOfType(this SyntaxNode node, ExtendedSyntaxType extendedSyntaxType) + { + return extendedSyntaxType.CheckIfExtendedType(node); + } + + /// + /// Get the first ancestor which is a member declaration syntax + /// + /// The current node + /// If true, the current node will be considered when checking for MemberDeclarationSyntax ancestors + /// The surrounding member declaration syntax + public static MemberDeclarationSyntax GetSurroundingMemberDeclarationSyntax(this SyntaxNode node, bool includeSelf = false) + { + + return GetSurroundingMemberDeclarationSyntax(node, includeSelf, false); + } + + /// + /// Get the first ancestor which is a member declaration syntax of the provided kind (class, method, namespace, etc) + /// + /// The current node + /// If true, the current node will be considered when checking for MemberDeclarationSyntax ancestors + /// If true, will grab the last surrounding declaration syntax of the given type. Useful to ignore of nested classes or namespaces + /// The surrounding member declaration syntax + public static MemberDeclarationSyntax GetSurroundingMemberDeclarationSyntax( + this SyntaxNode node, + bool includeSelf = false, + bool takeLast = false) + where TMemberDeclarationType : MemberDeclarationSyntax + { + var ancestors = includeSelf ? node.AncestorsAndSelf() : node.Ancestors(); + var target = takeLast + ? ancestors.LastOrDefault(ancestor => ancestor.IsSyntaxOfType()) + : ancestors.FirstOrDefault(ancestor => ancestor.IsSyntaxOfType()); + + return target as TMemberDeclarationType; + } + + /// + /// Checks if the current syntax node matches any of the given extended syntax type definitions + /// + /// The current node + /// The extended syntax types to match the current node agaisnt + /// True if the current node is of any of of the given extended types + public static bool IsOneOfThoseExtendedSyntaxTypes(this SyntaxNode node, params ExtendedSyntaxType[] extendedSyntaxTypes) + { + return extendedSyntaxTypes.Any(t => t.CheckIfExtendedType(node)); + } + + /// + /// Gets info about cyclomatic complexity, counting the number of predicates within the scope of rootNode and within each nested function + /// (methods and anonymous functions). + /// + /// The SyntaxNode to measure cyclomatic complexity within + /// A sequence of RegionInfo objects, each containing the region-defining node (either rootNode or a function-defining node) + /// and cyclomatic complexity values for the region. There will always be at least one RegionInfo returned for the rootNode, + /// plus one for each nested function. + public static IEnumerable GetCyclomaticComplexityInfo(this SyntaxNode rootNode, SemanticModel model) + { + var walker = new CyclomaticComplexityWalker(rootNode, model); + walker.Visit(rootNode); + return walker.Results; + } + + /// + /// Descends the hierarchy of 's descendants depth-first, looking for nodes that match . + /// If an encountered node matches , it is returned, and its own descendants are ignored. + /// If an encountered node matches , it and its descendants are ignored. + /// If is true, the skip predicate takes precedence over the keep predicate. + /// + /// The root syntax node to start the analysis at + /// The predicate used to indicate if a node should be retained or not. + /// The predicate used to indicate if a node (and its descendant) should be skipped or not. + /// If true, even a node that matches the keep predicate will not be retained if it also matches the skip predicate + /// All the first occurences of the targeted nodes for each branch of the syntax tree under the given root + public static IEnumerable GetAllFirstMatchingSubTreeRoots( + this SyntaxNode root, + Func keepPredicate, + Func skipPredicate = null, + bool prioritizeSkip = DefaultPrioritizeSkip) + { + // Iterative depth first search of the tree + var nodes = new Stack(root.ChildNodes()); + while (nodes.Any()) + { + var current = nodes.Pop(); + var skipCurrent = skipPredicate?.Invoke(current) ?? false; + var keepCurrent = keepPredicate(current); + + if (prioritizeSkip && skipCurrent) + { + continue; + } + + if (keepCurrent) + { + yield return current; + } + else if (!skipCurrent) + { + foreach (var child in current.ChildNodes()) + { + nodes.Push(child); + } + } + } + } + + /// + /// Descends the hierarchy of 's descendants depth-first, looking for nodes that match . + /// If an encountered node matches , it is returned, and its own descendants are ignored. + /// If an encountered node matches , it and its descendants are ignored. + /// If is true, the skip predicate takes precedence over the keep predicate. + /// If no value is given for the , the predicate is assumed to be always 'true", and so any subtree root matching the + /// given type will be returned (while its descendants are ignored) + /// + /// The root syntax node to start the analysis at + /// The predicate used to indicate if a node should be retained or not. + /// The predicate used to indicate if a node (and its descendant) should be skipped or not. + /// If true, even a node that matches the keep predicate will not be retained if it also matches the skip predicate + /// All the first occurences of the targeted nodes for each branch of the syntax tree under the given root + public static IEnumerable GetAllFirstMatchingSubTreeRootsOfType( + this SyntaxNode root, + Func keepPredicate = null, + Func skipPredicate = null, + bool prioritizeSkip = DefaultPrioritizeSkip) + where TNodeType : SyntaxNode + { + // We combine the 'keep' predicate with the type-matching predicate here + Func combinedPredicate = node => node is TNodeType && (keepPredicate?.Invoke(node) ?? true); + return root.GetAllFirstMatchingSubTreeRoots(node => combinedPredicate(node), skipPredicate, prioritizeSkip).Cast(); + } + /// + /// Returns true if the node appears to be in generated code. + /// + /// + /// + public static bool IsInGeneratedCode(this SyntaxNode node) + { + var fileName = Path.GetFileName(node.SyntaxTree.FilePath); + return IsFileNameForGeneratedCode(fileName); + } + + /// + /// Returns true if file name indicates that the code is generated. + /// https://github.com/dotnet/roslyn/blob/master/src/Workspaces/Core/Portable/GeneratedCodeRecognition/GeneratedCodeRecognitionServiceFactory.cs + /// + /// + /// + private static bool IsFileNameForGeneratedCode(string fileName) + { + if (fileName.StartsWith("TemporaryGeneratedFile_", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + string extension = Path.GetExtension(fileName); + if (extension != string.Empty) + { + fileName = Path.GetFileNameWithoutExtension(fileName); + + if (fileName.EndsWith("AssemblyInfo", StringComparison.OrdinalIgnoreCase) || + fileName.EndsWith(".designer", StringComparison.OrdinalIgnoreCase) || + fileName.EndsWith(".generated", StringComparison.OrdinalIgnoreCase) || + fileName.EndsWith(".g", StringComparison.OrdinalIgnoreCase) || + fileName.EndsWith(".g.i", StringComparison.OrdinalIgnoreCase) || + fileName.EndsWith(".AssemblyAttributes", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/TypeParameterSymbolExtensions.cs b/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/TypeParameterSymbolExtensions.cs new file mode 100644 index 0000000..4b01763 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/TypeParameterSymbolExtensions.cs @@ -0,0 +1,104 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis +{ + public static class TypeParameterSymbolExtensions + { + /// Uses reflection to obtain the EffectiveBaseClassNoUseSiteDiagnostics property and its value. + /// http://source.roslyn.codeplex.com/#Microsoft.CodeAnalysis.CSharp/Symbols/TypeParameterSymbol.cs#275 + private static MethodInfo GetReflectedEffectiveBaseClassMethodInfo(ITypeParameterSymbol typeSymbol) + { + return typeSymbol.GetType().GetRuntimeProperties().FirstOrDefault(methodInfo => methodInfo.Name == "EffectiveBaseClassNoUseSiteDiagnostics").GetMethod; ; + } + + /// Uses reflection to obtain the EffectiveInterfacesNoUseSiteDiagnostics property and its value. + /// http://source.roslyn.codeplex.com/#Microsoft.CodeAnalysis.CSharp/Symbols/TypeParameterSymbol.cs#300 + private static MethodInfo GetReflectedEffectiveInterfaceMethodInfo(ITypeParameterSymbol typeSymbol) + { + return typeSymbol.GetType().GetRuntimeProperties().FirstOrDefault(methodInfo => methodInfo.Name == "EffectiveInterfacesNoUseSiteDiagnostics").GetMethod; + } + + /// + /// Returns true if two ITypeParameterSymbols (symbols corresponding to a type parameter, eg 'T' in SomeMethod() { T t = ... }) appear to be equivalent, + /// meaning that they appear to have matching constraints. + /// + /// Note: this method is currently not watertight, due to difficult cases like self-referential constraints (eg 'where T : IComparable') + /// + /// Symbol to compare + /// Symbol to compare + /// True if the type parameters are equivalent + public static bool HasEquivalentConstraintsTo(this ITypeParameterSymbol current, ITypeParameterSymbol other) + { + if (current == null || other == null) + { + return false; + } + if (current.HasReferenceTypeConstraint != other.HasReferenceTypeConstraint + || current.HasValueTypeConstraint != other.HasValueTypeConstraint + || current.HasConstructorConstraint != other.HasConstructorConstraint) + { + return false; + } + return current.ConstraintTypes.AreTypeSetsEquivalent(other.ConstraintTypes); + } + + /// + /// Provides all the resolved/effetive interface constraints of the given type parameter symbol. + /// + /// + /// public Method where T : U where U : IEnumerable, V where V : IComparable + /// // U will have the following effective interface constraints: IEnumerable, Icomparable + /// + /// + /// + /// The type parameter symbol to resolve obtain the effective interface constraints from + /// All the resolved/effective interfaces that constrain the given type parameter symbol + public static IEnumerable GetAllEffectiveInterfaceConstraints(this ITypeParameterSymbol current) + { + return ((IEnumerable)GetReflectedEffectiveInterfaceMethodInfo(current).Invoke(current, null)).Cast(); + } + + /// + /// Attempts to resolve the constraints on the given type parameter symbol and obtain a concrete named type. + /// If no such constraint can be resolved into a named type, null is returned + /// + /// The type parameter symbol to resolve as a constrained type + /// The effective/constrained named type that has been resolved from the given type parameter symbol's constraint + public static INamedTypeSymbol TryGetAsConstrainedType(this ITypeParameterSymbol current) + { + var constrainedBaseType = (INamedTypeSymbol)GetReflectedEffectiveBaseClassMethodInfo(current).Invoke(current, null); + + // If the attempt to get the current type parameter symbol as a restrained type gives us a type symbol representing 'object' + // or 'ValueType', then this type parameter is unconstrained + return constrainedBaseType.SpecialType == SpecialType.System_Object || constrainedBaseType.SpecialType == SpecialType.System_ValueType + ? null + : constrainedBaseType; + } + } +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/TypeSymbolExtensions.cs b/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/TypeSymbolExtensions.cs new file mode 100644 index 0000000..dc62a50 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Microsoft/CodeAnalysis/TypeSymbolExtensions.cs @@ -0,0 +1,567 @@ +// ****************************************************************** +// Copyright � 2015-2018 nventive inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ****************************************************************** +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis +{ + public static class TypeSymbolExtensions + { + + private const bool DoDefaultCompareUsingSubstitutedType = true; + + /// + /// Returns true is the underlying type is an interface + /// + /// The type symbol + /// True if the type is an interface + public static bool IsInterface(this ITypeSymbol symbol) + { + return symbol.TypeKind == TypeKind.Interface; + } + + /// + /// Returns all the interfaces this type inherits from, including the type itself if it is an interface + /// + /// The type symbol + /// All the interfaces this type inherits from, including itself + public static IList GetAllInterfacesIncludingThis(this ITypeSymbol type) + { + var allInterfaces = type.AllInterfaces; + var namedType = type as INamedTypeSymbol; + if (namedType != null && namedType.TypeKind == TypeKind.Interface && !allInterfaces.Contains(namedType)) + { + var result = new List(allInterfaces.Length + 1); + result.Add(namedType); + result.AddRange(allInterfaces); + return result; + } + + return allInterfaces; + } + + private static INamedTypeSymbol GetTypeSymbolFromMetadataName(string otherTypeFullName, SyntaxNodeAnalysisContext context) + { + return context.SemanticModel.Compilation.GetTypeByMetadataName(otherTypeFullName); + } + + /// + /// Compares two type symbols for equality + /// + /// The current type symbol + /// Another type sybol + /// If false, the comparison will use the original definitions of both symbols (in case one or both are generic types) + /// If the symbols are equal + public static bool EqualsType(this ITypeSymbol current, ITypeSymbol other, bool compareUsingSubstitutedType = DoDefaultCompareUsingSubstitutedType) + { + var currentTypeSymbol = compareUsingSubstitutedType ? current : current?.OriginalDefinition; + var otherTypeSymbol = compareUsingSubstitutedType ? other : other?.OriginalDefinition; + + return currentTypeSymbol != null && currentTypeSymbol.OriginalDefinition.Equals(otherTypeSymbol); + } + + /// + /// If both current and other are ITypeParameterSymbols, checks if their constraints are equivalent. Otherwise, returns the result of EqualsType. + /// + /// + /// + /// + public static bool IsEquivalentToType(this ITypeSymbol current, ITypeSymbol other) + { + if (current is ITypeParameterSymbol && other is ITypeParameterSymbol) + { + return (current as ITypeParameterSymbol).HasEquivalentConstraintsTo(other as ITypeParameterSymbol); + } + + if (current is INamedTypeSymbol && other is INamedTypeSymbol) + { + return (current as INamedTypeSymbol).IsEquivalentToType(other as INamedTypeSymbol); + } + + return current.EqualsType(other, compareUsingSubstitutedType: true); + } + + /// + /// Checks if the underlying type of the current type symbol derives from the type with the provided name + /// + /// The current type symbol + /// The full name for the System.Type instance the current type symbol will be checked against for inheritance/implementation + /// The analysis context + /// If the underlying type of the current type symbol implements or inherits the target type + public static bool DerivesFromType(this ITypeSymbol symbol, string otherTypeFullName, SyntaxNodeAnalysisContext context) + { + var otherType = GetTypeSymbolFromMetadataName(otherTypeFullName, context); + return DerivesFromType(symbol, otherType); + } + + /// + /// Checks if the underlying type of the current type symbol derives from the type with the provided name + /// + /// The current type symbol + /// The full name for the System.Type instance the current type symbol will be checked against for inheritance/implementation + /// The analysis context + /// If the underlying type of the current type symbol implements or inherits the target type + public static bool DerivesFromType(this ITypeSymbol symbol, ITypeSymbol otherType) + { + var baseTypes = GetBaseTypesAndThis(symbol); + var implementedInterfaces = GetAllInterfacesIncludingThis(symbol); + + return baseTypes.Any(baseType => baseType.Equals(otherType) || + implementedInterfaces.Any(baseInterfaceType => baseInterfaceType.Equals(otherType))); + } + + /// + /// Checks if the underlying type of the current type symbol derives from the provided type + /// + /// The current type symbol + /// The System.Type instance that the type symbol will be checked against for inheritance/implementation + /// The analysis context + /// If the underlying type of the current type symbol implements or inherits the target type + public static bool DerivesFromType(this ITypeSymbol symbol, Type otherType, SyntaxNodeAnalysisContext context) + { + return DerivesFromType(symbol, otherType?.FullName, context); + } + + /// + /// Checks if the underlying type of the current type symbol derives from the provided type + /// + /// The current type symbol + /// The analysis context + /// The type names from which the symbol could inherit + /// If the underlying type of the current type symbol implements or inherits the target type + public static bool DerivesFromAnyOfTheseTypes(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context, params string[] typeNames) + { + return typeNames.Any(typeName => DerivesFromType(symbol, typeName, context)); + } + + /// + /// Checks if the underlying type of the current type symbol derives from the generic argument type + /// + /// The current type symbol + /// The analysis context + /// If the underlying type of the current type symbol implements or inherits the target type + public static bool DerivesFromType(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context) + { + return DerivesFromType(symbol, typeof(TSymbolType), context); + } + + /// + /// Checks if the underlying type of the current type symbol is the same as the type with the provided name + /// + /// The current type symbol + /// The full name for the System.Type instance the current type symbol will be checked against for equality + /// The analysis context + /// Determines if the comparison wil use the original definitions of both symbols (in case one or both are generic types) + /// If the underlying type of the current type symbol is equal to the target type + public static bool IsOfType(this ITypeSymbol symbol, string otherTypeFullName, SyntaxNodeAnalysisContext context, bool compareUsingOriginalType = DoDefaultCompareUsingSubstitutedType) + { + var otherType = GetTypeSymbolFromMetadataName(otherTypeFullName, context); + return EqualsType(symbol, otherType, compareUsingOriginalType); + } + + /// + /// Checks if the underlying type of the current type symbol is the same as the type with the provided type + /// + /// The current type symbol + /// The System.Type instance that the provided type symbol will be checked against for equality + /// The analysis context + /// Determines if the comparison wil use the original definitions of both symbols (in case one or both are generic types) + /// If the underlying type of the current type symbol is equal to the target type + public static bool IsOfType(this ITypeSymbol symbol, Type otherType, SyntaxNodeAnalysisContext context, bool compareUsingOriginalType = DoDefaultCompareUsingSubstitutedType) + { + return IsOfType(symbol, otherType?.FullName, context, compareUsingOriginalType); + } + + /// + /// Checks if the underlying type of the current type symbol is the same as the type with the provided generic type + /// + /// The current type symbol + /// The analysis context + /// Determines if the comparison wil use the original definitions of both symbols (in case one or both are generic types) + /// If the underlying type of the current type symbol is equal to the target type + public static bool IsOfType(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context, bool compareUsingOriginalType = DoDefaultCompareUsingSubstitutedType) + { + return IsOfType(symbol, typeof(TSymbolType), context, compareUsingOriginalType); + } + + /// + /// Checks if one of the provided types matches the underlying type of the current type symbol + /// + /// The current type symbol + /// The analysis context + /// Determines if the comparison wil use the original definitions of both symbols (in case one or both are generic types) + /// The full names for the System.Type instances that the provided type symbol will be checked against for equality + /// If the underlying type of the current type symbol is equal to one of the target types + public static bool IsOneOfTheseTypes(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context, bool compareUsingOriginalType, params string[] otherTypeNames) + { + var otherTypes = otherTypeNames.Select(typeName => GetTypeSymbolFromMetadataName(typeName, context)); + return otherTypes.Any(type => EqualsType(symbol, type, compareUsingOriginalType)); + } + + /// + /// Checks if one of the provided types matches the underlying type of the current type symbol. + /// Uses the default comparison strategy, defined in + /// + /// The current type symbol + /// The full names for the System.Type instances that the provided type symbol will be checked against for equality + /// The analysis context + /// If the underlying type of the current type symbol is equal to one of the target types + public static bool IsOneOfTheseTypes(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context, params string[] otherTypeNames) + { + return IsOneOfTheseTypes(symbol, context, true, otherTypeNames); + } + + /// + /// Checks if one of the provided types matches the underlying type of the current type symbol + /// + /// The current type symbol + /// Determines if the comparison wil use the original definitions of both symbols (in case one or both are generic types) + /// The System.Type instances the current type symbol will be check against for equality + /// The analysis context + /// If the underlying type of the current type symbol is equal to one of the target types + public static bool IsOneOfTheseTypes(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context, bool compareUsingOriginalType, params Type[] otherTypes) + { + return IsOneOfTheseTypes(symbol, context, compareUsingOriginalType, otherTypes.Select(t => t.FullName).ToArray()); + } + + /// + /// Checks if one of the provided types matches the underlying type of the current type symbol. + /// Uses the default comparison strategy, defined in + /// + /// The current type symbol + /// The System.Type instances the current type symbol will be check against for equality + /// The analysis context + /// If the underlying type of the current type symbol is equal to one of the target types + public static bool IsOneOfTheseTypes(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context, params Type[] otherTypes) + { + return IsOneOfTheseTypes(symbol, context, true, otherTypes); + } + + public static bool IsAnonymousType(this INamedTypeSymbol symbol) + { + return symbol?.IsAnonymousType == true; + } + + /// + /// True if type symbol represents void + /// + /// + /// + public static bool IsVoid(this ITypeSymbol symbol) + { + return symbol.SpecialType == SpecialType.System_Void; + } + + /// + /// True if type symbol is a primitive, DateTime, or DateTimeOffset + /// + /// + /// + /// + public static bool IsSimpleType(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context) + { + return symbol.IsOfType(context) + || symbol.IsOfType(context) + || symbol.IsOfType(context) + || symbol.IsOfType(context) + || symbol.IsOfType(context) + || symbol.IsOfType(context) + || symbol.IsOfType(context) + || symbol.IsOfType(context) + || symbol.IsOfType(context) + || symbol.IsOfType(context) + || symbol.IsOfType(context) + || symbol.IsOfType(context) + || symbol.IsOfType(context); + } + + /// + /// Return all the symbols for the base types present in the current type symbol's underlying type inheritance + /// hierarchy, including the type from the symbol itself + /// + /// The type symbol to analyze + /// The symbols for all the base type for the current type symbol's underlying type, including the current stype + public static IEnumerable GetBaseTypesAndThis(this ITypeSymbol type) + { + var current = type; + while (current != null) + { + yield return current; + current = current.BaseType; + } + } + + /// + /// Return all the symbols for the base types present in the current type symbol's underlying type inheritance + /// hierarchy + /// + /// The type symbol to analyze + /// The symbols for all the base type for the current type symbol's underlying type + public static IEnumerable GetBaseTypes(this ITypeSymbol type) + { + var current = type.BaseType; + while (current != null) + { + yield return current; + current = current.BaseType; + } + } + + /// + /// Return the symbols representing all the classes in which the current type symbol's underlying type is contained, + /// including the class of the current symbol's underlying type + /// hierarchy + /// + /// The type symbol to analyze + /// The symbols for all the classes in which the current type symbol's underlying type is contained, including the current type + public static IEnumerable GetContainingTypesAndThis(this ITypeSymbol type) + { + var current = type; + while (current != null) + { + yield return current; + current = current.ContainingType; + } + } + + /// + /// Return the symbols representing all the classes in which the current type symbol's underlying type is contained + /// hierarchy + /// + /// The type symbol to analyze + /// The symbols for all the classes in which the current type symbol's underlying type is containede + public static IEnumerable GetContainingTypes(this ITypeSymbol type) + { + var current = type.ContainingType; + while (current != null) + { + yield return current; + current = current.ContainingType; + } + } + + /// + /// Return all the attributes applied to the current type, include the ones inherited from inheritance ancestors + /// + /// The type symbol to analyze + /// All the attributes applied to the type, either directly or through inheritance ancestors that have those attributes + public static IEnumerable GetAllAppliedAttributes(this ITypeSymbol type) + { + return type + .GetBaseTypesAndThis() + .SelectMany(t => t.GetAttributes()); + } + + /// + /// Returns an enumeration of the methods that are accessible from within a type, + /// including protected and private methods. + /// + /// The type to explore + /// The analysis context + /// If true, the returned methods will included the accessible (public and protected) methods this type inherits from + /// If true, this will include the methods marked as obsolete + /// Only targets methods with this specific name (optionnal) + /// The methods that are available from within the provided type (includes inherited methods if is set to true) + public static IEnumerable GetAllAccessibleMethodsFromWithinType( + this ITypeSymbol type, + SyntaxNodeAnalysisContext context, + bool visitHierarchy = true, + bool includeObsoleteMethods = false, + string methodName = null) + { + return GetAllMethodsWithinType(type, context, visitHierarchy, includeObsoleteMethods, methodName) + .Where(x => x.DeclaredAccessibility == Accessibility.Public || x.DeclaredAccessibility == Accessibility.ProtectedOrFriend); + + } + + /// + /// Returns an enumeration of the methods that are publicly accessible for a given type + /// + /// The type to explore + /// The analysis context + /// If true, the returned methods will included the publicly accessible methods this type inherits from + /// If true, this will include the methods marked as obsolete + /// Only targets methods with this specific name (optionnal) + /// The methods that are publicly accessible for a given type (includes inherited methods if is set to true) + public static IEnumerable GetAllPubliclyAccessibleMethodsFromType( + this ITypeSymbol type, + SyntaxNodeAnalysisContext context, + bool visitHierarchy = false, + bool includeObsoleteMethods = false, + string methodName = null) + { + return GetAllMethodsWithinType(type, context, visitHierarchy, includeObsoleteMethods, methodName) + .Where(m => m.DeclaredAccessibility == Accessibility.Public); + } + + /// + /// Returns am enmumeration of methods contained within a type + /// + /// The type to explore + /// The analysis context + /// If true, the returned methods will included the methods this type inherits from + /// If true, this will include the methods marked as obsolete + /// Only targets methods with this specific name (optionnal) + /// The methods from a given type (includes inherited methods if is set to true) + private static IEnumerable GetAllMethodsWithinType( + ITypeSymbol currentType, + SyntaxNodeAnalysisContext context, + bool visitHierarchy, + bool includeObsoleteMethods, + string methodName) + { + var obsoleteAttribute = context.SemanticModel.Compilation.GetTypeByMetadataName("System.ObsoleteAttribute"); + + while (currentType != null) + { + var members = methodName != null ? currentType.GetMembers(methodName) : currentType.GetMembers(); + var methods = members + .OfType() + .Where(x => !x.IsConstructorOrDestructor()) + .Where(x => !x.IsAbstract) + .Where(x => includeObsoleteMethods || !x.HasAttributes(obsoleteAttribute)); + + foreach (var method in methods) + { + yield return method; + } + + if (!visitHierarchy) + { + break; + } + + currentType = currentType.BaseType; + } + } + + /// + /// Returns true if two sets of types are equivalent, ignoring order. + /// + /// + /// + /// + public static bool AreTypeSetsEquivalent(this IEnumerable current, IEnumerable other) + { + var otherList = other.ToList(); + foreach (var typeInCurrent in current) + { + int equivalentInOther = otherList.FindIndex(typeInOther => typeInCurrent.IsEquivalentToType(typeInOther)); + if (equivalentInOther == -1) + { + return false; + } + else + { + otherList.RemoveAt(equivalentInOther); + } + } + //If we found a type in other for every type in current, and there are no remaining types in other, then the sets are an exact match + return otherList.Count == 0; + } + + /// + /// Returns true if typeSymbol is a type parameter (eg 'TValue') or a generic type with only type parameters within its type arguments (eg Dictionary or Dictionary>, but not Dictionary or Dictionary>), false otherwise + /// + /// + /// + public static bool IsTypeParameterOrGenericWithTypeParameterArguments(this ITypeSymbol typeSymbol) + { + if (typeSymbol is ITypeParameterSymbol) + { + return true; + } + var asNamedType = typeSymbol as INamedTypeSymbol; + if (asNamedType?.IsGenericType ?? false) + { + return asNamedType.TypeArguments.All(typeArg => typeArg.IsTypeParameterOrGenericWithTypeParameterArguments()); + } + return false; + } + + /// + /// Returns the given type as its most constrained/effective version according to its type and, possibly, its type constraints. + /// If no constrained version of the type is found, the type itself is returned. + /// In the case of an array type, the method can also optionally provide the most constrained version of the array's underlying type + /// + /// The type symbol which the method will attempt to resolve as a constrained/effective type + /// If true, the logic will apply on the underlying type of the array (i.e 'A' is the underlying array type of 'A[]') in the case the provided type is an . + /// If false, the method will simply return the array type in the case where in the case the provided type is an + /// If true, the given symbol, under its original or potentially reduced form, will be processed in order + /// to obtain a constrained type in the case where the symbol in question is a + /// The most constrained/effective version of the given symbol, or the symbol itself if no such constrained/effective type was found + public static ITypeSymbol GetUnderlyingTypeOrSelf(this ITypeSymbol typeSymbol, bool getReducedArrayType = true, bool tryResolvingGenericConstraint = true) + { + // Array type + var typeSymbolAsArrayType = typeSymbol as IArrayTypeSymbol; + if (typeSymbolAsArrayType != null) + { + if (getReducedArrayType) + { + typeSymbol = typeSymbolAsArrayType.ElementType; + } + else + { + return typeSymbolAsArrayType; + } + } + + // Named type + if (typeSymbol is INamedTypeSymbol) + { + return typeSymbol; + } + + // Type parameter + var typeAsTypeParameterSymbol = typeSymbol as ITypeParameterSymbol; + if (tryResolvingGenericConstraint && typeAsTypeParameterSymbol != null) + { + var typeAsConstrained = typeAsTypeParameterSymbol.TryGetAsConstrainedType(); + return typeAsConstrained ?? (ITypeSymbol)typeAsTypeParameterSymbol; + } + + return typeSymbol; + } + + + /// + /// Return a more display friendly name for some of the named types with abbreviated or otherwise vague names + /// + /// The named type symbol to get the name from + /// The display friendly version of the current type symbol if it exists, or simply its regular name if it does not + public static string GetDisplayFriendlyName(this ITypeSymbol current) + { + switch (current.SpecialType) + { + case SpecialType.System_Int16: + case SpecialType.System_Int32: + case SpecialType.System_Int64: + return "Integer"; + case SpecialType.System_UInt16: + case SpecialType.System_UInt32: + case SpecialType.System_UInt64: + return "UnsignedInteger"; + default: + return current.Name; + } + } + } +} diff --git a/src/Uno.CodeGen.RoslynHelpers/Uno.CodeGen.RoslynHelpers.projitems b/src/Uno.CodeGen.RoslynHelpers/Uno.CodeGen.RoslynHelpers.projitems new file mode 100644 index 0000000..5cb140d --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Uno.CodeGen.RoslynHelpers.projitems @@ -0,0 +1,26 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + f099b362-19d8-450b-b9b2-dca3cdfcaab5 + + + Uno.CodeGen.RoslynHelpers + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Uno.CodeGen.RoslynHelpers/Uno.CodeGen.RoslynHelpers.shproj b/src/Uno.CodeGen.RoslynHelpers/Uno.CodeGen.RoslynHelpers.shproj new file mode 100644 index 0000000..6f85af7 --- /dev/null +++ b/src/Uno.CodeGen.RoslynHelpers/Uno.CodeGen.RoslynHelpers.shproj @@ -0,0 +1,13 @@ + + + + f099b362-19d8-450b-b9b2-dca3cdfcaab5 + 14.0 + + + + + + + + diff --git a/src/Uno.CodeGen.Tests.ExternalClasses/Uno.CodeGen.Tests.ExternalClasses.csproj b/src/Uno.CodeGen.Tests.ExternalClasses/Uno.CodeGen.Tests.ExternalClasses.csproj index 2250f27..b6c9711 100644 --- a/src/Uno.CodeGen.Tests.ExternalClasses/Uno.CodeGen.Tests.ExternalClasses.csproj +++ b/src/Uno.CodeGen.Tests.ExternalClasses/Uno.CodeGen.Tests.ExternalClasses.csproj @@ -12,12 +12,12 @@ - - + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Uno.CodeGen.Tests.JsonDisabled/Uno.CodeGen.Tests.JsonDisabled.csproj b/src/Uno.CodeGen.Tests.JsonDisabled/Uno.CodeGen.Tests.JsonDisabled.csproj index 99ce869..23c8144 100644 --- a/src/Uno.CodeGen.Tests.JsonDisabled/Uno.CodeGen.Tests.JsonDisabled.csproj +++ b/src/Uno.CodeGen.Tests.JsonDisabled/Uno.CodeGen.Tests.JsonDisabled.csproj @@ -12,9 +12,9 @@ - + - + diff --git a/src/Uno.CodeGen.Tests.MinimalDeps/Uno.CodeGen.Tests.MinimalDeps.csproj b/src/Uno.CodeGen.Tests.MinimalDeps/Uno.CodeGen.Tests.MinimalDeps.csproj index 1c412d8..5b80658 100644 --- a/src/Uno.CodeGen.Tests.MinimalDeps/Uno.CodeGen.Tests.MinimalDeps.csproj +++ b/src/Uno.CodeGen.Tests.MinimalDeps/Uno.CodeGen.Tests.MinimalDeps.csproj @@ -20,7 +20,7 @@ --> - + diff --git a/src/Uno.CodeGen.Tests/Uno.CodeGen.Tests.csproj b/src/Uno.CodeGen.Tests/Uno.CodeGen.Tests.csproj index 21731f8..8fea753 100644 --- a/src/Uno.CodeGen.Tests/Uno.CodeGen.Tests.csproj +++ b/src/Uno.CodeGen.Tests/Uno.CodeGen.Tests.csproj @@ -1,52 +1,55 @@  - - net461 - false - 1701;1702;1705;NU1701 - Uno.CodeGen.Tests.ruleset - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - NU1701 - - - - - - - - - - - - - - - - - - - - + + net461 + false + 1701;1702;1705;NU1701 + Uno.CodeGen.Tests.ruleset + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers + + + NU1701 + + + + + + + + + + + + + + + + + + + + diff --git a/src/Uno.CodeGen.sln b/src/Uno.CodeGen.sln index 35be8d4..4b3cd53 100644 --- a/src/Uno.CodeGen.sln +++ b/src/Uno.CodeGen.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2024 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31320.298 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.CodeGen", "Uno.CodeGen\Uno.CodeGen.csproj", "{7A05DD54-F3F1-4A48-97A5-1A5F45D9F315}" EndProject @@ -35,7 +35,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.CodeGen.Tests.MinimalDe EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uno.CodeGen.Tests.JsonDisabled", "Uno.CodeGen.Tests.JsonDisabled\Uno.CodeGen.Tests.JsonDisabled.csproj", "{8A1F7BE0-46F6-455B-8720-5B0698843733}" EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Uno.CodeGen.RoslynHelpers", "Uno.CodeGen.RoslynHelpers\Uno.CodeGen.RoslynHelpers.shproj", "{F099B362-19D8-450B-B9B2-DCA3CDFCAAB5}" +EndProject Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + Uno.CodeGen.RoslynHelpers\Uno.CodeGen.RoslynHelpers.projitems*{3f7d2a75-2fbe-4b2c-ab93-1fbf7effe3c9}*SharedItemsImports = 5 + Uno.CodeGen.RoslynHelpers\Uno.CodeGen.RoslynHelpers.projitems*{f099b362-19d8-450b-b9b2-dca3cdfcaab5}*SharedItemsImports = 13 + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU diff --git a/src/Uno.CodeGen/EqualityGenerator.cs b/src/Uno.CodeGen/EqualityGenerator.cs index 242a16a..9367848 100644 --- a/src/Uno.CodeGen/EqualityGenerator.cs +++ b/src/Uno.CodeGen/EqualityGenerator.cs @@ -153,7 +153,7 @@ private void GenerateEquality(INamedTypeSymbol typeSymbol) builder.AppendLineInvariant("// "); builder.AppendLineInvariant("// **********************************************************************************************************************"); - builder.AppendLineInvariant("// This file has been generated by Uno.CodeGen (ImmutableGenerator), available at https://github.com/nventive/Uno.CodeGen"); + builder.AppendLineInvariant("// This file has been generated by Uno.CodeGen (ImmutableGenerator), available at https://github.com/unoplatform/Uno.CodeGen"); builder.AppendLineInvariant("// **********************************************************************************************************************"); builder.AppendLineInvariant("// "); builder.AppendLineInvariant("#pragma warning disable"); @@ -618,7 +618,7 @@ private void GenerateHashLogic(INamedTypeSymbol typeSymbol, IndentedStringBuilde Warning( builder, "There is no members marked with [Uno.EqualityHash] or [Uno.EqualityKey]. " + - "You should add at least one. Documentation: https://github.com/nventive/Uno.CodeGen/blob/master/doc/Equality%20Generation.md"); + "You should add at least one. Documentation: https://github.com/unoplatform/Uno.CodeGen/blob/master/doc/Equality%20Generation.md"); builder.AppendLineInvariant("return 0; // no members to compute hash"); } diff --git a/src/Uno.CodeGen/ImmutableGenerator.cs b/src/Uno.CodeGen/ImmutableGenerator.cs index 79c890c..4e9ed66 100644 --- a/src/Uno.CodeGen/ImmutableGenerator.cs +++ b/src/Uno.CodeGen/ImmutableGenerator.cs @@ -263,7 +263,7 @@ private void GenerateImmutable(INamedTypeSymbol typeSymbol, builder.AppendLineInvariant("// "); builder.AppendLineInvariant("// **********************************************************************************************************************"); - builder.AppendLineInvariant("// This file has been generated by Uno.CodeGen (ImmutableGenerator), available at https://github.com/nventive/Uno.CodeGen"); + builder.AppendLineInvariant("// This file has been generated by Uno.CodeGen (ImmutableGenerator), available at https://github.com/unoplatform/Uno.CodeGen"); builder.AppendLineInvariant("// **********************************************************************************************************************"); builder.AppendLineInvariant("// "); builder.AppendLineInvariant("#pragma warning disable"); diff --git a/src/Uno.CodeGen/InjectableGenerator.cs b/src/Uno.CodeGen/InjectableGenerator.cs index 7111f87..4336bc3 100644 --- a/src/Uno.CodeGen/InjectableGenerator.cs +++ b/src/Uno.CodeGen/InjectableGenerator.cs @@ -65,7 +65,7 @@ private void GenerateInjectable((INamedTypeSymbol type, IEnumerable<(ISymbol mem builder.AppendLineInvariant("// "); builder.AppendLineInvariant("// **************************************************************(********************************************************"); - builder.AppendLineInvariant("// This file has been generated by Uno.CodeGen (InjectableGenerator), available at https://github.com/nventive/Uno.CodeGen"); + builder.AppendLineInvariant("// This file has been generated by Uno.CodeGen (InjectableGenerator), available at https://github.com/unoplatform/Uno.CodeGen"); builder.AppendLineInvariant("// ***********************************************************************************************************************"); builder.AppendLineInvariant("// "); builder.AppendLineInvariant("#pragma warning disable"); diff --git a/src/Uno.CodeGen/Uno.CodeGen.csproj b/src/Uno.CodeGen/Uno.CodeGen.csproj index f2cfa26..b1f41e1 100644 --- a/src/Uno.CodeGen/Uno.CodeGen.csproj +++ b/src/Uno.CodeGen/Uno.CodeGen.csproj @@ -9,28 +9,30 @@ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml nventive nventive - https://github.com/nventive/Uno.CodeGen - https://nv-assets.azurewebsites.net/logos/uno.png - https://github.com/nventive/Uno.CodeGen + https://github.com/unoplatform/Uno.CodeGen + https://github.com/unoplatform/Uno.CodeGen This package provides tooling for code generation. Copyright (C) 2015-2018 nventive inc. - all rights reserved + uno.png - + - + - - + + compile; build; native; contentfiles; analyzers; buildtransitive + + all runtime; build; native; contentfiles; analyzers - + NU1701 @@ -45,6 +47,8 @@ true build + + diff --git a/src/Uno.Equality/Uno.Equality.csproj b/src/Uno.Equality/Uno.Equality.csproj index efddefe..d0d73b9 100644 --- a/src/Uno.Equality/Uno.Equality.csproj +++ b/src/Uno.Equality/Uno.Equality.csproj @@ -1,32 +1,34 @@ - - net461;netstandard1.3;netstandard2.0 - Equality Declarations - true - True - - full - True - nventive - nventive + + net461;netstandard1.3;netstandard2.0 + Equality Declarations + true + True + + full + True + nventive + nventive This package provides attributes for Equality source code generation. This package is part of the Uno.CodeGen to generate equality members in your project. - Uno.Equality - bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml - Copyright (C) 2015-2018 nventive inc. - all rights reserved - https://github.com/nventive/Uno.CodeGen - https://github.com/nventive/Uno.CodeGen - https://nv-assets.azurewebsites.net/logos/uno.png - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - + Uno.Equality + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + Copyright (C) 2015-2018 nventive inc. - all rights reserved + https://github.com/unoplatform/Uno.CodeGen + https://github.com/unoplatform/Uno.CodeGen + uno.png + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + diff --git a/src/Uno.Immutables/Uno.Immutables.csproj b/src/Uno.Immutables/Uno.Immutables.csproj index 8f8914d..8be9bd3 100644 --- a/src/Uno.Immutables/Uno.Immutables.csproj +++ b/src/Uno.Immutables/Uno.Immutables.csproj @@ -1,37 +1,39 @@ - - net461;netstandard1.3;netstandard2.0 - Immutable Declarations - true - True - - full - True - nventive - nventive + + net461;netstandard1.3;netstandard2.0 + Immutable Declarations + true + True + + full + True + nventive + nventive This package provides attributes for immutable entities source code generation. This package is part of the Uno.CodeGen to generate immutable entities in your project. - Uno - bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml - https://github.com/nventive/Uno.CodeGen - https://github.com/nventive/Uno.CodeGen - https://nv-assets.azurewebsites.net/logos/uno.png - Copyright (C) 2015-2018 nventive inc. - all rights reserved - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - + Uno + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + https://github.com/unoplatform/Uno.CodeGen + https://github.com/unoplatform/Uno.CodeGen + Copyright (C) 2015-2018 nventive inc. - all rights reserved + uno.png + - - - + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/src/Uno.Injectable/Uno.Injectable.csproj b/src/Uno.Injectable/Uno.Injectable.csproj index 7150fa1..05afc77 100644 --- a/src/Uno.Injectable/Uno.Injectable.csproj +++ b/src/Uno.Injectable/Uno.Injectable.csproj @@ -1,32 +1,34 @@ - - net461;netstandard1.3;netstandard2.0 - Injectable Declarations - true - True - - full - True - nventive - nventive + + net461;netstandard1.3;netstandard2.0 + Injectable Declarations + true + True + + full + True + nventive + nventive This package provides attributes for injectable entities source code generation. This package is part of the Uno.CodeGen to generate injectable entities in your project. - Uno - bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml - https://github.com/nventive/Uno.CodeGen - https://github.com/nventive/Uno.CodeGen - https://nv-assets.azurewebsites.net/logos/uno.png - Copyright (C) 2015-2018 nventive inc. - all rights reserved - - - - - - - all - runtime; build; native; contentfiles; analyzers - - + Uno + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + https://github.com/unoplatform/Uno.CodeGen + https://github.com/unoplatform/Uno.CodeGen + Copyright (C) 2015-2018 nventive inc. - all rights reserved + uno.png + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + diff --git a/src/uno.png b/src/uno.png new file mode 100644 index 0000000..7b9b432 Binary files /dev/null and b/src/uno.png differ