diff --git a/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs b/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs index 342ffcfd2..f27f9d8c1 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Symbols/FunctionEvaluator.cs @@ -55,12 +55,13 @@ public override void Evaluate() { var returnType = TryDetermineReturnValue(); var parameters = Eval.CreateFunctionParameters(_self, _function, FunctionDefinition, !stub); + CheckValidOverload(parameters); _overload.SetParameters(parameters); // Do process body of constructors since they may be declaring // variables that are later used to determine return type of other // methods and properties. - var ctor = _function.Name.EqualsOrdinal("__init__") || _function.Name.EqualsOrdinal("__new__"); + var ctor = _function.IsDunderInit() || _function.IsDunderNew(); if (ctor || returnType.IsUnknown() || Module.ModuleType == ModuleType.User) { // Return type from the annotation is sufficient for libraries and stubs, no need to walk the body. FunctionDefinition.Body?.Walk(this); @@ -98,6 +99,110 @@ private IPythonType TryDetermineReturnValue() { return annotationType; } + private void CheckValidOverload(IReadOnlyList parameters) { + if (_self?.MemberType == PythonMemberType.Class) { + switch (_function) { + case IPythonFunctionType function: + CheckValidFunction(function, parameters); + break; + case IPythonPropertyType property: + CheckValidProperty(property, parameters); + break; + } + } + } + + private void CheckValidFunction(IPythonFunctionType function, IReadOnlyList parameters) { + // Only give diagnostic errors on functions if the decorators are valid + if (!function.HasValidDecorators(Eval)) { + return; + } + + // Don't give diagnostics on functions defined in metaclasses + if (SelfIsMetaclass()) { + return; + } + + // Static methods don't need any diagnostics + if (function.IsStatic) { + return; + } + + // Otherwise, functions defined in classes must have at least one argument + if (parameters.IsNullOrEmpty()) { + var funcLoc = Eval.GetLocation(FunctionDefinition.NameExpression); + ReportFunctionParams(Resources.NoMethodArgument, ErrorCodes.NoMethodArgument, funcLoc); + return; + } + + var param = parameters[0].Name; + var paramLoc = Eval.GetLocation(FunctionDefinition.Parameters[0]); + // If it is a class method check for cls + if (function.IsClassMethod && !param.Equals("cls")) { + ReportFunctionParams(Resources.NoClsArgument, ErrorCodes.NoClsArgument, paramLoc); + } + + // If it is a method check for self + if (!function.IsClassMethod && !param.Equals("self")) { + ReportFunctionParams(Resources.NoSelfArgument, ErrorCodes.NoSelfArgument, paramLoc); + } + } + + private void CheckValidProperty(IPythonPropertyType property, IReadOnlyList parameters) { + // Only give diagnostic errors on properties if the decorators are valid + if (!property.HasValidDecorators(Eval)) { + return; + } + + // Don't give diagnostics on properties defined in metaclasses + if (SelfIsMetaclass()) { + return; + } + + // No diagnostics on static and class properties + if (property.IsStatic || property.IsClassMethod) { + return; + } + + // Otherwise, properties defined in classes must have at least one argument + if (parameters.IsNullOrEmpty()) { + var propertyLoc = Eval.GetLocation(FunctionDefinition.NameExpression); + ReportFunctionParams(Resources.NoMethodArgument, ErrorCodes.NoMethodArgument, propertyLoc); + return; + } + + var param = parameters[0].Name; + var paramLoc = Eval.GetLocation(FunctionDefinition.Parameters[0]); + // Only check for self on properties because static and class properties are invalid + if (!param.Equals("self")) { + ReportFunctionParams(Resources.NoSelfArgument, ErrorCodes.NoSelfArgument, paramLoc); + } + } + + /// + /// Returns if the function is part of a metaclass definition + /// e.g + /// class A(type): + /// def f(cls): ... + /// f is a metaclass function + /// + /// + private bool SelfIsMetaclass() { + // Just allow all specialized types in Mro to avoid false positives + return _self.Mro.Any(b => b.IsSpecialized); + } + + private void ReportFunctionParams(string message, string errorCode, LocationInfo location) { + Eval.ReportDiagnostics( + Eval.Module.Uri, + new DiagnosticsEntry( + message.FormatInvariant(FunctionDefinition.Name), + location.Span, + errorCode, + Parsing.Severity.Warning, + DiagnosticSource.Analysis)); + } + public override bool Walk(AssignmentStatement node) { var value = Eval.GetValueFromExpression(node.Right) ?? Eval.UnknownType; foreach (var lhs in node.Left) { @@ -119,10 +224,8 @@ public override bool Walk(ReturnStatement node) { var value = Eval.GetValueFromExpression(node.Expression); if (value != null) { // although technically legal, __init__ in a constructor should not have a not-none return value - if (FunctionDefinition.Name.EqualsOrdinal("__init__") && _function.DeclaringType.MemberType == PythonMemberType.Class - && !value.IsOfType(BuiltinTypeId.NoneType)) { - - Eval.ReportDiagnostics(Module.Uri, new Diagnostics.DiagnosticsEntry( + if (_function.IsDunderInit() && !value.IsOfType(BuiltinTypeId.NoneType)) { + Eval.ReportDiagnostics(Module.Uri, new DiagnosticsEntry( Resources.ReturnInInit, node.GetLocation(Eval).Span, ErrorCodes.ReturnInInit, diff --git a/src/Analysis/Ast/Impl/Analyzer/Symbols/SymbolCollector.cs b/src/Analysis/Ast/Impl/Analyzer/Symbols/SymbolCollector.cs index f1402a4bc..9a5fca410 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Symbols/SymbolCollector.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Symbols/SymbolCollector.cs @@ -138,6 +138,7 @@ private void AddOverload(FunctionDefinition fd, IPythonClassMember function, Act // collection types cannot be determined as imports haven't been processed. var overload = new PythonFunctionOverload(function, fd, _eval.GetLocationOfName(fd), fd.ReturnAnnotation?.ToCodeString(_eval.Ast)); addOverload(overload); + _table.Add(new FunctionEvaluator(_eval, overload)); } } @@ -165,19 +166,17 @@ private bool TryAddProperty(FunctionDefinition node, IPythonType declaringType) foreach (var d in decorators.OfType()) { switch (d.Name) { case @"property": - AddProperty(node, declaringType, false); - return true; case @"abstractproperty": - AddProperty(node, declaringType, true); + AddProperty(node, declaringType); return true; } } return false; } - private void AddProperty(FunctionDefinition fd, IPythonType declaringType, bool isAbstract) { + private void AddProperty(FunctionDefinition fd, IPythonType declaringType) { if (!(_eval.LookupNameInScopes(fd.Name, LookupOptions.Local) is PythonPropertyType existing)) { - existing = new PythonPropertyType(fd, _eval.GetLocationOfName(fd), declaringType, isAbstract); + existing = new PythonPropertyType(fd, _eval.GetLocationOfName(fd), declaringType); // The variable is transient (non-user declared) hence it does not have location. // Property type is tracking locations for references and renaming. _eval.DeclareVariable(fd.Name, existing, VariableSource.Declaration); diff --git a/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs b/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs index 80f362270..ebd6fec83 100644 --- a/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs +++ b/src/Analysis/Ast/Impl/Diagnostics/ErrorCodes.cs @@ -25,10 +25,14 @@ public static class ErrorCodes { public const string UndefinedVariable = "undefined-variable"; public const string VariableNotDefinedGlobally= "variable-not-defined-globally"; public const string VariableNotDefinedNonLocal = "variable-not-defined-nonlocal"; + public const string NoSelfArgument = "no-self-argument"; + public const string NoClsArgument = "no-cls-argument"; + public const string NoMethodArgument = "no-method-argument"; public const string ReturnInInit = "return-in-init"; public const string TypingTypeVarArguments = "typing-typevar-arguments"; public const string TypingNewTypeArguments = "typing-newtype-arguments"; public const string TypingGenericArguments = "typing-generic-arguments"; public const string InheritNonClass = "inherit-non-class"; + public const string InvalidDecoratorCombination = "invalid-decorator-combination"; } } diff --git a/src/Analysis/Ast/Impl/Extensions/FunctionDefinitionExtensions.cs b/src/Analysis/Ast/Impl/Extensions/FunctionDefinitionExtensions.cs new file mode 100644 index 000000000..6cf42b965 --- /dev/null +++ b/src/Analysis/Ast/Impl/Extensions/FunctionDefinitionExtensions.cs @@ -0,0 +1,13 @@ +using Microsoft.Python.Analysis.Types; + +namespace Microsoft.Python.Analysis { + public static class ClassMemberExtensions { + public static bool IsDunderInit(this IPythonClassMember member) { + return member.Name == "__init__" && member.DeclaringType?.MemberType == PythonMemberType.Class; + } + + public static bool IsDunderNew(this IPythonClassMember member) { + return member.Name == "__new__" && member.DeclaringType?.MemberType == PythonMemberType.Class; + } + } +} diff --git a/src/Analysis/Ast/Impl/Extensions/PythonFunctionExtensions.cs b/src/Analysis/Ast/Impl/Extensions/PythonFunctionExtensions.cs index b1914d9c0..f9e410e8f 100644 --- a/src/Analysis/Ast/Impl/Extensions/PythonFunctionExtensions.cs +++ b/src/Analysis/Ast/Impl/Extensions/PythonFunctionExtensions.cs @@ -14,16 +14,20 @@ // permissions and limitations under the License. using System.Linq; +using Microsoft.Python.Analysis.Analyzer; +using Microsoft.Python.Analysis.Diagnostics; using Microsoft.Python.Analysis.Types; using Microsoft.Python.Analysis.Values; using Microsoft.Python.Core; +using Microsoft.Python.Parsing; +using Microsoft.Python.Parsing.Ast; namespace Microsoft.Python.Analysis.Extensions { public static class PythonFunctionExtensions { - public static bool IsUnbound(this IPythonFunctionType f) + public static bool IsUnbound(this IPythonFunctionType f) => f.DeclaringType != null && f.MemberType == PythonMemberType.Function; - public static bool IsBound(this IPythonFunctionType f) + public static bool IsBound(this IPythonFunctionType f) => f.DeclaringType != null && f.MemberType == PythonMemberType.Method; public static bool HasClassFirstArgument(this IPythonClassMember m) @@ -34,5 +38,44 @@ public static IScope GetScope(this IPythonFunctionType f) { IScope gs = f.DeclaringModule.GlobalScope; return gs?.TraverseBreadthFirst(s => s.Children).FirstOrDefault(s => s.Node == f.FunctionDefinition); } + + /// + /// Reports any decorator errors on function. + /// Returns true if the decorator combinations for the property are valid + /// + public static bool HasValidDecorators(this IPythonFunctionType f, IExpressionEvaluator eval) { + bool valid = true; + // If function is abstract, allow all decorators because it will be overridden + if (f.IsAbstract) { + return valid; + } + + foreach (var dec in (f.FunctionDefinition?.Decorators?.Decorators).MaybeEnumerate().OfType()) { + switch (dec.Name) { + case @"staticmethod": + if (f.IsClassMethod) { + ReportInvalidDecorator(dec, Resources.InvalidDecoratorForFunction.FormatInvariant("Staticmethod", "class"), eval); + valid = false; + } + break; + case @"classmethod": + if (f.IsStatic) { + ReportInvalidDecorator(dec, Resources.InvalidDecoratorForFunction.FormatInvariant("Classmethod", "static"), eval); + valid = false; + } + break; + } + } + return valid; + } + + private static void ReportInvalidDecorator(NameExpression name, string errorMsg, IExpressionEvaluator eval) { + eval.ReportDiagnostics(eval.Module.Uri, + new DiagnosticsEntry( + errorMsg, eval.GetLocation(name).Span, + Diagnostics.ErrorCodes.InvalidDecoratorCombination, + Severity.Warning, DiagnosticSource.Analysis + )); + } } } diff --git a/src/Analysis/Ast/Impl/Extensions/PythonPropertyExtensions.cs b/src/Analysis/Ast/Impl/Extensions/PythonPropertyExtensions.cs new file mode 100644 index 000000000..940b044c5 --- /dev/null +++ b/src/Analysis/Ast/Impl/Extensions/PythonPropertyExtensions.cs @@ -0,0 +1,60 @@ +// Copyright(c) Microsoft Corporation +// 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 +// +// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +// MERCHANTABILITY OR NON-INFRINGEMENT. +// +// See the Apache Version 2.0 License for specific language governing +// permissions and limitations under the License. + +using System.Linq; +using Microsoft.Python.Analysis.Analyzer; +using Microsoft.Python.Analysis.Diagnostics; +using Microsoft.Python.Analysis.Types; +using Microsoft.Python.Core; +using Microsoft.Python.Parsing; +using Microsoft.Python.Parsing.Ast; + +namespace Microsoft.Python.Analysis.Extensions { + public static class PythonPropertyExtensions { + /// + /// Returns true if the decorator combiantions for the property are valid + /// + public static bool HasValidDecorators(this IPythonPropertyType p, IExpressionEvaluator eval) { + bool valid = true; + // If property is abstract, allow all decorators because it will be overridden + if(p.IsAbstract) { + return valid; + } + + foreach (var dec in (p.FunctionDefinition?.Decorators?.Decorators).MaybeEnumerate().OfType()) { + switch (dec.Name) { + case @"staticmethod": + ReportInvalidDecorator(dec, Resources.InvalidDecoratorForProperty.FormatInvariant("Staticmethods"), eval); + valid = false; + break; + case @"classmethod": + ReportInvalidDecorator(dec, Resources.InvalidDecoratorForProperty.FormatInvariant("Classmethods"), eval); + valid = false; + break; + } + } + return valid; + } + + private static void ReportInvalidDecorator(NameExpression name, string errorMsg, IExpressionEvaluator eval) { + eval.ReportDiagnostics(eval.Module.Uri, + new DiagnosticsEntry( + errorMsg, eval.GetLocation(name).Span, + Diagnostics.ErrorCodes.InvalidDecoratorCombination, + Severity.Warning, DiagnosticSource.Analysis + )); + } + } +} diff --git a/src/Analysis/Ast/Impl/Extensions/PythonTypeExtensions.cs b/src/Analysis/Ast/Impl/Extensions/PythonTypeExtensions.cs index d024a35a5..992fb79ea 100644 --- a/src/Analysis/Ast/Impl/Extensions/PythonTypeExtensions.cs +++ b/src/Analysis/Ast/Impl/Extensions/PythonTypeExtensions.cs @@ -37,8 +37,5 @@ public static void TransferDocumentationAndLocation(this IPythonType s, IPythonT dst.Location = src.Location; } } - - public static bool IsConstructor(this IPythonClassMember m) - => m.Name.EqualsOrdinal("__init__") || m.Name.EqualsOrdinal("__new__"); } } diff --git a/src/Analysis/Ast/Impl/Resources.Designer.cs b/src/Analysis/Ast/Impl/Resources.Designer.cs index b0b0d9dde..1a66ae4a8 100644 --- a/src/Analysis/Ast/Impl/Resources.Designer.cs +++ b/src/Analysis/Ast/Impl/Resources.Designer.cs @@ -258,6 +258,24 @@ internal static string InterpreterNotFound { } } + /// + /// Looks up a localized string similar to {0} is not a valid decorator for {1} methods.. + /// + internal static string InvalidDecoratorForFunction { + get { + return ResourceManager.GetString("InvalidDecoratorForFunction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} are not valid getters for the property decorator.. + /// + internal static string InvalidDecoratorForProperty { + get { + return ResourceManager.GetString("InvalidDecoratorForProperty", resourceCulture); + } + } + /// /// Looks up a localized string similar to The first argument to NewType must be a string, but it is of type '{0}'.. /// @@ -267,6 +285,33 @@ internal static string NewTypeFirstArgNotString { } } + /// + /// Looks up a localized string similar to Class method '{0}' should have 'cls' as first argument.. + /// + internal static string NoClsArgument { + get { + return ResourceManager.GetString("NoClsArgument", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Method '{0}' has no argument.. + /// + internal static string NoMethodArgument { + get { + return ResourceManager.GetString("NoMethodArgument", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Method '{0}' should have 'self' as first argument.. + /// + internal static string NoSelfArgument { + get { + return ResourceManager.GetString("NoSelfArgument", resourceCulture); + } + } + /// /// Looks up a localized string similar to property of type {0}. /// diff --git a/src/Analysis/Ast/Impl/Resources.resx b/src/Analysis/Ast/Impl/Resources.resx index 998b98d10..bbf019c4b 100644 --- a/src/Analysis/Ast/Impl/Resources.resx +++ b/src/Analysis/Ast/Impl/Resources.resx @@ -195,6 +195,9 @@ A single constraint to TypeVar is not allowed. + + Method '{0}' should have 'self' as first argument. + The first argument to NewType must be a string, but it is of type '{0}'. @@ -210,4 +213,16 @@ Inheriting '{0}', which is not a class. - + + Class method '{0}' should have 'cls' as first argument. + + + Method '{0}' has no argument. + + + {0} is not a valid decorator for {1} methods. + + + {0} are not valid getters for the property decorator. + + \ No newline at end of file diff --git a/src/Analysis/Ast/Impl/Specializations/Specialized.cs b/src/Analysis/Ast/Impl/Specializations/Specialized.cs index e71222bc4..500393fc1 100644 --- a/src/Analysis/Ast/Impl/Specializations/Specialized.cs +++ b/src/Analysis/Ast/Impl/Specializations/Specialized.cs @@ -19,7 +19,7 @@ namespace Microsoft.Python.Analysis.Specializations { internal static class Specialized { public static IPythonPropertyType Property(string name, IPythonModule declaringModule, IPythonType declaringType, IMember returnValue) { var location = new Location(declaringModule); - var prop = new PythonPropertyType(name, location, declaringType, false); + var prop = new PythonPropertyType(name, location, declaringType); var o = new PythonFunctionOverload(prop.Name, location); o.AddReturnValue(returnValue); prop.AddOverload(o); diff --git a/src/Analysis/Ast/Impl/Types/Definitions/IPythonPropertyType.cs b/src/Analysis/Ast/Impl/Types/Definitions/IPythonPropertyType.cs index 20fc3c932..0af82c844 100644 --- a/src/Analysis/Ast/Impl/Types/Definitions/IPythonPropertyType.cs +++ b/src/Analysis/Ast/Impl/Types/Definitions/IPythonPropertyType.cs @@ -30,6 +30,16 @@ public interface IPythonPropertyType : IPythonClassMember { /// string Description { get; } + /// + /// Property is a @classmethod. + /// + bool IsClassMethod { get; } + + /// + /// Property is @staticmethod. + /// + bool IsStatic { get; } + /// /// True if the property is read-only. /// diff --git a/src/Analysis/Ast/Impl/Types/PythonFunctionType.cs b/src/Analysis/Ast/Impl/Types/PythonFunctionType.cs index 63208bc8b..5dc121fc1 100644 --- a/src/Analysis/Ast/Impl/Types/PythonFunctionType.cs +++ b/src/Analysis/Ast/Impl/Types/PythonFunctionType.cs @@ -25,6 +25,7 @@ namespace Microsoft.Python.Analysis.Types { [DebuggerDisplay("Function {Name} ({TypeId})")] internal sealed class PythonFunctionType : PythonType, IPythonFunctionType { + private static readonly IReadOnlyList DefaultClassMethods = new[] { "__new__", "__init_subclass__", "__class_getitem__" }; private ImmutableArray _overloads = ImmutableArray.Empty; private bool _isAbstract; private bool _isSpecialized; @@ -60,12 +61,13 @@ public PythonFunctionType( IPythonType declaringType, Location location ) : base(fd.Name, location, - fd.Name == "__init__" ? (declaringType?.Documentation ?? fd.GetDocumentation()) : fd.GetDocumentation(), + fd.Name == "__init__" ? (declaringType?.Documentation ?? fd.GetDocumentation()) : fd.GetDocumentation(), declaringType != null ? BuiltinTypeId.Method : BuiltinTypeId.Function) { DeclaringType = declaringType; location.Module.AddAstNode(this, fd); ProcessDecorators(fd); + DecideClassMethod(); } #region IPythonType @@ -117,9 +119,16 @@ internal void AddOverload(IPythonFunctionOverload overload) internal IPythonFunctionType ToUnbound() => new PythonUnboundMethod(this); + private void DecideClassMethod() { + if (IsClassMethod) { + return; + } + + IsClassMethod = DefaultClassMethods.Contains(Name); + } + private void ProcessDecorators(FunctionDefinition fd) { foreach (var dec in (fd.Decorators?.Decorators).MaybeEnumerate().OfType()) { - // TODO: warn about incompatible combinations. switch (dec.Name) { case @"staticmethod": IsStatic = true; diff --git a/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs b/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs index 9728627fa..219147b94 100644 --- a/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs +++ b/src/Analysis/Ast/Impl/Types/PythonPropertyType.cs @@ -13,6 +13,7 @@ // See the Apache Version 2.0 License for specific language governing // permissions and limitations under the License. +using System.Linq; using Microsoft.Python.Analysis.Values; using Microsoft.Python.Core; using Microsoft.Python.Parsing.Ast; @@ -20,16 +21,44 @@ namespace Microsoft.Python.Analysis.Types { class PythonPropertyType : PythonType, IPythonPropertyType { private IPythonFunctionOverload _getter; + private bool _isAbstract; - public PythonPropertyType(FunctionDefinition fd, Location location, IPythonType declaringType, bool isAbstract) - : this(fd.Name, location, declaringType, isAbstract) { + public PythonPropertyType(FunctionDefinition fd, Location location, IPythonType declaringType) + : this(fd.Name, location, declaringType) { declaringType.DeclaringModule.AddAstNode(this, fd); + ProcessDecorators(); } - public PythonPropertyType(string name, Location location, IPythonType declaringType, bool isAbstract) + public PythonPropertyType(string name, Location location, IPythonType declaringType) : base(name, location, string.Empty, BuiltinTypeId.Property) { DeclaringType = declaringType; - IsAbstract = isAbstract; + } + + private void ProcessDecorators() { + foreach (var dec in (FunctionDefinition?.Decorators?.Decorators).MaybeEnumerate().OfType()) { + switch (dec.Name) { + case @"staticmethod": + IsStatic = true; + break; + case @"classmethod": + IsClassMethod = true; + break; + case @"abstractmethod": + _isAbstract = true; + break; + case @"abstractstaticmethod": + IsStatic = true; + _isAbstract = true; + break; + case @"abstractclassmethod": + IsClassMethod = true; + _isAbstract = true; + break; + case @"abstractproperty": + _isAbstract = true; + break; + } + } } #region IPythonType @@ -38,10 +67,12 @@ public PythonPropertyType(string name, Location location, IPythonType declaringT #region IPythonPropertyType public FunctionDefinition FunctionDefinition => DeclaringModule.GetAstNode(this); - public override bool IsAbstract { get; } + public override bool IsAbstract => _isAbstract; public bool IsReadOnly => true; public IPythonType DeclaringType { get; } - public string Description + public bool IsStatic { get; private set; } + public bool IsClassMethod { get; private set; } + public string Description => Type == null ? Resources.PropertyOfUnknownType : Resources.PropertyOfType.FormatUI(Type.Name); public override IMember Call(IPythonInstance instance, string memberName, IArgumentSet args) => _getter?.Call(args, instance?.GetPythonType() ?? DeclaringType); @@ -49,5 +80,6 @@ public override IMember Call(IPythonInstance instance, string memberName, IArgum internal void AddOverload(IPythonFunctionOverload overload) => _getter = _getter ?? overload; private IPythonType Type => _getter?.Call(ArgumentSet.WithoutContext, DeclaringType)?.GetPythonType(); + } } diff --git a/src/Analysis/Ast/Test/LintDecoratorCombinationTests.cs b/src/Analysis/Ast/Test/LintDecoratorCombinationTests.cs new file mode 100644 index 000000000..1c9bbfa6c --- /dev/null +++ b/src/Analysis/Ast/Test/LintDecoratorCombinationTests.cs @@ -0,0 +1,194 @@ +// Copyright(c) Microsoft Corporation +// 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 +// +// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +// MERCHANTABILITY OR NON-INFRINGEMENT. +// +// See the Apache Version 2.0 License for specific language governing +// permissions and limitations under the License. + +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Python.Analysis.Diagnostics; +using Microsoft.Python.Analysis.Tests.FluentAssertions; +using Microsoft.Python.Core; +using Microsoft.Python.Parsing.Tests; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; + +namespace Microsoft.Python.Analysis.Tests { + [TestClass] + public class LintDecoratorCombinationTests : AnalysisTestBase { + public TestContext TestContext { get; set; } + + [TestInitialize] + public void TestInitialize() + => TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}"); + + [TestCleanup] + public void Cleanup() => TestEnvironmentImpl.TestCleanup(); + + [TestMethod, Priority(0)] + public async Task ClassMethodAndProperty() { + const string code = @" +class Test: + @property + @classmethod + def test(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.InvalidDecoratorCombination); + diagnostic.SourceSpan.Should().Be(4, 6, 4, 17); + diagnostic.Message.Should().Be(Resources.InvalidDecoratorForProperty.FormatInvariant("Classmethods")); + } + + [TestMethod, Priority(0)] + public async Task ClassMethodStaticMethod() { + const string code = @" +class Test: + @staticmethod + @classmethod + def test(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(2); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.InvalidDecoratorCombination); + diagnostic.SourceSpan.Should().Be(3, 6, 3, 18); + diagnostic.Message.Should().Be(Resources.InvalidDecoratorForFunction.FormatInvariant("Staticmethod", "class")); + + diagnostic = analysis.Diagnostics.ElementAt(1); + diagnostic.ErrorCode.Should().Be(ErrorCodes.InvalidDecoratorCombination); + diagnostic.SourceSpan.Should().Be(4, 6, 4, 17); + diagnostic.Message.Should().Be(Resources.InvalidDecoratorForFunction.FormatInvariant("Classmethod", "static")); + } + + [TestMethod, Priority(0)] + public async Task StaticMethodAndProperty() { + const string code = @" +class Test: + @property + @staticmethod + def test(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.InvalidDecoratorCombination); + diagnostic.SourceSpan.Should().Be(4, 6, 4, 18); + diagnostic.Message.Should().Be(Resources.InvalidDecoratorForProperty.FormatInvariant("Staticmethods")); + } + + + [TestMethod, Priority(0)] + public async Task StaticMethodClassMethodAndProperty() { + const string code = @" +class Test: + @property + @staticmethod + @classmethod + def test(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(2); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.InvalidDecoratorCombination); + diagnostic.SourceSpan.Should().Be(4, 6, 4, 18); + diagnostic.Message.Should().Be(Resources.InvalidDecoratorForProperty.FormatInvariant("Staticmethods")); + + diagnostic = analysis.Diagnostics.ElementAt(1); + diagnostic.ErrorCode.Should().Be(ErrorCodes.InvalidDecoratorCombination); + diagnostic.SourceSpan.Should().Be(5, 6, 5, 17); + diagnostic.Message.Should().Be(Resources.InvalidDecoratorForProperty.FormatInvariant("Classmethods")); + } + + [TestMethod, Priority(0)] + public async Task UnboundStaticMethodClassMethodAndProperty() { + const string code = @" +@staticmethod +@classmethod +def test(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task NoDecoratorMethods() { + const string code = @" +def test(x, y, z): + pass + +class A: + def test(self, x): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task AbstractClassPropertyNoErrors() { + const string code = @" +from abc import abstractmethod + +class A: + @property + @classmethod + @abstractmethod + def test(self, x): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task AbstractStaticClassMethodNoErrors() { + const string code = @" +from abc import abstractmethod + +class A: + @property + @staticmethod + @classmethod + @abstractmethod + def test(self, x): + pass + + @staticmethod + @classmethod + @abstractmethod + def test1(cls, x): + pass + + @property + @staticmethod + @classmethod + @abstractmethod + def test2(x, y): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + } +} diff --git a/src/Analysis/Ast/Test/LintInheritNonClassTests.cs b/src/Analysis/Ast/Test/LintInheritNonClassTests.cs index df97806ca..8cea18731 100644 --- a/src/Analysis/Ast/Test/LintInheritNonClassTests.cs +++ b/src/Analysis/Ast/Test/LintInheritNonClassTests.cs @@ -202,7 +202,7 @@ def hello(self): from module1 import B class C(B): - def hello(): + def hello(self): pass "; diff --git a/src/Analysis/Ast/Test/LintNewTypeTests.cs b/src/Analysis/Ast/Test/LintNewTypeTests.cs index aacddbf87..e12068d32 100644 --- a/src/Analysis/Ast/Test/LintNewTypeTests.cs +++ b/src/Analysis/Ast/Test/LintNewTypeTests.cs @@ -75,7 +75,7 @@ public async Task ObjectFirstArg() { from typing import NewType class X: - def hello(): + def hello(self): pass h = X() diff --git a/src/Analysis/Ast/Test/LintNoClsArgumentTests.cs b/src/Analysis/Ast/Test/LintNoClsArgumentTests.cs new file mode 100644 index 000000000..2677a957e --- /dev/null +++ b/src/Analysis/Ast/Test/LintNoClsArgumentTests.cs @@ -0,0 +1,186 @@ +// Copyright(c) Microsoft Corporation +// 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 +// +// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +// MERCHANTABILITY OR NON-INFRINGEMENT. +// +// See the Apache Version 2.0 License for specific language governing +// permissions and limitations under the License. + +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Python.Analysis.Diagnostics; +using Microsoft.Python.Analysis.Tests.FluentAssertions; +using Microsoft.Python.Core; +using Microsoft.Python.Parsing.Tests; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; + +namespace Microsoft.Python.Analysis.Tests { + [TestClass] + public class LintNoClsArgumentTests : AnalysisTestBase { + public TestContext TestContext { get; set; } + + [TestInitialize] + public void TestInitialize() + => TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}"); + + [TestCleanup] + public void Cleanup() => TestEnvironmentImpl.TestCleanup(); + + [TestMethod, Priority(0)] + public async Task FirstArgumentClassMethodNotCls() { + const string code = @" +class Test: + @classmethod + def test(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoClsArgument); + diagnostic.SourceSpan.Should().Be(4, 14, 4, 15); + diagnostic.Message.Should().Be(Resources.NoClsArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task AbstractClassMethodNeedsClsFirstArg() { + const string code = @" +from abc import abstractmethod + +class A: + @classmethod + @abstractmethod + def test(self, x): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoClsArgument); + diagnostic.SourceSpan.Should().Be(7, 14, 7, 18); + diagnostic.Message.Should().Be(Resources.NoClsArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task ClsMethodValidInMetaclass() { + const string code = @" +class A(type): + def x(cls): pass + +class B(A): + def y(cls): pass + +class MyClass(metaclass=B): + pass + +MyClass.x() +MyClass.y() +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task FirstArgumentClassMethodSpecialCase() { + const string code = @" +class Test: + def __new__(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoClsArgument); + diagnostic.SourceSpan.Should().Be(3, 17, 3, 18); + diagnostic.Message.Should().Be(Resources.NoClsArgument.FormatInvariant("__new__")); + } + + [TestMethod, Priority(0)] + public async Task FirstArgumentNotClsMultiple() { + const string code = @" +class Test: + @classmethod + def test(x, y, z): + pass + + @classmethod + def test2(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(2); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoClsArgument); + diagnostic.SourceSpan.Should().Be(4, 14, 4, 15); + diagnostic.Message.Should().Be(Resources.NoClsArgument.FormatInvariant("test")); + + diagnostic = analysis.Diagnostics.ElementAt(1); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoClsArgument); + diagnostic.SourceSpan.Should().Be(8, 15, 8, 16); + diagnostic.Message.Should().Be(Resources.NoClsArgument.FormatInvariant("test2")); + } + + [TestMethod, Priority(0)] + public async Task NestedClassFuncNoClsArg() { + const string code = @" +class Test: + class Test2: + @classmethod + def hello(x, y, z): + pass + + @classmethod + def test(x, y, z): ... +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(2); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoClsArgument); + diagnostic.SourceSpan.Should().Be(5, 19, 5, 20); + diagnostic.Message.Should().Be(Resources.NoClsArgument.FormatInvariant("hello")); + + diagnostic = analysis.Diagnostics.ElementAt(1); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoClsArgument); + diagnostic.SourceSpan.Should().Be(9, 14, 9, 15); + diagnostic.Message.Should().Be(Resources.NoClsArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task FirstArgumentIsCls() { + const string code = @" +class Test: + @classmethod + def test(cls): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task FirstArgumentIsClsManyParams() { + const string code = @" +class Test: + @classmethod + def test(cls, a, b, c, d, e): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + } +} diff --git a/src/Analysis/Ast/Test/LintNoMethodArgumentTests.cs b/src/Analysis/Ast/Test/LintNoMethodArgumentTests.cs new file mode 100644 index 000000000..face8184b --- /dev/null +++ b/src/Analysis/Ast/Test/LintNoMethodArgumentTests.cs @@ -0,0 +1,164 @@ +// Copyright(c) Microsoft Corporation +// 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 +// +// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +// MERCHANTABILITY OR NON-INFRINGEMENT. +// +// See the Apache Version 2.0 License for specific language governing +// permissions and limitations under the License. + +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Python.Analysis.Diagnostics; +using Microsoft.Python.Analysis.Tests.FluentAssertions; +using Microsoft.Python.Core; +using Microsoft.Python.Parsing.Tests; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; + +namespace Microsoft.Python.Analysis.Tests { + [TestClass] + public class LintNoMethodArgumentTests : AnalysisTestBase { + public TestContext TestContext { get; set; } + + [TestInitialize] + public void TestInitialize() + => TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}"); + + [TestCleanup] + public void Cleanup() => TestEnvironmentImpl.TestCleanup(); + + [TestMethod, Priority(0)] + public async Task MethodNoArgs() { + const string code = @" +class Test: + def test(): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoMethodArgument); + diagnostic.SourceSpan.Should().Be(3, 9, 3, 13); + diagnostic.Message.Should().Be(Resources.NoMethodArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task PropertyNoArgs() { + const string code = @" +class Test: + @property + def test(): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoMethodArgument); + diagnostic.SourceSpan.Should().Be(4, 9, 4, 13); + diagnostic.Message.Should().Be(Resources.NoMethodArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task AbstractPropertyNoArgs() { + const string code = @" +class Test: + @abstractmethod + @property + def test(): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoMethodArgument); + diagnostic.SourceSpan.Should().Be(5, 9, 5, 13); + diagnostic.Message.Should().Be(Resources.NoMethodArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task AbstractClassPropertyNoArgs() { + const string code = @" +class Test: + @classmethod + @abstractmethod + @property + def test(): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task ClassMethodNoArgs() { + const string code = @" +class Test: + @classmethod + def test(): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoMethodArgument); + diagnostic.SourceSpan.Should().Be(4, 9, 4, 13); + diagnostic.Message.Should().Be(Resources.NoMethodArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task DefaultMethodNoArgs() { + const string code = @" +class Test: + def __init__(): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoMethodArgument); + diagnostic.SourceSpan.Should().Be(3, 9, 3, 17); + diagnostic.Message.Should().Be(Resources.NoMethodArgument.FormatInvariant("__init__")); + } + + [TestMethod, Priority(0)] + public async Task FirstArgumentSpace() { + const string code = @" +class Test: + def test( ): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoMethodArgument); + diagnostic.SourceSpan.Should().Be(3, 9, 3, 13); + diagnostic.Message.Should().Be(Resources.NoMethodArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task NoDiagnosticOnStaticMethod() { + const string code = @" +class Test: + @staticmethod + def test(): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + } +} diff --git a/src/Analysis/Ast/Test/LintNoSelfArgumentTests.cs b/src/Analysis/Ast/Test/LintNoSelfArgumentTests.cs new file mode 100644 index 000000000..02623d98a --- /dev/null +++ b/src/Analysis/Ast/Test/LintNoSelfArgumentTests.cs @@ -0,0 +1,227 @@ +// Copyright(c) Microsoft Corporation +// 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 +// +// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +// MERCHANTABILITY OR NON-INFRINGEMENT. +// +// See the Apache Version 2.0 License for specific language governing +// permissions and limitations under the License. + +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Python.Analysis.Diagnostics; +using Microsoft.Python.Analysis.Tests.FluentAssertions; +using Microsoft.Python.Core; +using Microsoft.Python.Parsing.Tests; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; + +namespace Microsoft.Python.Analysis.Tests { + [TestClass] + public class LintNoSelfArgumentTests : AnalysisTestBase { + public TestContext TestContext { get; set; } + + [TestInitialize] + public void TestInitialize() + => TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}"); + + [TestCleanup] + public void Cleanup() => TestEnvironmentImpl.TestCleanup(); + + [TestMethod, Priority(0)] + public async Task FirstArgumentMethodNotSelf() { + const string code = @" +class Test: + def test(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoSelfArgument); + diagnostic.SourceSpan.Should().Be(3, 14, 3, 15); + diagnostic.Message.Should().Be(Resources.NoSelfArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task FirstArgumentPropertyNotSelf() { + const string code = @" +class Test: + @property + def test(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoSelfArgument); + diagnostic.SourceSpan.Should().Be(4, 14, 4, 15); + diagnostic.Message.Should().Be(Resources.NoSelfArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task FirstArgumentAbstractPropertyNotSelf() { + const string code = @" +class Test: + @abstractproperty + def test(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoSelfArgument); + diagnostic.SourceSpan.Should().Be(4, 14, 4, 15); + diagnostic.Message.Should().Be(Resources.NoSelfArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task FirstArgumentNotSelfMultiple() { + const string code = @" +class Test: + def test(x, y, z): + pass + + def test2(x, y, z): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(2); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoSelfArgument); + diagnostic.SourceSpan.Should().Be(3, 14, 3, 15); + diagnostic.Message.Should().Be(Resources.NoSelfArgument.FormatInvariant("test")); + + diagnostic = analysis.Diagnostics.ElementAt(1); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoSelfArgument); + diagnostic.SourceSpan.Should().Be(6, 15, 6, 16); + diagnostic.Message.Should().Be(Resources.NoSelfArgument.FormatInvariant("test2")); + } + + [TestMethod, Priority(0)] + public async Task NestedClassFuncNoSelfArg() { + const string code = @" +class Test: + class Test2: + def hello(x, y, z): + pass + + def test(x, y, z): ... +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(2); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoSelfArgument); + diagnostic.SourceSpan.Should().Be(4, 19, 4, 20); + diagnostic.Message.Should().Be(Resources.NoSelfArgument.FormatInvariant("hello")); + + diagnostic = analysis.Diagnostics.ElementAt(1); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoSelfArgument); + diagnostic.SourceSpan.Should().Be(7, 14, 7, 15); + diagnostic.Message.Should().Be(Resources.NoSelfArgument.FormatInvariant("test")); + } + + [TestMethod, Priority(0)] + public async Task FirstArgumentIsSelf() { + const string code = @" +class Test: + def test(self): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task FirstArgumentIsSelfManyParams() { + const string code = @" +class Test: + def test(self, a, b, c, d, e): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task StaticMethodNoSelfValid() { + const string code = @" +class C: + @staticmethod + def test(a, b): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task NormalFunction() { + const string code = @" +def test(): + pass + +class C: + def test1(self): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task NormalProperty() { + const string code = @" +class C: + @property + def test(self): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task AbstractClassMethod() { + const string code = @" +from typing import abstractclassmethod +class A: + @abstractclassmethod + def test(cls): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().BeEmpty(); + } + + [TestMethod, Priority(0)] + public async Task AbstractProperty() { + const string code = @" +class A: + @property + @abstractmethod + def test(cls): + pass +"; + var analysis = await GetAnalysisAsync(code); + analysis.Diagnostics.Should().HaveCount(1); + + var diagnostic = analysis.Diagnostics.ElementAt(0); + diagnostic.ErrorCode.Should().Be(ErrorCodes.NoSelfArgument); + diagnostic.SourceSpan.Should().Be(5, 14, 5, 17); + diagnostic.Message.Should().Be(Resources.NoSelfArgument.FormatInvariant("test")); + } + } +}