11using Microsoft . CodeAnalysis ;
22using Microsoft . CodeAnalysis . CSharp . Syntax ;
33using Microsoft . CodeAnalysis . Diagnostics ;
4+ using Microsoft . CodeAnalysis . Text ;
45using System ;
56using System . Collections . Concurrent ;
67using System . Collections . Generic ;
@@ -134,7 +135,8 @@ public static class Categories
134135 customTags : WellKnownDiagnosticTags . CompilationEnd ,
135136 helpLinkUri : "https://www.outsystems.com/tk/redirect?g=OS-ELG-MODL-05008" ) ;
136137
137- // https://www.outsystems.com/tk/redirect?g=OS-ELG-MODL-05009 - not implementing
138+ // https://www.outsystems.com/tk/redirect?g=OS-ELG-MODL-05009 - not possible to implement
139+ // (see https://github.com/jonathanalgar/CustomCode-Analyzer/issues/11)
138140
139141 private static readonly DiagnosticDescriptor NonPublicStructRule = new (
140142 DiagnosticIds . NonPublicStruct ,
@@ -544,14 +546,19 @@ Location GetMemberLocation()
544546 IPropertySymbol p => p . Type ,
545547 _ => null
546548 } ;
547-
548549 // If the type is not valid, report the unsupported parameter type
549550 if ( memberType != null && ! IsValidParameterType ( memberType , context . Compilation ) )
550551 {
551- // Get the location of the member's type
552- var loc = member . DeclaringSyntaxReferences . First ( ) . GetSyntax ( ) . GetLocation ( ) ;
552+ // Get the location based on whether it's a field or property
553+ Location loc = member switch
554+ {
555+ IFieldSymbol _ when member . DeclaringSyntaxReferences . FirstOrDefault ( ) ? . GetSyntax ( )
556+ is VariableDeclaratorSyntax fieldSyntax => fieldSyntax . Identifier . GetLocation ( ) ,
557+ IPropertySymbol _ when member . DeclaringSyntaxReferences . FirstOrDefault ( ) ? . GetSyntax ( )
558+ is PropertyDeclarationSyntax propSyntax => propSyntax . Identifier . GetLocation ( ) ,
559+ _ => member . DeclaringSyntaxReferences . First ( ) . GetSyntax ( ) . GetLocation ( )
560+ } ;
553561
554- // Report diagnostic if the parameter type is unsupported
555562 context . ReportDiagnostic (
556563 Diagnostic . Create (
557564 UnsupportedParameterTypeRule ,
@@ -594,11 +601,22 @@ private static void AnalyzeInterface(
594601
595602 // Extract library name from attribute or interface name
596603 string libraryName = null ;
604+ Location nameLocation = null ;
605+
597606 // First, check the 'Name' property
598607 var nameArg = osInterfaceAttr . NamedArguments . FirstOrDefault ( na => na . Key == "Name" ) ;
599608 if ( nameArg . Key != null && nameArg . Value . Value is string specifiedName )
600609 {
601610 libraryName = specifiedName ;
611+ // Get the location from the attribute argument syntax
612+ var attrSyntax = osInterfaceAttr . ApplicationSyntaxReference ? . GetSyntax ( ) as AttributeSyntax ;
613+ var nameArgSyntax = attrSyntax ? . ArgumentList ? . Arguments
614+ . FirstOrDefault ( arg => arg . NameEquals ? . Name . Identifier . ValueText == "Name" )
615+ ? . Expression as LiteralExpressionSyntax ;
616+ if ( nameArgSyntax != null )
617+ {
618+ nameLocation = nameArgSyntax . GetLocation ( ) ;
619+ }
602620 }
603621 else
604622 {
@@ -607,12 +625,26 @@ private static void AnalyzeInterface(
607625 if ( originalNameArg . Key != null && originalNameArg . Value . Value is string originalName )
608626 {
609627 libraryName = originalName ;
628+ // Get the location from the attribute argument syntax
629+ var attrSyntax = osInterfaceAttr . ApplicationSyntaxReference ? . GetSyntax ( ) as AttributeSyntax ;
630+ var originalNameArgSyntax = attrSyntax ? . ArgumentList ? . Arguments
631+ . FirstOrDefault ( arg => arg . NameEquals ? . Name . Identifier . ValueText == "OriginalName" )
632+ ? . Expression as LiteralExpressionSyntax ;
633+ if ( originalNameArgSyntax != null )
634+ {
635+ nameLocation = originalNameArgSyntax . GetLocation ( ) ;
636+ }
610637 }
611638 }
639+
612640 // If no name specified in attributes, use the interface name without 'I' prefix
613- libraryName ??= ( typeSymbol . Name . StartsWith ( "I" , StringComparison . Ordinal ) )
614- ? typeSymbol . Name . Substring ( 1 )
615- : typeSymbol . Name ;
641+ if ( libraryName == null )
642+ {
643+ libraryName = ( typeSymbol . Name . StartsWith ( "I" , StringComparison . Ordinal ) )
644+ ? typeSymbol . Name . Substring ( 1 )
645+ : typeSymbol . Name ;
646+ nameLocation = syntax . Identifier . GetLocation ( ) ;
647+ }
616648
617649 // Validate name constraints
618650 if ( libraryName . Length > 50 )
@@ -621,7 +653,7 @@ private static void AnalyzeInterface(
621653 context . ReportDiagnostic (
622654 Diagnostic . Create (
623655 NameMaxLengthExceededRule ,
624- syntax . GetLocation ( ) ,
656+ nameLocation ?? syntax . GetLocation ( ) , // Fall back to full syntax if we couldn't get specific location
625657 libraryName ) ) ;
626658 }
627659
@@ -631,7 +663,7 @@ private static void AnalyzeInterface(
631663 context . ReportDiagnostic (
632664 Diagnostic . Create (
633665 NameBeginsWithNumbersRule ,
634- syntax . GetLocation ( ) ,
666+ nameLocation ?? syntax . GetLocation ( ) ,
635667 libraryName ) ) ;
636668 }
637669
@@ -642,7 +674,7 @@ private static void AnalyzeInterface(
642674 context . ReportDiagnostic (
643675 Diagnostic . Create (
644676 UnsupportedCharactersInNameRule ,
645- syntax . GetLocation ( ) ,
677+ nameLocation ?? syntax . GetLocation ( ) ,
646678 libraryName ,
647679 string . Join ( ", " , invalidChars ) ) ) ;
648680 }
@@ -653,7 +685,12 @@ private static void AnalyzeInterface(
653685 context . ReportDiagnostic (
654686 Diagnostic . Create (
655687 NonPublicInterfaceRule ,
656- syntax . GetLocation ( ) ,
688+ Location . Create (
689+ syntax . SyntaxTree ,
690+ TextSpan . FromBounds (
691+ syntax . Keyword . SpanStart ,
692+ syntax . Identifier . Span . End
693+ ) ) ,
657694 typeSymbol . Name ) ) ;
658695 }
659696 }
@@ -682,12 +719,17 @@ private static void AnalyzeMethod(SymbolAnalysisContext context, IMethodSymbol m
682719 if ( ( hasOSInterfaceAttribute || implementsOSInterface ) &&
683720 methodSymbol . Name . StartsWith ( "_" , StringComparison . Ordinal ) )
684721 {
685- context . ReportDiagnostic (
686- Diagnostic . Create (
687- NameBeginsWithUnderscoresRule ,
688- methodSyntax . GetLocation ( ) ,
689- "Method" ,
690- methodSymbol . Name ) ) ;
722+ if ( syntaxRef . GetSyntax ( ) is MethodDeclarationSyntax methodDeclSyntax )
723+ {
724+ var identifierLocation = methodDeclSyntax . Identifier . GetLocation ( ) ;
725+
726+ context . ReportDiagnostic (
727+ Diagnostic . Create (
728+ NameBeginsWithUnderscoresRule ,
729+ identifierLocation ,
730+ "Method" ,
731+ methodSymbol . Name ) ) ;
732+ }
691733 }
692734
693735 // Only check for by-ref parameters in the OSInterface itself (not the implementation)
@@ -785,11 +827,32 @@ private static void AnalyzeClass(SymbolAnalysisContext context, INamedTypeSymbol
785827 if ( ! typeSymbol . DeclaredAccessibility . HasFlag ( Accessibility . Public ) &&
786828 typeSymbol . DeclaringSyntaxReferences . FirstOrDefault ( ) ? . GetSyntax ( ) is ClassDeclarationSyntax clsDecl )
787829 {
788- context . ReportDiagnostic (
789- Diagnostic . Create (
790- MissingPublicImplementationRule ,
791- clsDecl . Identifier . GetLocation ( ) ,
792- implementedInterface . Name ) ) ;
830+ if ( clsDecl . Modifiers . Any ( ) )
831+ {
832+ // Start from first modifier (internal) and go through the class name
833+ context . ReportDiagnostic (
834+ Diagnostic . Create (
835+ MissingPublicImplementationRule ,
836+ Location . Create (
837+ clsDecl . SyntaxTree ,
838+ TextSpan . FromBounds (
839+ clsDecl . Modifiers . First ( ) . SpanStart ,
840+ clsDecl . Identifier . Span . End ) ) ,
841+ implementedInterface . Name ) ) ;
842+ }
843+ else
844+ {
845+ // If no modifiers, start from class keyword through the class name
846+ context . ReportDiagnostic (
847+ Diagnostic . Create (
848+ MissingPublicImplementationRule ,
849+ Location . Create (
850+ clsDecl . SyntaxTree ,
851+ TextSpan . FromBounds (
852+ clsDecl . Keyword . SpanStart ,
853+ clsDecl . Identifier . Span . End ) ) ,
854+ implementedInterface . Name ) ) ;
855+ }
793856 }
794857
795858 // Must have a public parameterless constructor
@@ -855,7 +918,7 @@ private static void AnalyzeCompilationEnd(
855918 context . ReportDiagnostic (
856919 Diagnostic . Create (
857920 NoSingleInterfaceRule ,
858- ifDecl . GetLocation ( ) ) ) ;
921+ ifDecl . Identifier . GetLocation ( ) ) ) ;
859922 }
860923 }
861924 else if ( osInterfaces . Count > 1 )
@@ -869,8 +932,14 @@ private static void AnalyzeCompilationEnd(
869932 // Create a comma-separated list of interface names
870933 var interfaceNames = string . Join ( ", " , osInterfaces . Keys . OrderBy ( n => n ) ) ;
871934 // Report diagnostic indicating multiple OSInterfaces
872- context . ReportDiagnostic (
873- Diagnostic . Create ( ManyInterfacesRule , firstSyntax . GetLocation ( ) , interfaceNames ) ) ;
935+ foreach ( var osInterface in osInterfaces . Values )
936+ {
937+ context . ReportDiagnostic (
938+ Diagnostic . Create (
939+ ManyInterfacesRule ,
940+ osInterface . Syntax . Identifier . GetLocation ( ) ,
941+ interfaceNames ) ) ;
942+ }
874943 }
875944 else
876945 {
@@ -890,7 +959,7 @@ private static void AnalyzeCompilationEnd(
890959 context . ReportDiagnostic (
891960 Diagnostic . Create (
892961 MissingImplementationRule ,
893- syntax . GetLocation ( ) ,
962+ syntax . Identifier . GetLocation ( ) ,
894963 symbol . Name ) ) ;
895964 }
896965 else if ( implementations . Count > 1 )
@@ -899,13 +968,16 @@ private static void AnalyzeCompilationEnd(
899968 var implNames = string . Join ( ", " ,
900969 implementations . Select ( i => i . Name ) . OrderBy ( n => n ) ) ;
901970
902- // Report diagnostic indicating multiple implementations
903- context . ReportDiagnostic (
904- Diagnostic . Create (
905- ManyImplementationRule ,
906- syntax . GetLocation ( ) ,
907- symbol . Name ,
908- implNames ) ) ;
971+ // Report diagnostics indicating multiple implementations
972+ foreach ( var implementingClass in implementations )
973+ {
974+ context . ReportDiagnostic (
975+ Diagnostic . Create (
976+ ManyImplementationRule ,
977+ implementingClass . Locations . First ( ) , // Location of each implementing class
978+ symbol . Name ,
979+ implNames ) ) ;
980+ }
909981 }
910982 }
911983
@@ -920,18 +992,15 @@ private static void AnalyzeCompilationEnd(
920992
921993 foreach ( var duplicate in duplicates )
922994 {
923- // Get the first struct (ordered by name) for consistent error reporting
924- var firstStruct = duplicate . OrderBy ( d => d . Name ) . First ( ) ;
925-
926- // Create a comma-separated list of struct names that share the same name
927- var structNames = string . Join ( ", " , duplicate . Select ( d => d . Name ) . OrderBy ( n => n ) ) ;
928-
929- context . ReportDiagnostic (
930- Diagnostic . Create (
931- DuplicateNameRule ,
932- firstStruct . Locations . First ( ) ,
933- structNames ,
934- duplicate . Key ) ) ;
995+ // Report diagnostic for each duplicate instance
996+ foreach ( var struct_ in duplicate )
997+ {
998+ context . ReportDiagnostic (
999+ Diagnostic . Create (
1000+ DuplicateNameRule ,
1001+ struct_ . Locations . First ( ) ,
1002+ duplicate . Key ) ) ;
1003+ }
9351004 }
9361005 }
9371006
0 commit comments